Following earlier articles on configuring GeoIP and using it with NGINX, this tutorial covers geoblocking on SSH. Geoblocking doesn't make a great defense on its own, but it's a great way to keep out thousands of automated scanners and low-level scripted attacks. Geoblocking is most effective on the perimeter and best implemented on a firewall. However, implementation directly on an SSH server may be helpful for certain situations where a network firewall with this capability isn't available.

Implementing geoblocks directly with firewalld, iptables or other software based firewalls is not recommended due to the performance impact caused by loading millions of IP ranges into the firewall. This tutorial uses fail2ban to dynamically block IP addresses from excluded countries, thereby avoiding the need to preload and update IP ranges directly in the firewall. It also provides a single source of management for geoblocks and standard fail2ban blocking.

Commands listed will work on RedHat/CentOS derivatives and may need to be adjusted for Ubuntu/Debian or other types Linux systems.

For information on configuring GeoIP services, see here.

The Tutorial

Start by installing and enabling fail2ban (this requires the EPEL repository).

sudo yum install fail2ban
sudo systemctl enable fail2ban

Configuring Jail

In fail2ban, jails refer to the set of filter that define suspicious activity and the actions to take when they are detected. Copy the default jail file to avoid overwriting changes during package upgrades.

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit jail.local and to include the lines listed for each section below. Bantime defines how long an IP will be blocked. It can be set to any reasonable amount (the default is 10 minutes), but 25 hours will prevent the next run of automated scanners operating on a daily schedule. Ignoreip is used to define IP addresses that will never get banned. Make sure to include local IP ranges and other important addresses, such as bastion hosts, to prevent accidental lockouts.

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 
bantime = 25h

[sshd]
enabled = true

/etc/fail2ban/jail.local

Restart the fail2ban service and use the fail2ban-client command to ensure it is working.

systemctl restart fail2ban
fail2ban-client status sshd

Get geoIPfilter.sh

The geoIPfilter.sh script checks the location of incoming connections and uses fail2ban to block the IP unless the connection is from an allowed country. By default, it will use the sshd jail as configured in /etc/fail2ban/jail.local for the ban.

Download the script from here and copy it to /usr/local/bin/geoIPfilter.sh. Make the script executable with chmod 755 /usr/local/bin/geoIPfilter.sh.

In the script, change allowed="US" to include an uppercase, space-delimited list of allowed countries. For a full list of iso3166 country codes, see https://www.geonames.org/countries/.

To avoid accidental lockouts during testing, set a low jail time (e.g. 30 seconds) and reload the configuration with fail2ban-client reload. Don't forget to set the jail time back to normal when finished.

To test the script, use the command line to pass an IP from a blocked country and check that is was successfully banned. Perform reverse-testing as well, to verify that an IP from an allowed country does not get banned.

/usr/local/bin/geoIPfilter.sh <IP>
fail2ban-client status sshd
fail2ban-client set sshd unbanip <IP>

Banning IP by Location

Add the following line to /etc/hosts.allow. This will pass the IP of every incoming SSH connection to geoIPfilter.sh.

sshd: ALL: spawn (/usr/local/bin/geoIPfilter.sh %a)

/etc/hosts.allow

Some Extra To Do

In some environments, it may be necessary to create a unique fail2ban jail instead of the standard sshd jail. This is particularly true if the sshd jail is integrated with AbuseIPDB - do not send every geoblocked IP to AbuseIPDB. A unique jail can also be useful if the ban list generated by geoIPfilter.sh gets too long and starts to impact performance. In this case, use a separate jail with shorter ban times.

To use a separate jail for geoblocking, follow fail2ban documentation to create a new jail and edit the jail= configuration in geoIPfilter.sh to reflect the changes.