IP address filtering in Logstash: To cidr, or not to cidr?

Written by - 0 comments

Published on - Listed in Logstash ELK Linux Network


Logstash's filter capabilities are very helpful to sort incoming logs based on rules and patterns, rewrite the logs, add or remove fields, change metadata or simply specify a different output based on a filter.

A filter can also be applied on IP addresses. From a previous article (Quick and easy log listener with Logstash and local file output) we could see, that even for the most basic log events, a client (or source) ip address is logged and saved in the [host] field:

root@logstash:~# cat /var/log/applications.log
{
      "@version" => "1",
    "@timestamp" => 2022-01-24T14:41:29.540Z,
       "message" => "Sending log event to Logstash\n",
          "host" => "192.168.253.110"

We can use this (remote) [host] field to separate incoming logs. Let's assume that this Logstash receives logs from many applications, spread across many networks. If we know the (static) IP addresses of certain application servers, we also know the application. Another possibility is to use network segmentation to separate TEST and PRODUCTION environments.

Using classic if conditions

The first obvious solution is to create a filter which uses an if condition for the IP address lookup, found in the [host] field. Here's a practical example how to determine the application environment based on the remote host ip address:

root@logstash:~# cat /etc/logstash/conf.d/10-filter.conf

filter {

  if [host] == "192.168.253.111" or [host] == "192.168.253.112" or [host] == "10.150.79.139" or [host] == "10.150.79.140" or [host] == "10.150.79.143" or [host] == "10.150.79.144" {
    mutate { add_field => { "logtarget" => "test" } }
  } else if [host] == "192.168.253.115" or [host] == "192.168.253.116" or [host] == "10.150.79.11" or [host] == "10.150.79.12" or [host] == "10.150.79.13" or [host] == "10.150.79.14" or [host] == "10.150.79.15" or [host] == "10.150.79.16" or [host] == "10.150.79.17" or [host] == "10.150.79.18" {
    mutate { add_field => { "logtarget" => "prod" } }
  } else {
    mutate { add_field => { "logtarget" => "generic" } }
  }

}

If the log event was sent by a (remote) host matching one of the IP addresses of the first if condition, the field [logtarget] with value "test" is added. The second/else if condition does the same for a production environment. If no condition matched the IP address, a "generic" value is added.

The Logstash output {} can now be configured to use the [logtarget] field in the file name:

root@logstash:~# cat /etc/logstash/conf.d/99-output.conf
output {

  file {
    path => "/log/app-%{logtarget}-%{+YYYY-MM-DD-HH}.log"
    file_mode => 0644
    codec => plain
  }

}

Note: The variable %{logtarget} can also be used in an Elasticsearch output, for example to write into a specific index.

Depending on where the logs came from, the log file will be one of:

  • /log/app-test-2022-02-01-11.log
  • /log/app-prod-2022-02-01-11.log
  • /log/app-generic-2022-02-01-11.log

This way (if conditions) of handling IP addresses works fine for a couple of static IP addresses. But you can see from the above condition: The more IP addresses need to be handled, the larger the condition becomes. That's not only annoying to manage, Logstash also needs more time to parse the condition (for each incoming event!).

Using if conditions with regular expressions

If all the application hosts are known and use static IP addresses, the if conditions can also be shortened by creating a regular expression (regex) around the IP addresses:

root@logstash:~# cat /etc/logstash/conf.d/10-filter.conf

filter {

  if [host] =~ /^192\.168\.253\.(111|112)/ or [host] =~ /^10.150.79.(139|140|143|144)/ {
    mutate { add_field => { "logtarget" => "test" } }
  } else if [host] =~ /^192\.168\.253\.(115|116)/ or [host] =~ /^10.150.79.1([1-9])/ {
    mutate { add_field => { "logtarget" => "prod" } }
  } else {
    mutate { add_field => { "logtarget" => "generic" } }
  }

}

This is basically the same filter as before, just shorter and (at least in my opinion) more manageable.

But there's still one problem with this filter: We must know the IP addresses of the application. If the application scales up, additional hosts may need to be added. The chances that new hosts are forgotten to be added into the filter are pretty high!

Using CIDR filter

There's one more filter plugin, which can help us out here: The CIDR filters plugin. The documentation of the plugin lacks a lot of information and examples and is at first hard to understand. Basically what you need to understand is the following:

  • The "address" option is the most important input. The value of this option tells the filter plugin which IP address to compare against the "network" option. As the CIDR filter doesn't make sense without the "address" option, it should actually be marked as a mandatory option.
  • The "network" option is an array that supports multiple networks in CIDR format (e.g. 192.168.10.0/24). The value from "address" is compared against the networks listed in "network".
  • Instead of listing networks in the "network" option, an external file can also be used as "list of networks". The local path to this external file can be specified using the "network_path" option.
  • If the "address" is a match against one of the CIDR networks listed in "network" (or "network_path"), additional actions inside the cidr {} filter are applied (such as add_field or remove_field).

"Translating" the above filters to use the cidr filter, results in the following new filter:

root@logstash:~# cat /etc/logstash/conf.d/10-filter.conf

filter {

  # Test networks
  cidr {
    address => [ "%{host}" ]
    network => [ "192.168.253.111/32", "192.168.253.112/32", "10.150.79.128/25" ]
    add_field => { "logtarget" => "test" }
  }


  # Production networks
  cidr {
    address => [ "%{host}" ]
    network => [ "192.168.253.115/32", "192.168.253.116/32", "10.150.79.0/25" ]
    add_field => { "logtarget" => "prod" }
  }

}

In this example, the [host] field is used as variable and used as value for the "address" option. "address" is then compared against different networks, defined in "network". Note that there are no if conditions in this situation; the comparison happens within the cidr filter and actions (add_field) are only applied if the comparison is a match (true).

Although each environment has two static host IP addresses defined (using the /32 suffix), the big advantage here is the separation of segmented networks. By checking the "address" against the TEST (10.150.79.128/25) and PROD (10.150.79.0/25) network ranges, we don't need to know the exact source IPs where the application runs. Application scaling (within the environment) is perfectly supported and the filter does not need to be adjusted.

Note: In older Logstash versions (e.g. 6.8) the cidr filter is not yet part of the official Logstash plugins and needs to be installed manually (using /usr/share/logstash/bin/logstash-plugin install logstash-filter-cidr). In more recent versions (tested with 7.16.x), the cidr filter is part of the default filters.


Add a comment

Show form to leave a comment

Comments (newest first)

No comments yet.

RSS feed

Blog Tags:

  AWS   Android   Ansible   Apache   Apple   Atlassian   BSD   Backup   Bash   Bluecoat   CMS   Chef   Cloud   Coding   Consul   Containers   CouchDB   DB   DNS   Database   Databases   Docker   ELK   Elasticsearch   Filebeat   FreeBSD   Galera   Git   GlusterFS   Grafana   Graphics   HAProxy   HTML   Hacks   Hardware   Icinga   Influx   Internet   Java   KVM   Kibana   Kodi   Kubernetes   LVM   LXC   Linux   Logstash   Mac   Macintosh   Mail   MariaDB   Minio   MongoDB   Monitoring   Multimedia   MySQL   NFS   Nagios   Network   Nginx   OSSEC   OTRS   Office   PGSQL   PHP   Perl   Personal   PostgreSQL   Postgres   PowerDNS   Proxmox   Proxy   Python   Rancher   Rant   Redis   Roundcube   SSL   Samba   Seafile   Security   Shell   SmartOS   Solaris   Surveillance   Systemd   TLS   Tomcat   Ubuntu   Unix   VMWare   VMware   Varnish   Virtualization   Windows   Wireless   Wordpress   Wyse   ZFS   Zoneminder