logfilter

Zeek Package for Log Filter

Zeek master Test Status Documentation Status Coverage Status Template Status

Enables plugins to write fine-grained policy for log filtering, modification, and path customization.

Quick Start

If you already have Zeek and zkg installed, simply run:

zkg install https://github.com/esnet-security/logfilter

If this is being installed on a cluster, install the package on the manager, then deploy it via:

zeekctl deploy

Updating and Unloading

We use SemVer for versioning. For the versions available, see the tags on this repository. You can pass an additional argument to the install command with the desired version.

To upgrade to the latest version run:

zkg upgrade logfilter

You can modify the above command by replacing upgrade with:

  • unload, to configure Zeek to not load the package on startup.
  • load, to configure Zeek to load the package on startup (default after an install).
  • remove, to delete the package from the system.

If you're operating in a cluster, after performing any of the above changes, you'll need to re-run zeekctl deploy.

Installation

Zeek v3.3 Test Status Zeek v3.2 Test Status Zeek v3.1 Test Status Zeek v3.0 Test Status

This is a package designed to run with the Zeek Network Security Monitor. First, get Zeek. We strive to support both the current feature and LTS releases.

The recommended installation method is via the Zeek package manager, zkg. Follow the Quickstart guide.

To have Zeek load packages managed by zkg, ensure that @load packages is being loaded by Zeek.

Writing Filters

  1. Filtering Let's say that we only want our ssh.log file to have connections where the responder's port is 22. .. code-block:: zeek @ifdef ( SSH::Info ) hook pred_hook(stream: Log::ID, filter_name: string, rec: any) { if ( stream != SSH::LOG || filter_name != "default" ) return; local r = rec as SSH::Info; if ( r$id$resp_p != 22/tcp ) break; } @endif To write a filter, we handle the pred_hook hook. Whenever a script calls Log::write, the hook fires. If any hook handler execution results in a break, the log message is not written. For more information about hooks, see: https://docs.zeek.org/en/current/script-reference/types.html#type-hook Because pred_hook is used for all log files, we need to take care to make sure we're handling the right log messages. Wrapping the filter in an @ifdef directive will prevent syntax errors if the base SSH scripts aren't loaded, for some reason. Using @ifdef is preferred, since if we were to load the scripts ourselves, we'd remove the user's ability to not load those scripts. Line 4 checks that the message is from the SSH log, and the default filter. The logging framework supports additional filters (see: https://docs.zeek.org/en/current/frameworks/logging.html ), this check just ensures that we're only modifying the behavior of the default ssh.log file. Line 7 converts our info record from any type to an SSH::Info record, which allows us to access the fields therein. Finally, we check the responder's port, and break out of the hook if it's not TCP 22. The break causes the message to not be logged.
  2. Redirecting Next, let's say that instead of simply filtering what gets logged, we want to log messages to two different logs: ssh.log and ssh_nonstandard_port.log. First, we create a new log stream: .. code-block:: zeek module LogFilter; @ifdef ( SSH::Info ) export { redef enum Log::ID += { SshNonStdPort_LOG }; } event LogFilter::initialized() { Log::create_stream(SshNonStdPort_LOG, [$columns=SSH::Info, $path="ssh_nonstandard_port"]); } @endif Much of this should look familiar from the Zeek logging framework documentation (https://docs.zeek.org/en/current/frameworks/logging.html#add-a-new-log-file ). The difference is that we create the log in the LogFilter::initialized event, rather than in zeek_init. Once the Log Filter has activated and attached itself to all of the logs, this event fires. This provides an easy way to add a new log, without worrying about the log filter attaching itself multiple times, etc. Our hook handler also looks familiar: .. code-block:: zeek @ifdef ( SSH::Info ) hook pred_hook(stream: Log::ID, filter_name: string, rec: any) { if ( stream != SSH::LOG || filter_name != "default" ) return; local r = rec as SSH::Info; if ( r$id$resp_p != 22/tcp ) { Log::write(SshNonStdPort_LOG, r); break; } } @endif The only difference is in line 11, where we write the line to our new log file before breaking out of the hook.
  3. Copying If in our previous example we omitted the break in line 12, all log lines would be written to ssh.log, and only the ones where the server wasn't running on port 22 would be written to our new log.
  4. Modifying Log lines can even be modified before they're written to the log files. Let's say that instead of version 2, one of our servers actually runs SSH version 9000, and we want to set the field appropriately: .. code-block:: zeek @ifdef ( SSH::Info ) hook pred_hook(stream: Log::ID, filter_name: string, rec: any) { if ( stream != SSH::LOG || filter_name != "default" ) return; local r = rec as SSH::Info; if ( r$id$resp_h == 192.168.1.22 ) r$version = 9000; } @endif Note that when modifying log messages like this, we're only modifying them at the very last second before they get written out. Any policy scripts have already inspected this record.

Contributing

Contributions are welcome! The easiest way to give back is to comment on issues that are important to you -- even a quick reaction (thumbs-up/heart/thumbs-down) would help us prioritize issues.

There's a more in-depth contribution guide which lays out some ways that anyone can help.

Package Template

This package was created with a template, using Cruft. A CI job checks for updates to the template. To update the package, simply run:

pip install -U cruft
cruft update

License

This project is licensed under the BSD license. See the LICENSE file for details.

Package Version :