Syslog-ng 101, part 9: Filters

This is the ninth part of my syslog-ng tutorial. Last time, we learned about macros and templates. Today, we learn about syslog-ng filters. At the end of the session, we will see a more complex filter and a template function.

You can watch the video or read the text below.

Declaring filters

Filters are expressions to select, or in other words, filter log messages. They make sure that the right messages reach the right destinations. For example, you can use filters to discard debug level log messages, or make sure that all authentication-related messages are routed to your SIEM system.

A filter definition is a collection of one or more filter functions. It consists of two parts. It starts with the word “filter”, followed by an identifier for the filter which you will use later to refer to the given filter. After that, it lists the filter functions with their parameters. You can combine multiple filter functions using boolean operators. Here is how its syntax looks like:

filter name { filterfunction(); };
filter f_default { level(info..emerg) and not (facility(mail)); };

Common filter functions

You can select or filter log messages using filter functions. Some of the more common filter functions are:

  • level: filters for the severity, or in other words the importance of the log message

  • facility: filters for the facility, or in other words the main category of the log message

  • host: filters based on hostnames

  • program: filters based on the application name

  • match: filters the message content using regular expressions

  • netmask: filters by sender IP or subnet

  • filter: uses a different filter

Example

This configuration will look familiar. It collects local log messages and filters them similarly to how the /var/log/messages file is often created:

@version:3.19
@include "scl.conf"
source s_sys { system(); internal();};
destination d_mesg { file("/var/log/messages"); };
filter f_default { level(info..emerg) and not (facility(mail)); };
log { source(s_sys); filter(f_default); destination(d_mesg);};

You should take a closer look at the filter line. There are two filter functions. The first one discards debug-level log messages. The second one selects mail-related log messages. The two functions are connected with “and not”, which means that both debug level and mail-related messages are filtered out. Debug level messages are usually discarded, unless really needed in a debug session. Mail-related messages are usually saved to a separate log file.

The inlist() filter

The inlist() filter filters log messages based on white- or blacklisting. It compares the content of a single field with a list of values. These values are read from text files, there is one value in each line.

There are several usecases for the inlist() filter. The original idea came at an info security conference: the inlist() filter can be the poor man’s SIEM. You can download various IP address databases which list spammers, malware command and control hosts, or similar databases. Using these lists, syslog-ng can alert in real-time when there is a match. Another possibility is filtering based on a list of application names.

If / else

Using if/else statements you can make conditional expressions in a log path. It makes using the results of filtering a lot easier. Instead of creating complex configurations using multiple log paths, you can use a simple syntax:

if (filter()) { do this }; else { do that };

You can use if statements to use different parsers on different logs. Another use is sending matching logs to a given destination and implementing real-time alerting using syslog-ng.

Example

In the following example configuration, we will see not just the if statement in action, but two more new configuration elements: template functions and an in-line destination. And a configuration element we already mentioned but we cannot see it on the screen: Application Adapters.

@version:3.21
@include "scl.conf"
source s_sys { system(); internal();};
destination d_mesg { file("/var/log/messages"); };
log { source(s_sys); destination(d_mesg); };
filter f_sudo {program("sudo")};
destination d_sudoall {
  file("/var/log/sudo.json" template("$(format-json --scope nv_pairs --scope dot_nv_pairs –scope rfc5424)\n\n"));
};
log {
  source(s_sys);
  filter(f_sudo);
  if (match("workshop" value(".sudo.SUBJECT"))) {
    destination { file("/var/log/sudo_filtered"); };
  };
  destination(d_sudoall);
};

The first five lines of the configuration are the same ones that we have already seen a couple of times before:

  • A version number declaration

  • Including the syslog-ng configuration library

  • A source for local and internal log messages

  • A destination

  • A log path that connects the source to the destination

When it comes to a more complex log statement, I prefer to follow the configuration by reading the log statement. So, let’s jump to the end of the configuration and check the log statement. It starts with a source and uses the very same source definition that we used in the first log statement. However, in this case, it is followed by a filter. It selects any log messages from the sudo application, using the program() filter.

The next three lines are a conditional expression or an if statement. It uses the match() filter, and checks if the content of the “.sudo.SUBJECT” name-value pair equals “workshop”. Where does this name-value pair come from? As mentioned earlier, when you use the syslog-ng configuration library and the system() source in your configuration, your log messages are parsed automatically by a number of parsers, called Application Adapters. One of those is the sudo log parser. This name-value pair contains the name of the user running commands through sudo.

The second line of the if statement looks familiar, but still there is something strange. Instead of referring to a destination, the destination itself is declared there in-line. Anything you declare this way cannot be used anywhere else in the configuration. In this configuration, any log message matching the filter in the if statement is saved to this file destination.

The last line of the log statement refers to a file destination in the way that we learned earlier: by its name. But when you take a closer look at the destination you will see something new: a template directly at the file destination (so, it cannot be used elsewhere) and a template function. It writes log messages in JSON format. Why is it necessary? The default file template only saves the syslog header and the log messages, which means that name-value pairs created from the log message are silently discarded. Template functions allow creating log messages from name-value pairs.

The format-json template function allows you to create JSON formatted log messages. Using --scope you can select which group of name-value pairs to include. These lines usually span many lines on screen, so the double line feed at the end makes the file easier to read for humans.

As sudo is installed on almost all Linux hosts, you can easily test this configuration just by rewriting the username in the if statement to something available on your system.

If you have any questions or comments, leave a comment on YouTube or reach out to me on Twitter / Mastodon.

-

If you have questions or comments related to syslog-ng, do not hesitate to contact us. You can reach us by email or even chat with us. For a list of possibilities, check our GitHub page under the “Community” section at https://github.com/syslog-ng/syslog-ng. On Twitter, I am available as @PCzanik, on Mastodon as @Pczanik@fosstodon.org.

Related Content