Home Assistant – Garbage Reminder

Overview

This post describes how I made a garbage reminder that changes every week.  In my area, rubbish collection is on a two week cycle as per the following table

Week 1Week2
MondayRecycling / Council Rubbish
TuesdayGlassGreen waste / Rubbish Bin
Table showing what is picked up when

There has been some changes to what Home Assistant (HA) can provide, so this is all being done using the native options, without relying on third party plugins.  This solution allows for an image to change depending upon the week.  There are three components to make this solution

  1. Toggle switch is the brains of the system
  2. Picture card – displays an image depending upon the position of the toggle switch
  3. Calendar event – toggles the switch when an event occurs.

Toggle Switch

Lets get the brains of this solution configured first

  • Go to Settings > Devices and Services > Helpers
  • Create a new helper of type Toggle
  • I have called my Recycle Week and when turned on represents week 1 of my schedule
  • Make note of the entity ID as we will need this (input_boolean.recycle_week).
  • Set the toggle to on to represent the first week

Picture Card

Now we have our toggle switch, lets now get the picture card working, and then we can toggle the switch as required and confirm all works as expected.  For this blog, I have used two random pictures to represent each week.  Later I will update these to represent actual pictures of what I want.

I did this with XML code.  Not sure if you can do this with built in editor.

  1. Switch to a dashboard where you want the card to be displayed
  2. Click the Add Card button
  3. Select Picture Entity
  4. Select the Show code editor entity
  5. Replace the XML with the code below
  6. Make modifications as follows:
    1. Set the input_boolean value as created above
    2. Change the photos URL (see below)
  7. Click Save
type: picture-entity
entity: input_boolean.recycle_week
show_name: false
show_state: false
state_image:
  'on': >-
    https://images.unsplash.com/photo-1682686581551-867e0b208bd1?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHx8
  'off': >-
    https://images.unsplash.com/photo-1703555508141-4397207fc6d2?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw1fHx8ZW58MHx8fHx8
tap_action: none

Testing Image Toggling

On the same dashboard

  1. Press Add Card
  2. Change tab to By Entity
  3. Search or scroll and find Recycle Week entity
  4. Put a tick in the checkbox next to it
  5. Press Continue
  6. Press Add to Dashboard
  7. Toggle the slide switch of Recycle Switch card and the image in the picture card should change.
  8. Once satisfied that this works, then delete the Recycle Week toggle card if desired.

Note: We are only temporarily putting this on the dash board for us to test with.  Once working, then we can remove this card.

Calendar

Recurring event

If you do not have the local calendar integration installed, then you need to go into settings and install it.  This will allow us to create a recurring schedule entry.

Create a new calendar (I have called mine Automations) where we are going to put into the repeating event.

Add a repeating calendar event, weekly.  Since I have a Monday/Tuesday collection, I have made the event occur at 4:30pm on a Tuesday.  I have chosen then, as by 4:30 on a Tuesday it is too late to put out the Rubbish, and then from Wednesday onward, it is correct.  However, chose a time that suits yourself.  Make a note of the name of the event you created, as we need this later – my event is called Change Recycling Week

Set the duration of the calendar event to 1 minute.  We are only going to be reacting to the start of the event.

Automation

Now we have an event that is recurring, we now need to set up and automation that fires when the calendar event starts.

  1. Go to Settings > Automations & Scenes
  2. Under the Automations tab, select Create Automation
  3. Select Create a new automation
  4.  Under Triggers, select Add Trigger
  5. Select Calendar
  6. Select the calendar that has the trigger event
  7. Leave the rest of the trigger options as default
  8. Add a condition
  9. Select Template
  10. add the following in, changing the value on the right hand side to equal what the calendar event is called: {{ trigger.calendar_event.summary == ‘Test Event’ }}
  11. Add an Action
  12. select: Call a service
  13. Select: Input Boolean: Toggle
  14. Select the entity we created: Recycle Week
  15. Click Save
  16. Give it a name, for example: Automation: Change Recycle Week
  17. Click Save

Testing it all out

Change the calender event time to be > 15 minutes from now (HA only reads the calendar every 15 minutes).  Make a note of the setting of the switch, and then see if it changes when the time elapses.

 

<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> Home Assistant | Leave a comment

Getting correct image size from Eagle PCB

So, having issues getting the correct print size from Eagle PCB on printer.  These steps seem to work for me

  • Create the board in Eagle
  • Turn off all layers except those wanted, ie Top, Pads, Vias
  • File -> Export -> Image
    • Resolution: 600
    • Select Monochrome
    • Give a file name
    • Click OK
  • Open file in Gimp
    • Image -> Crop to Content
    • If need to invert image, ie Top mask overlay (Flip Horizontally & Flip vertically), Colors -> Invert
  • Save modified image
  • Print
    • Page Setup tab
      • Ensure scale is 100%
    • Image Settings tab
      • Draw Crop marks (if needed)
    • Color Tab
      • Monochrome
    • Advanced
      • Print Scaling: None
      • Print Optimization: Graphics
    • Press Print
<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> Uncategorized | Leave a comment

Multi Battery Charger – WiFi enabled

Overview

This project is about creating a multi battery charger that is smart enabled.  The circuit allows up to 8 batteries to be charged from a single battery charger.  When the battery charger is started, it starts charging one battery at a time, until all batteries have been charged.  At this point, the battery charger will turn off.

Features of the charger

  • Multiple batteries can be charged
  • Auto turn off when completed
  • Automatic changing to next battery when charged
  • Capable to handling 30A charge current
  • Simple interface
  • Voltage/Current display
  • Built with ESPHome
  • WiFi Enabled – allow to be integrated into Home Assistant
  • Bring your own charger – This device just switches between batteries, it is not responsible for the type of battery connected

Components

  • 8 Channel WiFi enabled relay board

Inputs / Output

Inputs

  • On/Off button
  • Start/Pause button
  • Next/Stop button
  • Current – ADC connection

Outputs

  • 8 Relay outputs
  • 8 LED’s showing relay status
  • Running LED
  • Charger relay on/off
  • Unit on LED

Connections

  • Running LED connected to the Charger relay on/off output
  • 8 LED outputs added as an extension on the built in HC595 serial/parallel converter
  • Current through the ADC pin
  • Unit On LED – on when power to the unit

Pseudo Code

Here is some pseudo code for the battery charger

Variables

  • unit_state (default value is on as unit will be on) – states can be on, off, pause, running
  • relay_active ( number of the current active relay) – default is 1
  • current_countdown – counts down number of below threshold readings – default 3

On/Off Button

  • Button press
    • If unit_state is off
      • set unit_state to on
      • Turn on all LEDs for 0.5 second (LED test)
      • Turn on LEDs to show number of active outputs
      • set relay_active=1
    • if unit_state is pause, running, on
      • Turn unit_state to off
      • Turn off all relays
      • Disable relays
      • Turn off charger relay

Start/Pause Button

  • Button Press
    • if unit_state is on or pause
      • if unit_state is on, then set relay_active to 1 (reset to start)
      • set unit_state to running
      • Turn off all LEDs
      • Turn on LED for active relay
      • Turn on relay for active LED
      • Enable relays
      • Enable charger relay
    • if unit_state is running
      • Turn off charger relay
      • Turn off active relay
      • Disable relays
      • set unit_state to pause
      • (Leave LED on to show current battery being charged)

Next / Reset Button

  • Button Press
    • if the button is pressed less than than 0.5 second (Next)
      • if unit_state is running
        • Turn off battery relay
        • Turn off battery LED
        • Increment relay_active
        • if relay_active > 8, then relay_active=1
        • Turn on battery LED
        • Wait 15 (?) seconds
        • Turn on battery relay
      • If unit_state is paused
        • Turn off battery LED
        • Increment relay_active
        • if relay_active > 8, then relay_active=1
        • Turn on battery LED
    • if button is pressed > 3 seconds (Reset)
      • Turn off battery relay
      • Disable relays
      • Turn off charger
      • set relay_active = 1
      • Turn on LEDs showing active outputs
      • set unit_state = on

Current Sense Reading

Current sense will be read every 15 seconds, and filtered to give an average over 3 readings.

  • if reading > minimum threshold
    • current_countdown = 3
  • if reading < minimum threshold
    • decrement current_countdown
    • if current_count = 0
      • Turn off battery relay
      • Turn off battery LED
      • Increment relay_active
      • if relay_active > 8,
        • Disable relays
        • Turn off charger
        • Turn off LEDs
        • set relay_active = 1
        • set unit_state to off
      • else
        • Turn on battery LED
        • Wait 15 (?) seconds
        • Turn on battery relay

Pin Outs for the Charger

ESP-12 PinFunction
IO 0
IO 2On/Off Start/Pause Button
IO4Next Button
IO 5595 Relay OE pin
IO 12595 Relay Latch Pin
IO 13595 Relay Latch Clock Pin
IO 14595 Relay Data Pin
IO 15Charger Relay
IO 16Status LED
TX
RX
ADCCurrent ADC

Relay control

The board comes with an HC595 controlling the relays.  The pin-outs are hard coded.  The following table shows the pins that are used on the ESP32, along with the connections to the chip

ESP-12 PinFunction74HC595 PinESPHome name
5!OE13oe_pin
12RCLK12latch_pin
13SRCLK11clock_pin
14SER14data pin
<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> Electronics | Leave a comment

Updating code into ESP chip from factory

When getting a new module/board, you need to load code into it.  After the code is created in ESPHome, download it for manual install, then use the following command to load it into the chip

esptool.py --chip auto -p /dev/ttyUSB0 write_flash 0x0 ~/snap/chromium/current/Downloads/file.bin

 

<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> Uncategorized | Leave a comment

Using AVR High Voltage Parallel Programmer

Purchased the below pictured AVR programmer from Aliexpress.  Needed to use AVR Studio to get it going until I found the magic sauce.

Well, the magic sauce is this for linux, use the device type of stk500pp (parallel programming).  Once this is done, works a treat.  However, inside of Eclipse, cannot seem to read fuse values, or device type.  Maybe there is a plug in update, but at least it allows to program devices.

For a command line option, this is what works

avrdude -p m88p -c stk500pp -P /dev/ttyUSB0 -b 115200 -U signature:r:file.bin

 

1

<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> AVR | Leave a comment

Azure AD Roles and authentication methods

So, I was trying to improve the MS Security score, and the awesome feature of the admin users without MFA secure score item, is that it cannot tell you who exactly does not have it.  It only gives you a number.  So, I set about to create a script, using MS Graph to enable this to occur.  This script provides

  • List of all roles that have users allocated to them, and the users that are assigned the role
  • List of authentication methods for all users who have Azure AD Roles
# Import the module
# If you need to install the module, use "Install-Module Microsoft.Graph".  Be prepared to wait 5-20 minutes for it to install though!

if (Get-InstalledModule -Name Microsoft.Graph -ErrorAction SilentlyContinue) {
    Import-Module Microsoft.Graph.Identity.DirectoryManagement
    Import-Module Microsoft.Graph.Identity.SignIns
} else {
    write-host -foregroundColor Yellow "Sorry - need to install PS module.  Use Install-Module Microsoft.Graph (prepare to wait 5-20 minutes for this to finish!)"
    exit
}

# Connect with the rights needed
Write-host "Connecting to Tenant..."
Connect-MgGraph -Scopes "User.Read.All", "RoleManagement.Read.Directory", "UserAuthenticationMethod.Read.All"

# Get list of directory roles
Write-host "Getting list of roles..."
$roles = Get-MgDirectoryRole
$users = @{}

# Iterate through the roles showing who has what roles
foreach ($role in $roles) {
    $members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.id
    if ($members) {
        write-host $role.DisplayName 
        foreach ($member in $members) {
            $user = Get-MgUser -UserId $member.Id
            $users[$member.Id] = $user.UserPrincipalName
            write-host "  $($user.UserPrincipalName)"
        }
        write-host 
    }
}

# Now we have a list of users, lets find out what authentication methods they are using
Write-host -foregroundColor Yellow "User authentication methods"
write-host "Gathering information: " -NoNewline
$userAuthMethods = @()
foreach ($userKey in $users.Keys) {
            $authenticatorMethods = Get-MgUserAuthenticationMethod -UserId $userKey
            $methods = ""
            foreach ($method in $authenticatorMethods) {
                $properties = $method.AdditionalProperties 
                foreach ($key in $properties.keys) {
                    if ($key -eq '@odata.type') {
                        $type = $properties[$key] -replace "#microsoft.graph.", ""
                        $type = $type -replace "Method", ""
                        $methods += " $type,"
                        
                    }
                }
            }
            Write-Host '.' -NoNewline
            $methods = $methods.Trim(',',' ')
            $obj = New-Object -TypeName psobject
            $obj | Add-Member -MemberType NoteProperty -Name Name -Value $users[$userkey]
            $obj | Add-Member -MemberType NoteProperty -Name AuthenticationMethods -Value $methods
            $userAuthMethods += $obj
#            write-host "  $($users[$userkey]) - [$methods]"
}
write-host
$userAuthMethods | Sort-Object -Property Name
Disconnect-Graph

<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> Powershell | Leave a comment

Cakephp JQuery Bootstrap and Modal forms

Wanted to create a pop-up modal form to edit, view and create database information.  The default is, of course, to go to a new page.  I thought that I would try and work through it to make it more HTML2.0 like.  Here is what you need to do…

Set up the main view

First off, we need to set up the main view to hold the modal dialog code.  So, in the index.php template, or whatever main view template you want to do, add the following code to define the modal dialog box…

<!-- Modal -->
<div class="modal fade" id="myModal" role="dialog">
    <div class="modal-dialog modal-lg">
    
        <!-- Modal content-->
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title" id="modal-title">Popup Window</h4>
                <button type="button" class="close" data-dismiss="modal">&times;</button>
            </div>
            <div class="modal-body">

            </div>
 <!--            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
            </div> -->
        </div>
      
    </div>
</div>

We also need to add some Javascript to the template file. We do this as blocks to ensure it is loaded after the JQuery library is loaded. The following shows two types of ways to include the scripts. One we load in, and the other is inline. Note, that we don’t need the `test` file in this blog, but shows how to load it in should you want to do this instead of being inline. Note: it is best practice to not have javascript inline any longer.

<?= $this->Html->script('test', ['block' => 'script']);?>

<?php $this->Html->scriptStart(['block' => true]);?>
$(document).ready(function() {
	    $('.openPopup').on('click',function(){
	    var title = $(this).attr('data-title');
        var dataURL = $(this).attr('data-href');
        $('.modal-title').text(title);
        $('.modal-body').load(dataURL,function(){
            $('#myModal').modal({show:true});
        });
    }); 
});
<?php $this->Html->scriptEnd();?>

To make use of this, we need to define two items in the button, or link to open the code. The first parameter is `data-title` which has the title of the modal box. `data-href` is the URL of the page to be loaded into the modal dialog box. Examples of this

Note that when using Cake, we don’t use the button type, as this triggers a connection to the backend server

<?= $this->Html->tag('a', __('Rename'), ['class' => 'btn btn-xs btn-outline-primary openPopup', 'escape' => false, 'data-title'=>'Edit Framework', 'data-href'=>'/admin/frameworks/edit/'.$framework->id]) ?>
<a href="javascript:void(0);" data-title="This is a title" data-href="/admin/framework-subcontrols/edit/04eff439-88be-4765-b6e8-aed6102f7e4f" class="openPopup">About Us</a>
<a class="btn btn-xs btn-outline-primary openPopup" data-href="<?= $this->Url->build(['controller' => 'FrameworkSubcontrols', 'action' => 'view', $frameworkSubcontrols->id]); ?>">view</a>

Ensure that you have the openPopup class element on each button that is used, to trigger the modal dialog box.

To close the modal dialog box, use the following code snippet in the view

<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> CakePHP, Programming | Leave a comment

Davmail and Office 365 MFA

Needed to set up getting email from Office365 for some DMARC reports through Elastic.  Parsedmarc requires a connection to an IMAP server, so what do you do, then when the account is on Office 365, and you need to use MFA, and app passwords are disabled.

So, a program that can do this is DAVmail.  It can run in headless mode, and do the bridge of IMAP from client, and EWS using MFA.  I used Docker to run the application.

This is my docker-compose file

version: '2.2'
  services:
    davmail:
      container_name: "davmail"
      image: kran0/davmail-docker:latest
      ports:
        - 1143:1143
      volumes:
        - ./davmail/davmail.properties:/davmail/davmail.properties
        - /var/log/davmail365:/var/log/davmail
      restart: always

 

The davmail\davmail.properties file starts off list this.  This will enable you to test the connection using standard IMAP, if you are able to do it (account without MFA).

# DavMail settings, see http://davmail.sourceforge.net/ for documentation

#############################################################
# Basic settings

# Server or workstation mode
davmail.server=true
# Exchange/Office 365 connection mode:
# - O365Modern Office 365 modern authentication (Oauth2)
# - O365Interactive Office 365 with interactive browser window, not available in headless mode (OpenJFX required)
# - O365Manual Office 365 with interactive dialog, not available in headless mode
# - O365 Office 365 EWS mode
# - EWS Exchange 2007 and later
# - WebDav Exchange 2007 and earliear WebDav mode
# - Auto WebDav mode with EWS failover
davmail.mode=EWS
# base Exchange OWA or EWS url
davmail.url=https://outlook.office365.com/EWS/Exchange.asmx

# Listener ports
#davmail.caldavPort=1080
davmail.imapPort=1143
#davmail.ldapPort=1389
#davmail.popPort=1110
#davmail.smtpPort=1025

#############################################################
# Network settings

# Network proxy settings
davmail.enableProxy=false
davmail.useSystemProxies=false
davmail.proxyHost=
davmail.proxyPort=
davmail.proxyUser=
davmail.proxyPassword=

# proxy exclude list
davmail.noProxyFor=

# allow remote connection to DavMail
davmail.allowRemote=true
# bind server sockets to a specific address
davmail.bindAddress=
# client connection timeout in seconds - default 300, 0 to disable
davmail.clientSoTimeout=

# DavMail listeners SSL configuration
davmail.ssl.keystoreType=
davmail.ssl.keystoreFile=
davmail.ssl.keystorePass=
davmail.ssl.keyPass=

# Accept specified certificate even if invalid according to trust store
davmail.server.certificate.hash=

# disable SSL for specified listeners
davmail.ssl.nosecurecaldav=false
davmail.ssl.nosecureimap=true
davmail.ssl.nosecureldap=false
davmail.ssl.nosecurepop=false
davmail.ssl.nosecuresmtp=false

# disable update check
davmail.disableUpdateCheck=true

# Send keepalive character during large folder and messages download
davmail.enableKeepAlive=true
# Message count limit on folder retrieval
davmail.folderSizeLimit=0
# Default windows domain for NTLM and basic authentication
davmail.defaultDomain=

#############################################################
# Caldav settings

# override default alarm sound
davmail.caldavAlarmSound=
# retrieve calendar events not older than 90 days
davmail.caldavPastDelay=90
# EWS only: enable server managed meeting notifications
davmail.caldavAutoSchedule=true
# WebDav only: force event update to trigger ActiveSync clients update
davmail.forceActiveSyncUpdate=false

#############################################################
# IMAP settings

# Delete messages immediately on IMAP STORE \Deleted flag
davmail.imapAutoExpunge=true
# Enable IDLE support, set polling delay in minutes
davmail.imapIdleDelay=
# Always reply to IMAP RFC822.SIZE requests with Exchange approximate message size for performance reasons
davmail.imapAlwaysApproxMsgSize=

#############################################################
# POP settings

# Delete messages on server after 30 days
davmail.keepDelay=30
# Delete messages in server sent folder after 90 days
davmail.sentKeepDelay=90
# Mark retrieved messages read on server
davmail.popMarkReadOnRetr=false

#############################################################
# SMTP settings

# let Exchange save a copy of sent messages in Sent folder
davmail.smtpSaveInSent=true

#############################################################
# Loggings settings

# log file path, leave empty for default path
davmail.logFilePath=/var/log/davmail.log
# maximum log file size, use Log4J syntax, set to 0 to use an external rotation mechanism, e.g. logrotate
davmail.logFileSize=1MB
# log levels
log4j.logger.davmail=WARN
log4j.logger.httpclient.wire=WARN
log4j.logger.org.apache.commons.httpclient=WARN
log4j.rootLogger=WARN

#############################################################
# Workstation only settings

# smartcard access settings
davmail.ssl.pkcs11Config=
davmail.ssl.pkcs11Library=

# SSL settings for mutual authentication
davmail.ssl.clientKeystoreType=
davmail.ssl.clientKeystoreFile=
davmail.ssl.clientKeystorePass=

# disable all balloon notifications
davmail.disableGuiNotifications=false
# disable tray icon color switch on activity
davmail.disableTrayActivitySwitch=false
# disable startup balloon notifications
davmail.showStartupBanner=true

# enable transparent client Kerberos authentication
davmail.enableKerberos=false

log4j.logger.org.apache.commons.httpclient=WARN
log4j.logger.httpclient.wire=WARN
log4j.rootLogger=WARN
log4j.logger.davmail=DEBUG

If the account has MFA then the above config will work if App Passwords are allowed.  If they are not allowed, then it wont work.  To get this working, you need to change the line

`davmail.mode=EWS` to `davmail.mode=O365Modern`.

Set the account to use the Microsoft Authenticator for MFA.  Then connect to the davmail output, and you should see it pop up with a URL during signin

https://login.microsoftonline.com/common/oauth2/authorize?client_id=UUID HERE&response_type=code&redirect_uri=https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient&response_mode=query&login_hint=useraccount&resource=https%3A%2F%2Foutlook.office365.com

At this point, copy the URL into a browser and authenticate.  You will be prompted for MFA.  Accept the permissions that are being added to the account for DAVmail.  The browser will stay at a blank screen.

To continue on, but may not be accurate.  I noticed that it was polling for MFA again, so accepted, and then away it goes – IMAP through MFA account.

DAVmail will keep the token renewed, so that is great.

<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> Uncategorized | Leave a comment

Cakephp DebugKit not working

I run cakephp in docker containers behind a reverse proxy.  This means that there is common logging, Lets Encrypt certs when required etc.  However, CakePHP is normally run on port 80 in a docker container, so that means when you try and enable debug kit, the browser will complain about mixed content and NOT display the debugKit panel.

As can be seen, unless you look for it, you will go down the ‘rabbit hole of pain’, to try and find the issue.  To resolve, there are a couple of options

  • Set up the docker container to run on HTTPS – NGINX can handle a self signed cert so wont cause a problem
  • Force Cakephp to use a baseURL

Force CakePHP to use baseURL

This is the approach I took, as seemed easier, and since it should only be used in development, means locally its fine.

Edit config\app_local.php and add the following
/* 'App' => [
'fullBaseUrl' => 'https://url_to_site',
], */

This will tell CakePHP to use the BaseURL for any links that it generates, which as client is connecting to server with SSL, this all works out.

<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> CakePHP | Leave a comment

NGINX Access log and NGINX Filebeat modification

Modifed the filebeat NGINX module, as the access log was extended to include upstream metrics.

log_format log_requests '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" '
                        '"$host" $SSL_protocol "$SSL_cipher" "$upstream_addr" '
                        'rt=$request_time uct=$upstream_connect_time '
                        'uht=$upstream_header_time urt=$upstream_response_time';

This is basically the combined log format with the additional metrics of

  • host – server block name, ie www.thesmithcave.nz
  • $SSL_protocol – TLS protocol, ie TLSv1.2
  • $SSL_cipher – Cipher used for the SSL connection
  • $upstream_addr – backend server IP Address
  • $rt, uct,uht and urt as time values

To be able to use this with Elastics filebeat module requires a few changes to the ingest pipeline.  The changes are as follows:

  • Add a replacement to replace rt=-, uct=-, uht=- or urt=- to be =0 instead of =-
  • Added additional GROK pattern to parse the above

NOTE:  If the module is updated, then the ingest pipeline will need to be modified before ingestion occurs

The full file of /usr/share/filebeat/module/nginx/access/ingest/pipeline.yml:

 

description: Pipeline for parsing Nginx access logs. Requires the geoip and user_agent
  plugins.
processors:
- set:
    field: event.ingested
    value: '{{_ingest.timestamp}}'
- gsub:
    field: message
    pattern: "urt=-"
    replacement: "urt=0"
- gsub:
    field: message
    pattern: "uht=-"
    replacement: "uht=0"
- gsub:
    field: message
    pattern: "uct=-"
    replacement: "uct=0"
- gsub:
    field: message
    pattern: "rt=-"
    replacement: "rt=0"
- grok:
    field: message
    patterns:
    - (%{NGINX_HOST} )?"?(?:%{NGINX_ADDRESS_LIST:nginx.access.remote_ip_list}|%{NOTSPACE:source.address}) - (-|%{DATA:user.name}) \[%{HTTPDATE:nginx.access.time}\] "%{DATA:nginx.access.info}" %{NUMBER:http.response.status_code:long} %{NUMBER:http.response.body.bytes:long} "(-|%{DATA:http.request.referrer})" "(-|%{DATA:user_agent.original})" "%{DATA:url.host}" %{DATA:tls.protocol} "%{DATA:tls.cipher}" "%{DATA:nginx.upstream.backend}" rt=%{NUMBER:nginx.request.time:float} uct=%{NUMBER:nginx.upstream.connect.time:float} uht=%{NUMBER:nginx.upstream.header.time:float} urt=%{NUMBER:nginx.upstream.response.time:float}
    - (%{NGINX_HOST} )?"?(?:%{NGINX_ADDRESS_LIST:nginx.access.remote_ip_list}|%{NOTSPACE:source.address})
      - (-|%{DATA:user.name}) \[%{HTTPDATE:nginx.access.time}\] "%{DATA:nginx.access.info}"
      %{NUMBER:http.response.status_code:long} %{NUMBER:http.response.body.bytes:long}
      "(-|%{DATA:http.request.referrer})" "(-|%{DATA:user_agent.original})"
    pattern_definitions:
      NGINX_HOST: (?:%{IP:destination.ip}|%{NGINX_NOTSEPARATOR:destination.domain})(:%{NUMBER:destination.port})?
      NGINX_NOTSEPARATOR: "[^\t ,:]+"
      NGINX_ADDRESS_LIST: (?:%{IP}|%{WORD})("?,?\s*(?:%{IP}|%{WORD}))*
    ignore_missing: true
- grok:
    field: nginx.access.info
    patterns:
    - '%{WORD:http.request.method} %{DATA:url.original} HTTP/%{NUMBER:http.version}'
    - ""
    ignore_missing: true
- remove:
    field: nginx.access.info
- split:
    field: nginx.access.remote_ip_list
    separator: '"?,?\s+'
    ignore_missing: true
- split:
    field: nginx.access.origin
    separator: '"?,?\s+'
    ignore_missing: true
- set:
    field: source.address
    if: ctx.source?.address == null
    value: ""
- script:
    if: ctx.nginx?.access?.remote_ip_list != null && ctx.nginx.access.remote_ip_list.length > 0
    lang: painless
    source: >-
      boolean isPrivate(def dot, def ip) {
        try {
          StringTokenizer tok = new StringTokenizer(ip, dot);
          int firstByte = Integer.parseInt(tok.nextToken());
          int secondByte = Integer.parseInt(tok.nextToken());
          if (firstByte == 10) {
            return true;
          }
          if (firstByte == 192 && secondByte == 168) {
            return true;
          }
          if (firstByte == 172 && secondByte >= 16 && secondByte <= 31) {
            return true;
          }
          if (firstByte == 127) {
            return true;
          }
          return false;
        }
        catch (Exception e) {
          return false;
        }
      }
      try {
        ctx.source.address = null;
        if (ctx.nginx.access.remote_ip_list == null) {
          return;
        }
        def found = false;
        for (def item : ctx.nginx.access.remote_ip_list) {
          if (!isPrivate(params.dot, item)) {
            ctx.source.address = item;
            found = true;
            break;
          }
        }
        if (!found) {
          ctx.source.address = ctx.nginx.access.remote_ip_list[0];
        }
      }
      catch (Exception e) {
        ctx.source.address = null;
      }
    params:
      dot: .
- remove:
    field: source.address
    if: ctx.source.address == null
- grok:
    field: source.address
    patterns:
    - ^%{IP:source.ip}$
    ignore_failure: true
- remove:
    field: message
- rename:
    field: '@timestamp'
    target_field: event.created
- date:
    field: nginx.access.time
    target_field: '@timestamp'
    formats:
    - dd/MMM/yyyy:H:m:s Z
    on_failure:
    - append:
        field: error.message
        value: '{{ _ingest.on_failure_message }}'
- remove:
    field: nginx.access.time
- user_agent:
    field: user_agent.original
    ignore_missing: true
- geoip:
    field: source.ip
    target_field: source.geo
    ignore_missing: true
- geoip:
    database_file: GeoLite2-ASN.mmdb
    field: source.ip
    target_field: source.as
    properties:
    - asn
    - organization_name
    ignore_missing: true
- rename:
    field: source.as.asn
    target_field: source.as.number
    ignore_missing: true
- rename:
    field: source.as.organization_name
    target_field: source.as.organization.name
    ignore_missing: true
- set:
    field: event.kind
    value: event
- append:
    field: event.category
    value: web
- append:
    field: event.type
    value: access
- set:
    field: event.outcome
    value: success
    if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code < 400" - set: field: event.outcome value: failure if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code >= 400"
- append:
    field: related.ip
    value: "{{source.ip}}"
    if: "ctx?.source?.ip != null"
- append:
    field: related.ip
    value: "{{destination.ip}}"
    if: "ctx?.destination?.ip != null"
- append:
    field: related.user
    value: "{{user.name}}"
    if: "ctx?.user?.name != null"
on_failure:
- set:
    field: error.message
    value: '{{ _ingest.on_failure_message }}'
<span class="entry-utility-prep entry-utility-prep-cat-links">Posted in</span> Uncategorized | Leave a comment