MikroTik Solutions

Using fail2ban with Remote syslog
Login

Motivation

The popular fail2ban tool monitors log messages for suspicious activity, then issues firewall commands to temporarily ban those hosts. After a configurable period of time, it automatically un-bans them.

RouterOS has logs and a suitable command interface to its firewall, but it's impractical to run fail2ban directly on the router. This guide shows what I believe to be a superior method: set up a machine on the network capable of running fail2ban in a way that lets it monitor the logs on the router's behalf and manage its firewall from afar.

In this article, we're going to set fail2ban up to react to failed SSH connections to the router. You can use this mechanism to react to anything RouterOS can produce a sensible log message about, so long as you can produce a regex that will match the necessary elements of the message. For instance, it would be easy to extend the examples below to make it ban IPs attempting to break in via WinBox, WebFig, or your VPN technology of choice.

Preliminaries

I developed and tested this with a macOS server running Homebrew, so to keep things simple I will use its commands and paths. The basic idea will work on most any Linux or BSD box, but the installation commands and configuration file locations will differ. You're expected to be able to map these trivial details to match your local system's quirks. The ideas are the hard part, and they transfer nicely as long as you can hold up your end.

The examples use the TEST-NET-1 IP address scheme:

IP Usage
192.0.2.1 RouterOS box
192.0.2.99 Server running rsyslog and fail2ban

Adjust the IPs as necessary.

Set Up

1. Install & Start rsyslog

macOS doesn't have a syslog server built-in, but it's easy to add one. Follow those instructions to make your router send log messages to the host that will run fail2ban.

The default configuration file for the Homebrew rsyslog package contains:

# minimal config file for receiving logs over UDP port 10514
$ModLoad imudp
$UDPServerRun 10514
*.* /usr/local/var/log/rsyslog-remote.log

That will work perfectly well for us as-is. I show it only because we'll refer back to these configurable values below.

2. Set Up the Router

Now that the fail2ban host is receiving log messages from the router(s), you also need to add a firewall filter chain to each monitored router for use in step 3 below:

> /ip/firewall/filter
> add action=jump chain=input jump-target=fail2ban
> add action=return chain=fail2ban

We put this in the "input" chain because we're reacting to SSH connections to the router in this example.

Move the "jump" action to the position in the firewall's input chain where you want fail2ban ban actions to occur. The correct position depends on your local firewall configuration, which I can't predict. The purpose of this chain is to give fail2ban a named position for the ban actions to be inserted. Think of it like a bookmark: wherever you put it, that's where the automated ban/unban actions will occur. Without this, we'd have to hard-code some brittle rule in the ban action like "place-before=42".

3. Install, Configure & Start fail2ban

Say:

% brew install fail2ban

Before we can start it, we have to tell fail2ban what files to look at and what to do when it finds a suspicious log line.

The following paths are relative to your fail2ban configuration directory, which is /usr/local/etc/fail2ban under Homebrew, but may be elsewhere on other OSes, such as /etc/fail2ban.

The Filter

Put the following into filter.d/routeros-rsyslog-sshd.conf:

[Definition]

_router     = (<F-ROUTER>[a-zA-Z0-9.-]+</F-ROUTER>)
failregex   = ^\s?<_router> .* login failure for user .* from <HOST> via ssh

This matches rsyslog messages such as:

2022-04-12T02:51:22.573113-06:00 officeswitch.lan system,error,critical login failure for user admin from 192.0.2.88 via ssh

fail2ban automatically removes the timestamp at the beginning, but it doesn't remove the following space, which is why our failregex allows for a single optional whitespace character at the beginning of the pattern. Immediately following that, we match either an IPv4 address or an ASCII host name and store it in the F-ROUTER variable for later use. This part of the regex won't match IPv6 addresses, domain names with odd characters, etc.

The regex then skips over the message's tag list and finds the actual failure message, including the host that caused it.

It's possible to extend the failregex to multiple possible matches, but that's a topic for the fail2ban documentation.

The Action

Now we have to teach fail2ban how to apply the firewall rule changes to RouterOS. Put the following into actions.d/routeros.conf:

[Init]

# SSH credentials to use to log into the router
user   = admin
port   = 22
pubkey = /var/root/.ssh/id_routeros

# SSH connection command
ssh = /usr/bin/ssh
cmd = <ssh> -i <pubkey> -p <port> <user>@<F-ROUTER>

# What to do on ban.
action = drop
chain  = fail2ban

# Command-shortening aliases
iff     = /ip/firewall/filter
what    = src-address="<ip>" chain="<chain>"
addwhat = <what> dst-port="<port>" proto="tcp" action="<action>"


[Definition]

actionban   = <cmd> '<iff> add <addwhat> place-before=0'
actionunban = <cmd> '<iff> remove numbers=[find <what>]'

Notes:

  1. Change the "user" value if your router's full-capability admin user is called something other than "admin".

  2. Change the "port" value if you've got the RouterOS SSH service listening on a nonstandard port. I recommend doing so on externally-facing routers since it reduces script kiddie and bot noise in your logs considerably. Note that this value is used both for logging into the router to send the RouterOS ban/unban commands and also in the firewall filter rules themselves.

  3. The proper name and location of the public key to use for the login depends on how fail2ban-server runs. The scheme in the example is typical of the Homebrew macOS build, but on a Linux box, you'll likely need something like /root/.ssh/id_routeros instead.

    Remember that the OpenSSH client is very picky about which key files it will use: if you arrange for fail2ban to run as user bob instead of as root, it will refuse to use anything other than Bob's SSH keys. A nice side benefit of this necessity is that it lets you generate an SSH key specifically for your RouterOS boxes, rather than reuse your OS's default SSH key.

  4. You may wish to change the "action" to "reject" or "tarpit". See the RouterOS filter docs for details.

The "chain" value matches the filter chain we set up above. If you change one, change the other to match.

You shouldn't have to change anything else.

The Jail

Now we can tie the two elements above together. Put the following into the jail.local file at the top of the fail2ban configuration directory. It may or may not already exist. You know you're in the right location if there's a jail.conf file, which you do not modify.

[routeros-rsyslog-sshd]

action     = routeros
backend    = polling
enabled    = true
ignoreself = true
logpath    = /usr/local/var/log/rsyslog-remote.log

Change the logpath variable if your syslog server puts the log information somewhere else.

The ignoreself setting is critical. It causes fail2ban to ignore failures originating from the host running fail2ban-server. Without this, fail2ban could never unban its host IP, since that requires SSH access. Worse, if you'd disabled all other management interfaces on the router (e.g. WinBox) you could end up locking yourself out of the router entirely.

You may have configured your RouterOS firewall to accept all packets from the fail2ban host ahead of this chain to prevent this very sort of lockout problem, but if so, the ban is useless, so you might as well set ignoreself in that case as well. There's no value slowing the router down by giving it useless firewall filters.

Final Steps

With all of this in place, you can start it running:

% sudo brew services start fail2ban
% sudo fail2ban-client status

The ban/unban actions likely won't work to start with. Although you may have been managing your routers over SSH before now, you probably haven't been doing so as the local root user, as fail2ban does. You will likely have to issue a command like the following against each router you're monitoring:

% sudo ssh -i /var/root/.ssh/id_routeros admin@officeswitch.lan

Until you do that and say "yes" to the resulting question, the router won't be in the local root user's known-hosts file, so fail2ban won't be able to automatically log in and send RouterOS commands.

Do this step even if you don't think it's necessary: it tests that the SSH login from the local root account works as you think it should.

That accomplished, you can test that the ban/unban actions actually send the proper RouterOS commands:

% sudo fail2ban-client set routeros-rsyslog-sshd banip 1.2.3.4
% sudo fail2ban-client set routeros-rsyslog-sshd unbanip 1.2.3.4

After the first command, you should find a "drop" rule for IP 1.2.3.4 in the fail2ban chain of your router, which the the second should remove. If you don't see that happen, check fail2ban.log to see whether it's seeing the router's complaint in the log, and if so, where it's getting hung up trying to apply the ban.

You may be wondering why we're doing all of this through sudo even though that's normally frowned-on in the Homebrew world. The reason is that we want fail2ban to run on system startup, not as a user service. The package will let you run the commands without sudo, but you'll end up with a fail2ban-server process running as your normal user, which won't start until you log into your Mac, and it might not have permission to do what it needs to. Worse, you may end up with a conflict where you have two fail2ban servers, one running as root, the other as your normal user, creating a conflict.

Integrating with SELinux

Although I said above I'm focusing on the macOS + Homebrew case, I tested the instructions on a CentOS box as well. For the most part, the translation is trivial, but I did run into a serious problem with getting fail2ban to run SSH commands, since modern Red Hat type systems put a lot of security restrictions on background services. For entirely sensible reasons, the default configuration of the stock fail2ban package won't allow it to launch /usr/bin/ssh, and if you allow that, you then run into problems with it opening the SSH key files in the root user's home directory.

The following SELinux module will grant the necessary permissions:

module f2b-ros-ssh 1.0;

require {
    type fail2ban_t;
    type ssh_exec_t;
    type ssh_port_t;
    type ssh_home_t;
    type admin_home_t;
    class file { execute execute_no_trans getattr map open read };
    class dir { getattr search };
    class tcp_socket name_connect;
}

allow fail2ban_t admin_home_t:file read;
allow fail2ban_t ssh_exec_t:file { execute execute_no_trans getattr map open read };
allow fail2ban_t ssh_home_t:dir search;
allow fail2ban_t ssh_home_t:dir getattr;
allow fail2ban_t ssh_home_t:file open;
allow fail2ban_t ssh_home_t:file { getattr read };
allow fail2ban_t ssh_port_t:tcp_socket name_connect;

Place that into a file called f2b-ros-ssh.te somewhere, then run these commands to compile and load it:

$ checkmodule -Mmo f2b-ros-ssh.mod f2b-ros-ssh.te
$ semodule_package -o f2b-ros-ssh.pp -m f2b-ros-ssh.mod
$ sudo semodule -i f2b-ros-ssh.pp

That having been done, you should no longer be getting action execution errors in /var/log/fail2ban.log.

Can I Run This on Windows?

While I see no reason it would be impossible to run rsyslog on Windows, there are many alternatives that will work just as well for this. You just need one that logs to a plain text file. (As opposed to the Windows event log, a database, etc.)

Getting fail2ban running on Windows is trickier.

Superficially, the easiest method on modern versions of Windows is via WSL, since the most common Linux distro fror WSL is Ubuntu, and it has a fail2ban package.

The main problem with this is that even with the vast improvement that WSL2 is over WSL1, it's still primarily an interactive user environment. Getting background services to run under WSL remains a massive PITA. This method looks like the simplest way to solve the problem, but this one might be more robust.

I suspect it's easier to use Cygwin for this purpose instead since it has the cygrunsrv facility to run background services via the normal Windows mechanism. The main problem with this option is that there are no ready-made Cygwin packages for fail2ban, so you'd have to install it from source. Since fail2ban is based on Python, this shouldn't be especially difficult.

Setting up a proper Linux distro under Hyper-V might be easier in the end.

Or, press a Raspberry Pi into service as a remote logging host.

License

This work is © 2022-2024 by Warren Young and is licensed under CC BY-NC-SA 4.0