Skip to content

Configuring Ellis

François KUBLER edited this page Jun 21, 2018 · 5 revisions

Ellis configuration resides in a single file. This file is divided in several sections. Each section contains the configuration of a Rule.

The file is written using the ini format.

Specifying the configuration file location

You can specify the path to Ellis configuration file by calling Ellis with the -c (or --config) option:

$ ellis --config /path/to/ellis.conf

If you don't specify a path, Ellis will try to read its configuration from the following default locations:

  • /etc/ellis.conf
  • /etc/ellis/ellis.conf
  • ./ellis.conf

If none of the above exist, Ellis won't be able to start.

If more than one of these default locations exist, the configuration is merged, which can lead to one (or more) section(s) being overriden.

The last location (./ellis.conf) takes precedence over the second one, which takes precedence over the first one.

Understanding Rules

A Rule is what defines Ellis behavior: it tells Ellis what to watch, what to do and when to do it.

As such, Rules are the most important part of Ellis. It's very important to understand them, and to have them well-written.

A Rule is made of 5 things:

Name

An arbitrary name you can chose to identify the Rule.

For example: SSHBan.

Filter

One (or more) regular expression(s) that will be tested against log entries. If you need to provide more than one regex, put each one on its own line.

For example: ^Invalid user .* from (?P<ip><IP>) port .*$

Action

A Python function that will be run when the Rule limit is reached.

For example: ipset.ban(timeout=600)

Systemd-unit

The name of the systemd-unit to watch. This allows Ellis to monitor only the needed systemd-units. If you don't provide a systemd-unit for all your Rules, Ellis will watch every journald entry, which can lead to poorer performances. If the name of the systemd-unit you provide doesn't end with .service, Ellis will try to append it.

For example: sshd.service

Limit

Number of matches that Ellis has to reach to trigger the action.

For example, if you set the limit to 3, the action will be triggered only once Ellis got 3 matches.

Rule format

The format is pretty simple and follows the above definitions:

[<name>]
systemd-unit = <systemd-unit>
filter = <filter>
action = <action>
limit = <limit>

Following the previously given examples, an example of a valid Rule definition would be:

[SSHBan]
systemd-unit = sshd.service
filter = ^Invalid user .* from (?P<ip><IP>) port .*$
action = ipset.ban(timeout=600)
limit = 3

To be comprehensive, let's precise that a Rule:

  • MUST have a name, a filter and an action ;
  • SHOULD have a systemd-unit (for better performance) ;
  • CAN have a limit (defaults to 1 if not provided).

This means that this is also a valid Rule definition (not advised though):

[SSHBan]
filter = ^Invalid user .* from (?P<ip><IP>) port .*$
action = ipset.ban

Writing Rules

We plan to ship a bunch of pre-made (and well tested) Rules in the near future so users would only have to run the following to get a good starting point:

$ cat ellis_rules/SSHBan.rule >> /path/to/ellis.conf
$ cat ellis_rules/SASL.rule >> /path/to/ellis.conf

If you are still interested in writing your own Rules, please follow these 5 steps:

1. Chosing a name for your Rule

Chosing the good name for your Rule is really up to you. Yet, try to chose something revelant and meaningful. Also be sure not to chose a name already taken by another Rule. Ellis doesn't check that. It will overwrite the previous Rule, which can lead to bad surprises !

2. Identifying the systemd-unit

Then you will need to identify the systemd-unit you want Ellis to monitor. If you don't really know your system setup, the following command will list all active units. This should help you:

$ systemctl list-units --type=service

It really recommended to specify the systemd-unit parameter for your Rule. If you don't, Ellis will check all journald entries against all filters of all rules, resulting in poor performances.

3. Writing Filters

Writing filters is the most difficult part when it comes to configuring Ellis. It requires a good ability to write valid regular expressions.

Here are a few recommendations:

  • Make sure the regex starts with ^ and ends with $ ;
  • Ensure it is as restrictive as possible (for example, don't put .* where \d+ is sufficient) ;
  • Try to keep it as readable as possible ;
  • Use tags (see below) ;
  • Make sure the regex you are writing is not vulnerable to DoS attacks, especially through text injection ;
  • You should be able to use any Python regex feature (backreferences, ...) you find useful ;
  • If you need to catch a specific portion of the log entry for later use, use named groups (see below).

About tags

Tags are small regular expressions made available to ease the creation of filters. For now, Ellis supports two tags:

<IP>
Will match any IPv4 or IPv6 address. The validity of the caught IP address is not checked. It's up to the Action to check this validity if needed.
<PORT>
Will match any port number between [1..65534].

For example: ^Invalid user .* from <IP> port <PORT>.*$

About named groups

Named groups are a feature of Python regex that allows us to catch a small portion of the given string and reference it with a name.

For example, consider the following filter:

filter = ^Invalid user .* from (?P<ip><IP>) port .*$

We use a named group (?P<ip>) with a tag (<IP>). This allows us to catch an IP address and store it in a variable called ip. Ellis will also make sure the Action can access this variable by passing it as a named argument.

Note that if your Rule has a Limit bigger than 1, you MUST have at least one named group in your regex. This named group is then used by Ellis as a reference to count matches.

4. Chosing an Action

As we've seen before, Actions are Python functions that Ellis are run when the Limit has been reached. Ellis automatically looks in the ellis_actions package to see if the function is available. If you want to write your own Actions, please remember to put them there.

Currently, there is no way to get a list of available Actions. That might be added in the future.

To specify an Action, you MUST follow the pattern module.function so that Ellis knows where to look.

For example, if you want to call the ban function of the ipset module, you'd write:

action = ipset.ban

Passing arguments to the Action

Sometimes you may want to call your Action with a specific parameter. For example, if you use the ipset.ban Action, you may want to specify a timeout after which the banned IP address will be released.

Ellis allows you to do that. Simply follow the pattern module.function(arg1='value1', arg2='value2') when you specify the Action. arg1 and arg2 will then be passed to the Action.

Of course, you have to make sure the Action knows what to do with these arguments.

For example, the ipset.ban Action accepts two arguments : ip and timeout. The ip argument is logically gathered from the log entry. On the other side, the timeout argument can't be gathered from the log entry. You have to either specify it or use the default value. If you want the IP address to be banned for say 10 minutes, you'd write:

action = ipset.ban(timeout=600)

Also make sure to respect the function signature and name the argument(s) accordingly. For example, as we've seen it before, the ipset.ban Action accepts two arguments : ip and timeout. It's useless to call it with an argument called whatever. Worse: this will cause an error and the Action won't be run at all.

Note that an Action that accepts **kwargs as argument will obviously accept all given arguments. This is the case, for example, of the dummy.wait Action. This might be useful for devs that want to debug their Action function.

5. Setting a Limit

This step is optional.

TL;DR : Setting a Limit allows you to define a fault tolerance.

The Limit MUST be a number (an integer) strictly greater than zero. Ellis will only run the Rule Action once this Limit is reached. By default, the Limit is set to 1, which means the Action will be triggered each time a log entry that matches the Rule Filter is detected.

When the Limit is greater than 1, a counter is created and incremented for each corresponding entry.

For example, let's say that you plan to ban the IP addresses of clients that fail to log in through SSH. You have the following Filter and Limit:

filter = ^Invalid user .* from (?P<ip><IP>) port <PORT>.*$
limit = 3

Notice the named group (?P<ip><IP>) ? This means that the counter for the Rule won't be incremented each time a match is found. Instead, Ellis will create and increment a counter for each IP address that has been detected. If the same IP address is detected three times, the Action is run : the IP address is blacklisted. This means someone with the same IP address can attempt to connect three times (and can fail only twice).

Clone this wiki locally