Limit your SSH logins using GeoLite2 "country"

One annoyance I really have are the constant login brute-force attempts, mainly from countries like China. Rather than loading in entire lists of network blocks into iptables and using up precious memory for (in my case) New Zealand, I wanted to simply check the origin of the connecting IP and deny those connections that do not originate from a pre-approved list of countries.

Installing the free GeoLite2 Country database

Update January 2019: Maxmind have officially dropped all support for the widely used geoiplookup binary and GeoIP Country (v1) database that it used.

The current v2 format (GeoLite2-Country.mmdb) doesn’t have a native drop-in client, so I wrote my own goiplookup which uses the latest database format and can be used successfully with this script without any modifications. It also supports database updating (eg: via a monthly cron).

Ensure geoiplookup is working

Make sure that geoiplookup is working before implementing the script below.

geoiplookup 8.8.8.8
GeoIP Country Edition: US, United States

The script

I wrote a simple shell script which returns either an true (ACCEPT) or false (DENY) response, and if it’s a DENY it logs to the system messages (using logger). Save this file in /usr/local/bin/sshfilter.sh:

#!/bin/bash

# UPPERCASE space-separated country codes to ACCEPT
ALLOW_COUNTRIES="NZ AU"

if [ $# -ne 1 ]; then
  echo "Usage:  `basename $0` <ip>" 1>&2
  exit 0 # return true in case of config issue
fi

COUNTRY=`/usr/local/bin/geoiplookup $1 | awk -F ": " '{ print $2 }' | awk -F "," '{ print $1 }' | head -n 1`

[[ $COUNTRY = "IP Address not found" || $ALLOW_COUNTRIES =~ $COUNTRY ]] && RESPONSE="ALLOW" || RESPONSE="DENY"

if [ $RESPONSE = "ALLOW" ]
then
  exit 0
else
  logger "$RESPONSE sshd connection from $1 ($COUNTRY)"
  exit 1
fi

Edit the ALLOW_COUNTRIES to include a list of country codes you want your SSH server to accept connections from.

Note: if an IP address cannot be matched to a country (such as an internal IP address), the connection is accepted too (see the $COUNTRY = "IP Address not found").

Don’t forget to make the script executable:

chmod 775 /usr/local/bin/sshfilter.sh

Lock down SSH

The last things we need to do is tell the ssh daemon (sshd) to deny all connections (by default) and to run this script to determine whether the connection should be allowed or not.

In /etc/hosts.deny add the line:

sshd: ALL

and in /etc/hosts.allow add the line

sshd: ALL: aclexec /usr/local/bin/sshfilter.sh %a

Testing

Obviously you want to test that this is working before you are accidentally logged out of your system. On the console I can do a test with the 8.8.8.8 which I happen to know is from the US, and in my case should be DENIED access. So:

/usr/local/bin/sshfilter.sh 8.8.8.8

The script will not return anything visible, however in /var/log/syslog I have the result:

Jun 26 17:02:37 pi root: DENY sshd connection from 8.8.8.8 (US)

Do test a working connection too (via normal SSH), although I don’t expect there to be any problems.

After a while you should find results in your /var/log/syslog along the lines of:

Jan 17 19:28:55 myserver logger: DENY sshd connection from 36.156.24.96 (CN)
Jan 17 19:29:06 myserver logger: DENY sshd connection from 223.111.139.244 (CN)
Jan 17 19:34:26 myserver logger: DENY sshd connection from 122.226.181.167 (CN)
Jan 17 19:34:30 myserver logger: DENY sshd connection from 122.226.181.167 (CN)
Jan 17 19:35:13 myserver logger: DENY sshd connection from 42.2.59.190 (HK)

From the connecting side of a non-allowed country, the ssh connection is simply cut off before it can even try log in:

ssh root@<ipaddress>
ssh_exchange_identification: Connection closed by remote host

Comments