Limit your SSH logins using GeoIP
in Networking, Security & Encryption, SystemI run a Raspberry PI which acts as a firewall, VPN and DNS server for my network. 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 if not found matching a certain list of countries.
Installing GeoIP country database
The first thing you need to do is to install geoiplookup and the GeoIP country (free) database. Most Linux distributions have these packages. On Debian you can simply:
$ apt-get install geoip-bin geoip-database
Make sure it works by doing a simple test:
$ 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/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: is 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/messages 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/messages along the lines of:
Jun 25 11:59:54 pi logger: DENY sshd connection from 82.221.102.185 (IS)
Jun 25 15:47:54 pi logger: DENY sshd connection from 220.227.123.122 (IN)
Jun 25 18:43:51 pi logger: DENY sshd connection from 221.229.166.252 (CN)
Jun 25 20:49:04 pi logger: DENY sshd connection from 221.208.245.226 (CN)
Jun 25 22:53:22 pi logger: DENY sshd connection from 122.224.247.188 (CN)
Jun 25 23:36:40 pi logger: DENY sshd connection from 61.160.207.146 (CN)
Jun 26 01:13:56 pi logger: DENY sshd connection from 82.221.102.185 (IS)
Jun 26 01:24:42 pi logger: DENY sshd connection from 1.93.34.228 (CN)
Jun 26 06:04:48 pi logger: DENY sshd connection from 118.98.96.81 (ID)
Jun 26 06:28:00 pi logger: DENY sshd connection from 61.47.35.61 (TH)
Jun 26 07:17:45 pi logger: DENY sshd connection from 118.98.96.81 (ID)
Jun 26 15:07:39 pi logger: DENY sshd connection from 117.79.91.220 (CN)
From the connecting side of a non-allowed country, the ssh connection is simply cut off before it can even be attempted:
$ ssh root@<ipaddress>
ssh_exchange_identification: Connection closed by remote host
Updating GeoIP
In order to make sure you are up-to-date with your GeoIP (free) country database, you will need to write another script which you can run as a monthly root cron job.
Please note that if you just installed the GeoIP database, or you have never manually updated it (it does not auto-update), then you should run the following script manually after installing it! The default database that gets installed is several years old and very inaccurate.
#!/bin/bash
cd /tmp
wget -q https://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
if [ -f GeoIP.dat.gz ]
then
gzip -d GeoIP.dat.gz
rm -f /usr/share/GeoIP/GeoIP.dat
mv -f GeoIP.dat /usr/share/GeoIP/GeoIP.dat
else
echo "The GeoIP library could not be downloaded and updated"
fi