What this is about: Export AP logs for successful logins to a txt file. Use the txt file to compare each following login from the same AP: Is new login on the list or not. So, 1) Find and export the relevant data 2) Create a Pipeline and test new logins against this data. I am no docker/Graylog expert. This is more like 'note-to-self" kind of thing. Please check with Docker or Graylog documentation.

I would like to have some ideas about the whereabouts of my APs. Especially logins. Ideally OpenWrt would simply log successful/failed logins and that would be the end of this. However, I cannot figure out how to log the failed attempt ... still. What I can get from OpenWrt is successful logins. Like:

Feb 23 10:31:40 SQUARE dnsmasq-dhcp[5815]: DHCPDISCOVER(br-lan) 192.168.3.214 8a:9e:63:c5:52:30 
Feb 23 10:31:40 SQUARE dnsmasq-dhcp[5815]: DHCPOFFER(br-lan) 192.168.3.214 8a:9e:63:c5:52:30 
Feb 23 10:31:40 SQUARE dnsmasq-dhcp[5815]: DHCPREQUEST(br-lan) 192.168.3.214 8a:9e:63:c5:52:30 
Feb 23 10:31:40 SQUARE dnsmasq-dhcp[5815]: DHCPACK(br-lan) 192.168.3.214 8a:9e:63:c5:52:30 bingtrek
Feb 23 10:31:50 SQUARE dnsmasq-dhcp[5815]: DHCPDISCOVER(br-lan) 192.168.3.214 8a:9e:63:c5:52:30 
Feb 23 10:31:50 SQUARE dnsmasq-dhcp[5815]: DHCPOFFER(br-lan) 192.168.3.214 8a:9e:63:c5:52:30 
Feb 23 10:31:50 SQUARE dnsmasq-dhcp[5815]: DHCPREQUEST(br-lan) 192.168.3.214 8a:9e:63:c5:52:30 
Feb 23 10:31:50 SQUARE dnsmasq-dhcp[5815]: DHCPACK(br-lan) 192.168.3.214 8a:9e:63:c5:52:30 bingtrek
Feb 23 11:46:32 SQUARE dnsmasq-dhcp[5815]: DHCPREQUEST(br-lan) 192.168.3.126 5b:14:f1:c9:d2:7d 
Feb 23 11:46:32 SQUARE dnsmasq-dhcp[5815]: DHCPACK(br-lan) 192.168.3.126 5b:14:f1:c9:d2:7d piptrip

Above is all about successful login events (and generated nonsense for this write). So, if I can only get successful logins and I really is most interested in failed or new logins, I will have to 1) create a list off known clients (previous logged in clients) and 2) compare this list against each new DHCPACK. It is not ideal: Hard to imagen a successful login from an unknown device and failed logins are still not logged. Also; the 'all clear' list needs to be updated for each new valid client manually. For the 5 or 6 devices connected to the AP and not much else happening, it is not perfect, but it is something. 

Logs from the OpenWrt AP ends up in Graylog and a GROK extractor is configured (format is slightly different):

SQUARE\s%{GREEDYDATA:source}\[%{NUMBER:sourcenr}\]\:\s%{GREEDYDATA:message}\(br-lan\)\s(%{IPV4:client})?(\s)?%{COMMONMAC:mac}(\s)?(%{WORD:hostname})?

From the above GROK patten I get the below fields which are much easier to work with (The last part of the GROK patten was added because some devices are not providing a hostname. Therefore, hostname is optional). Fields generated from the above GROK patten:

BASE10NUM       5815
client          192.168.3.213
facility        system daemon
facility_num    3
hostname        piptrip
level           6
mac             5b:14:f1:c9:d2:7d
message         DHCPACK
source          dnsmasq-dhcp
sourcenr        5815
timestamp       2022-08-18 15:41:00.000

It is not much but IP, MAC and hostname is logged for client logins. First column header is "mac". Second column header is "hostname". Exported distinct data from Graylog (https://docs.graylog.org/docs/csv-export):

"mac","hostname"
"8a:9e:63:c5:52:30","bingtrek"
"5b:14:f1:c9:d2:7d","vintagestar"
"5d:2f:6d:9f:2e:df","piptrip"
"d9:ba:c5:6f:21:b8","starstah"
"5a:de:5b:83:fa:a7","purepip"

The above format is generated from Graylog CSV export, but Graylog is very flexible when importing the data so the format should not matter much. With the white-list prepared, it is time to setup the Lookup table and the pipeline to use it.

The Lookup Table consist of 3 parts: Lookup table, Cache and Data Adapters. First create the Data Adapter and Cache and with these two parts, you can create the Lookup Table. The Data Adapter provide the actual data for the lookup table. I.e., the data adapter will point to the generated whitelist txt file. There is a lot of different options here for making the txt file available for the Data Adapter. A Graylog container will already have some persisted storage configured for "/usr/share/graylog/data". Just need to find the mount point. Snip from docker-compose.yml:

...
 volumes:
      - graylog_data:/usr/share/graylog/data
...

The same Graylog data volume:

$ docker volume ls
DRIVER    VOLUME NAME
...
local     graylog_data
...

Find local mount point for Graylog_data volume:

$ docker volume inspect graylog_data 
...
        "Mountpoint": "/var/lib/docker/volumes/graylog_graylog_data/_data",
...

So, I can copy the lookup mac/hostname txt file to "/var/lib/docker/volumes/greylog_greylog_data/_data" and inside the Graylog container, this file will be available in: "/usr/share/graylog/data". From the host:

sudo ls -l /var/lib/docker/volumes/graylog_graylog_data/_data
total 32
drwxr-x--- 2 1100 1100 4096 Jan 17  2022 config
drwxr-xr-x 2 1100 1100 4096 Jan 17  2022 contentpacks
drwxr-x--- 2 1100 1100 4096 Jan  5  2022 data
drwxr-x--- 3 1100 1100 4096 Aug 19 15:28 journal
drwxr-xr-x 2 1100 1100 4096 Jan 17  2022 libnative
drwxr-x--- 2 1100 1100 4096 Jan  5  2022 log
-rw-r--r-- 1 root root  158 Aug 11 16:15 mac.csv        <- mac/hostname txt file.
drwxr-x--- 2 1100 1100 4096 Jan  5  2022 plugin
$

And inside the container:

$ ls -l /usr/share/graylog/data
total 32
drwxr-x--- 2 graylog graylog 4096 Jan 17  2022 config
drwxr-xr-x 2 graylog graylog 4096 Jan 17  2022 contentpacks
drwxr-x--- 2 graylog graylog 4096 Jan  5  2022 data
drwxr-x--- 3 graylog graylog 4096 Aug 19 13:31 journal
drwxr-xr-x 2 graylog graylog 4096 Jan 17  2022 libnative
drwxr-x--- 2 graylog graylog 4096 Jan  5  2022 log
-rw-r--r-- 1 root    root     158 Aug 11 14:15 mac.csv        <- Point to this.
drwxr-x--- 2 graylog graylog 4096 Jan  5  2022 plugin

1) Data Adapter: From Graylog: System > Lookup Tables. Click "Data Adapter" (top right) and "Create data adapter".

Data Adapter type CSV file  
Title and Description All square MAC addresses Anything you want
Name local-mac Short and descriptive
File path /usr/share/graylog/data/mac.csv Point to the mac/hostname txt file
Check interval 300  Static file, so pretty high value
Separator , Depends on the format of the txt file. For the above file, the default values are OK
Quote character " Same as above. Use what is relevant format for your txt file.
Key column mac The lookup value (MAC address) Header of the first column in the txt file.
Value column hostname Return value of the lookup. The corresponding hostname of the MAC address. Header of the second column in the txt file.
Allow case-insensitive lookups   If relevant for your data


2) Cache: From Graylog: System > Lookup Tables. Click "Caches" (top right) and "Create cache".

Cache Type Node-local, in-memory cache  
Title and Description   Anything you want
Name local-mac  
Maximum entries 50 What fits your hardware/inputs/data throughput
Expire after access disabled What fits your hardware/inputs/data throughput
Expire after write disabled What fits your hardware/inputs/data throughput


3) Lookup table: From Graylog: System > Lookup Tables. Click "Lookup Tables" (top right) and "Create lookup table".

Title and Description   Anything you want
Name local-mac Short and descriptive
Enable single default value Enable  
Default single value not found (string) The lookup table will return this value then there is no match. 
Data Adapter local-mac Data Adapter created in step 1)
Cache local-mac Cache created in step 2)

 

Test the Lookup Table: System > Lookup Tables. Find the newly configured Lookup table.

Graylog include a very nice test feature for the lookup table. Plug in a MAC addresses from the list in the 'Key' field and the result should be the host name. Likewise plug in anything else and the result should be: ‘not found’

Now we will create a pipeline that will use the Lookup Table to ... lookup new logins from the AP: Test if the new MAC address  is on the list. If on the list it will return the corresponding host name. If not on the list, it will return 'not found'.

From Graylog: System > Pipelines. Click "Manage rules" (top right) and "Create Rule" (I assume you already have a pipeline for the relevant stream).

Description Something descriptive
Rule source rule "lookup table for allowed mac"
when
    has_field("mac")
then    
    let lookupvalue = lookup_value("local-mac", $message.mac);
    set_field("known_id",lookupvalue);
end

For this to work:

1) Messages in the stream have a field called 'mac' (the field was created with the GROK patten above)
2) The lookup table is called: local-mac (created in step 3)

What this pipeline does:
Compare the incoming message 'mac' field with the mac column in the Lookup Table. If there is a match, a new field called 'know_id' will be added to the message. The value of the field will be the hostname in the hostname (second) column.

Lots of different ways to present the data on the dashboard. A count for 'not found' would fit the purpose fine. For a test run I use a bar chart for showing login counts for different clients.

Graylog dashboard