Get started with FilterX
In this tutorial, you build a FilterX block that processes iptables log messages. You learn how to filter messages, parse key=value pairs, modify a field, and send the result to a destination.
Prerequisites
- Docker. For details, see Install AxoSyslog with Docker. To use Podman instead, see Install AxoSyslog with Podman and replace all
dockercommands withpodman. - Basic knowledge of AxoSyslog configuration: sources, destinations, and log paths.
Sample messages
You use two sample messages in this tutorial. The first one is an iptables message from the kernel. It’s an RFC3164-formatted message with space-separated key=value pairs:
Dec 08 12:00:00 hostname.example kernel: IN=eth0 OUT= MAC=11:22:33:44:55:66:aa:bb:cc:dd:ee:ff:08:00 SRC=192.0.2.2 DST=192.168.0.1 LEN=40 TOS=0x00 PREC=0x00 TTL=232 ID=12345 PROTO=TCP SPT=54321 DPT=22 WINDOW=1023 RES=0x00 SYN URGP=0The second one is an sshd message. Your FilterX block must not let it through:
Dec 08 12:00:01 hostname.example sshd[1234]: Accepted publickey for alice from 192.0.2.2 port 54321 ssh2Step 1: Start AxoSyslog without FilterX
First, you run a minimal configuration that reads messages from a file and writes them to another file.
-
Create an empty directory and an empty input file:
Terminal window mkdir filterx-tutorial cd filterx-tutorial touch input.log -
Create a file called
filterx-tutorial.confwith the following content:Terminal window @version: current source s_tutorial { file("/tutorial/input.log" follow-freq(1)); }; destination d_tutorial { file("/tutorial/output.log" template("${PROGRAM}: ${MESSAGE}\n")); }; log { source(s_tutorial); destination(d_tutorial); }; -
Start AxoSyslog in a container. The container mounts the current directory as
/tutorial, and keeps running in the background.Terminal window docker run -d --name filterx-tutorial -v "$PWD:/tutorial" ghcr.io/axoflow/axosyslog:latest -f /tutorial/filterx-tutorial.conf -
Append the two sample messages to the input file:
Terminal window echo 'Dec 08 12:00:00 hostname.example kernel: IN=eth0 OUT= MAC=11:22:33:44:55:66:aa:bb:cc:dd:ee:ff:08:00 SRC=192.0.2.2 DST=192.168.0.1 LEN=40 TOS=0x00 PREC=0x00 TTL=232 ID=12345 PROTO=TCP SPT=54321 DPT=22 WINDOW=1023 RES=0x00 SYN URGP=0' >> input.log echo 'Dec 08 12:00:01 hostname.example sshd[1234]: Accepted publickey for alice from 192.0.2.2 port 54321 ssh2' >> input.log -
Check the output file:
Terminal window tail -n 2 output.logYou should see both messages:
Terminal window kernel: IN=eth0 OUT= MAC=11:22:33:44:55:66:aa:bb:cc:dd:ee:ff:08:00 SRC=192.0.2.2 DST=192.168.0.1 LEN=40 TOS=0x00 PREC=0x00 TTL=232 ID=12345 PROTO=TCP SPT=54321 DPT=22 WINDOW=1023 RES=0x00 SYN URGP=0 sshd: Accepted publickey for alice from 192.0.2.2 port 54321 ssh2
Keep the container running. In the next steps, you edit filterx-tutorial.conf, then apply the changes with the following command:
docker exec filterx-tutorial syslog-ng-ctl reloadYou should see:
Config reload successfulStep 2: Filter the messages
Now you add a FilterX block that lets only iptables messages through.
-
Replace the log path in
filterx-tutorial.confwith the following:Terminal window log { source(s_tutorial); filterx { ${PROGRAM} == "kernel"; ${MESSAGE} =~ "PROTO="; }; destination(d_tutorial); };A message passes the FilterX block only if every statement in the block is true. For details, see Truthy and falsy values . The
=~operator checks if the message matches a regular expression. For details, see FilterX operator reference. -
Reload the configuration:
Terminal window docker exec filterx-tutorial syslog-ng-ctl reload -
Append the two sample messages again:
Terminal window echo 'Dec 08 12:00:00 hostname.example kernel: IN=eth0 OUT= MAC=11:22:33:44:55:66:aa:bb:cc:dd:ee:ff:08:00 SRC=192.0.2.2 DST=192.168.0.1 LEN=40 TOS=0x00 PREC=0x00 TTL=232 ID=12345 PROTO=TCP SPT=54321 DPT=22 WINDOW=1023 RES=0x00 SYN URGP=0' >> input.log echo 'Dec 08 12:00:01 hostname.example sshd[1234]: Accepted publickey for alice from 192.0.2.2 port 54321 ssh2' >> input.log -
Check the output file:
Terminal window tail -n 3 output.logYou should see:
Terminal window kernel: IN=eth0 OUT= MAC=11:22:33:44:55:66:aa:bb:cc:dd:ee:ff:08:00 SRC=192.0.2.2 DST=192.168.0.1 LEN=40 TOS=0x00 PREC=0x00 TTL=232 ID=12345 PROTO=TCP SPT=54321 DPT=22 WINDOW=1023 RES=0x00 SYN URGP=0 sshd: Accepted publickey for alice from 192.0.2.2 port 54321 ssh2 kernel: IN=eth0 OUT= MAC=11:22:33:44:55:66:aa:bb:cc:dd:ee:ff:08:00 SRC=192.0.2.2 DST=192.168.0.1 LEN=40 TOS=0x00 PREC=0x00 TTL=232 ID=12345 PROTO=TCP SPT=54321 DPT=22 WINDOW=1023 RES=0x00 SYN URGP=0The first two lines are from step 1. The last line is new: the kernel message passed the filter, but the sshd message didn’t.
Step 3: Parse the key=value pairs
Next, you parse the iptables message into separate fields, and send them as JSON.
-
Add the following lines to the end of the FilterX block, after the two filter statements:
Terminal window iptables = parse_kv(${MESSAGE}, value_separator="=", pair_separator=" "); ${MESSAGE} = iptables;parse_kvsplits the message into key=value pairs, and stores them in theiptablesvariable.iptablesis a local FilterX variable, because its name doesn’t start with$. AxoSyslog doesn’t send local variables to the destination, unless you assign them to a name-value pair. For details, see FilterX variables in destinations .- Assigning the
iptablesdict to${MESSAGE}stores it as JSON, so the destination receives a JSON string. You don’t have to format it explicitly. To send it in another format, use a formatting function, likeformat_kv. For details, see Format data.
-
Reload the configuration, and append the two sample messages again, the same way as in Step 2 .
-
Check the output file:
Terminal window tail -n 1 output.logYou should see the parsed message as JSON:
Terminal window kernel: {"IN":"eth0","OUT":"","MAC":"11:22:33:44:55:66:aa:bb:cc:dd:ee:ff:08:00","SRC":"192.0.2.2","DST":"192.168.0.1","LEN":"40","TOS":"0x00","PREC":"0x00","TTL":"232","ID":"12345","PROTO":"TCP","SPT":"54321","DPT":"22","WINDOW":"1023","RES":"0x00","URGP":"0"}The word
SYNisn’t in the output, because it isn’t a key=value pair.
Step 4: Rename a field
Finally, you rename the SRC field to src_ip.
-
Add the following lines to the FilterX block, before the
${MESSAGE} = iptables;line:Terminal window iptables.src_ip = iptables.SRC; unset(iptables.SRC);The first line copies the value of
SRCto a newsrc_ipfield. Theunsetfunction deletes theSRCfield.The FilterX block now looks like this:
Terminal window filterx { ${PROGRAM} == "kernel"; ${MESSAGE} =~ "PROTO="; iptables = parse_kv(${MESSAGE}, value_separator="=", pair_separator=" "); iptables.src_ip = iptables.SRC; unset(iptables.SRC); ${MESSAGE} = iptables; }; -
Reload the configuration, and append the two sample messages again, the same way as in Step 2 .
-
Check the output file:
Terminal window tail -n 1 output.logYou should see:
Terminal window kernel: {"IN":"eth0","OUT":"","MAC":"11:22:33:44:55:66:aa:bb:cc:dd:ee:ff:08:00","DST":"192.168.0.1","LEN":"40","TOS":"0x00","PREC":"0x00","TTL":"232","ID":"12345","PROTO":"TCP","SPT":"54321","DPT":"22","WINDOW":"1023","RES":"0x00","URGP":"0","src_ip":"192.0.2.2"}The
SRCfield is gone, and thesrc_ipfield is at the end of the JSON object.
What you built
You now have a FilterX block that selects iptables messages, parses them into fields, and renames a field. AxoSyslog sends the result to a file as JSON. Your complete filterx-tutorial.conf file looks like this:
@version: current
source s_tutorial {
file("/tutorial/input.log" follow-freq(1));
};
destination d_tutorial {
file("/tutorial/output.log" template("${PROGRAM}: ${MESSAGE}\n"));
};
log {
source(s_tutorial);
filterx {
${PROGRAM} == "kernel";
${MESSAGE} =~ "PROTO=";
iptables = parse_kv(${MESSAGE}, value_separator="=", pair_separator=" ");
iptables.src_ip = iptables.SRC;
unset(iptables.SRC);
${MESSAGE} = iptables;
};
destination(d_tutorial);
};Clean up
To stop and remove the container, run:
docker rm -f filterx-tutorial