SSHGuard logo directing long-exposure road traffic

How-to protect SSH remote login in Fedora with SSHGuard and FirewallD

SSHGuard 2 provides a new FirewallD backend that makes it easier to work with in Linux distributions that use FirewallD tools. Here is how you install and configure SSHGuard on such a distribution.

You can reduce the number of scripted attacks against SSH significantly by changing the default port number. Obscuring where the SSH service is accessed shouldn’t be your only line of defense, however. This is where SSHGuard comes in: it will add the IP addresses of repeated attackers to the system firewall to drop the connections before they even reach your SSH service.

Build and install

SSHGuard isn’t packaged for Fedora yet (I’ll see what I can do about that), so you’ll have to compile and install it yourself. Luckily, that isn’t a very complicated process so you should have SSHGuard up and running on your system in a few minutes by following this tutorial.

Start by installing the build dependencies using the below command. If you’re using Ubuntu or another distribution, you’ll find the appropriate apt command in the INSTALL file. You can follow along in this tutorial even if you don’t use Fedora, but choose the appropriate firewall backend for your distribution.

dnf install byacc flex gcc make wget

Next, you need to download and uncompressed the source from the latest release tarball from SourceForge. The example commands below downloads version 2.0, extracts it from the package, and changes the working directory to the source directory.

wget "https://sourceforge.net/projects/sshguard/files/sshguard/2.0.0/sshguard-2.0.0.tar.gz"
tar -xzf sshguard-2.0.0.tar.gz
cd sshguard-2.0.0/

Once you’ve got your very own copy of the source code from the release tarball, you can proceed to configure and build SSHGuard:

./configure --prefix="/usr/local/"
make

Unless you see error messages, you should be ready to install the final program as well as some example configuration resources. The following commands needs to be executed as root, and will copy the files you need to where they need to go:

make install
cp ./examples/sshguard.conf.sample /usr/local/etc/sshguard.conf
cp ./examples/sshguard.service /etc/systemd/system/

Configuring SSHGuard

The example configuration file has enough inline documentation to guide you through the required configuration options. You’ll only need to configure the BACKEND and LOGREADER options. For Fedora, we’ll work with FirewallD as the firewall backend and systemd journal as the log reader. Make the following changes in /usr/local/etc/sshguard.conf:

BACKEND="/usr/local/libexec/sshg-fw-firewalld"
LOGREADER="LANG=C /usr/bin/journalctl -afb -p info -n1 -t sshd -o cat"

All those journalctl switches tells the journal to pipe out all messages from the sshd process with priority info or higher with basic formatting (syslog-like).

You can look through the other configuration options in the file to see if you want to make any other changes. You may want to double the DETECTION_TIME option if you’re experiencing slow-paced brute-force attacks.

Next, you’ll need to make one small change in the systemd unit service file to account for the installation prefix we made during the build process. Change the ExecStart option in /etc/systemd/system/sshguard.service to the following:

ExecStart=/usr/local/sbin/sshguard

As you’re all done configuring the systemd unit file for SSHGuard, run the following command to let systemd know that there is a new unit file:

systemctl daemon-reload

Configuring FirewallD zone

The FirewallD backend in SSHGuard relies on the ipset feature of iptables. Blocked attackers are added to either the sshguard4 for IPv4 or sshguard6 for IPv6 ipsets. Additionally, SSHGuard will add a firewall rule to FirewallD that tells it to drop connections from any source IP found in either one of these ipsets.

By default, SSHGuard’s drop-rule will be added to the default FirewallD zone. Depending on your firewall zone configuration, you may want to add additional drop rules to other zones. If you only use the default public zone then no further configuration is needed. The following command would add the default rule to the home zone:

firewallctl zone "home" --permanent add rich-rule "rule source ipset=sshguard4 drop"
firewallctl zone "home" --permanent add rich-rule "rule source ipset=sshguard6 drop"
firewallctrl reload

You can inspect the blocked entries in each ipset using the following commands:

firewallctl info ipset --permanent "sshguard4"
firewallctl info ipset --permanent "sshguard6"

You can unblock a blocked attacker, in this example 10.10.1.10; using the following command:

firewallctl ipset "sshguard4" --permanent remove entry "10.10.1.10"

Note that SSHGuard’s two ipsets are deleted when SSHGuard flushes its own firewall rules (on exit). This may cause a problem if FirewallD rules are loaded when the ipsets don’t exist yet. To work around this, you can either add the creation of these two ipsets to your permanent FirewallD configuration or you may want to take more control of SSHGuard’s FirewallD backend script. You can copy and modify the sshg-fw-firewalld script (it’s an easy to follow bash script) and configuring the BACKEND option to use your custom firewall backend.

Starting the service

Now that SSHGuard is installed and configured, you can start the service and check up on it’s status with these commands:

systemctl start sshguard
systemctl status sshguard

Verify that it says active (running) in the output and that you don’t have any errors or warnings in the log output.

To have SSHGuard start at systemboot, run one last command:

systemctl enable sshguard

Test and verify

At this point SSHGuard should be installed, configured, and protecting your machine. But how can you be sure that it’s working? Run the following command to follow the systemd journal log entries from sshd and sshguard:

journalctl -ef -t sshguard -t sshd

From another machine (or at least IP), try to login to the protected system over SSH using invalid credentials. On the protected system you should see log entries from both SSHGuard and SSH about the unsuccessful login attempts. Continue the login attempts with invalid credentials until it stops working, and look in the log output. It should say that the IP was blocked and for how long. If you want for the block duration timeout to expire, and then continue to attempt more logins you should see that the block time gets doubled.

You should see login attempts to sshd and log messages from sshguard like “Attack from “client8.homenet.example.com” on service 100 with danger 10.” After five attacks sshguard should block the attacker with an error saying “Blocking “client8.homenet.example.com” for 600 secs (3 attacks in 75 secs, after 1 abuses over 75 secs.)

You can repeat the test and first use the ssh -4 switch and then repeat the test with the ssh -6 switch when attempting to pretend login to your protected system to verify that both IPv4 and IPv6 protections are working.

You can tweak the BLOCK_TIME and DETECTION_TIME duration options in sshguard.conf to your liking. Note that the BLOCK_TIME is doubled on every subsequent block per source IP.

Reboot the system and repeat the test.

If you’ve accidentally locked yourself out, you can find the commands for releasing yourself from the block in the FirewallD section above.