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:
Change the "
user
" value if your router's full-capability admin user is called something other than "admin".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.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 userbob
instead of asroot
, 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.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.