Allow SSH access based on GeoIP country

Written by - 5 comments

Published on November 3rd 2016 - last updated on June 28th 2019 - Listed in Linux Security


For a certain online infrastructure there's a SSH jump host in place so that admins, developers and also some external users involved in projects are allowed to connect to the servers.

Of course only key authentication works and we also limited the source ip addresses to the ranges of certain internet providers. But this turned out to be a constant bugger. Either suddenly some new ranges appeared or sometimes a developer has changed his internet provider and the firewall rule needed to be changed again.

Because I know that people involved in this environment only work from a few countries, why not use an access filter based on GeoIP?

Whenever a connection comes in to the SSH daemon, the IP address' origin should be determined by a GeoIP lookup. Depending on which countries are defined as "allowed", they should be allowed to connect.
And this is how it's done (source: https://www.axllent.org/docs/view/ssh-geoip/) on an Ubuntu server (in my case Ubuntu 14.04).

First install the necessary geoip packages:

# apt-get install geoip-database geoip-bin

Then create the GeoIP lookup script, which will be launched whenever a new SSH connection is opened. I created it as /usr/local/bin/ipfilter.sh:

#!/bin/bash
# License: WTFPL

# UPPERCASE space-separated country codes to ACCEPT
ALLOW_COUNTRIES="CH"
LOGDENY_FACILITY="authpriv.notice"

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

if [[ "`echo $1 | grep ':'`" != "" ]] ; then
  COUNTRY=`/usr/bin/geoiplookup6 "$1" | awk -F ": " '{ print $2 }' | awk -F "," '{ print $1 }' | head -n 1`
else
  COUNTRY=`/usr/bin/geoiplookup "$1" | awk -F ": " '{ print $2 }' | awk -F "," '{ print $1 }' | head -n 1`
fi
[[ $COUNTRY = "IP Address not found" || $ALLOW_COUNTRIES =~ $COUNTRY ]] && RESPONSE="ALLOW" || RESPONSE="DENY"

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

Make the script executable:

# chmod 755 /usr/local/bin/ipfilter.sh

In /etc/hosts.deny set all connections to sshd to DENY:

# cat /etc/hosts.deny | grep sshd
sshd: ALL

In /etc/hosts.allow define the GeoIP lookup script created before for all connections to sshd:

# cat /etc/hosts.allow | grep sshd
sshd: ALL: aclexec /usr/local/bin/ipfilter.sh %a

You probably already guessed it correctly. The variable %a represents the incoming IP address, which is then forwarded to the lookup script. The lookup script then makes the GeoIP lookup with the given IP address. If the country code is in the list of allowed countries (ALLOW_COUNTRIES) or is an unknown source the script will exit 0 (OK) and therefore the connection will be allowed. If the country isn't in the allowed list, the script will exit 1 (NOT OK) and that's the signal to the system to drop the connection.

Let's try to make an ssh connection from a host in Germany:

$ ssh claudio@jumphost.example
ssh_exchange_identification: Connection closed by remote host

On the jump host itself the following log entries appear:

Nov  3 12:17:14 jumphost logger: DENY sshd connection from 144.76.85.24 (DE)
Nov  3 12:17:14 jumphost sshd[11345]: aclexec returned 1
Nov  3 12:17:14 jumphost sshd[11345]: refused connect from 144.76.85.24 (144.76.85.24)

Note: If you're using a RHEL based distribution (for example CentOS), the command "aclexec" doesn't seem to work (see http://tecadmin.net/allow-server-access-based-on-country).
In this case use "spawn" instead of "aclexec" in hosts.allow:

root@rhel # cat /etc/hosts.allow | grep sshd
sshd: ALL: spawn /usr/local/bin/ipfilter.sh %a

On the other hand spawn didn't work on my Ubuntu jump host - here only aclexec really blocked the access.

Of course this is not to be considered as a high security setting. The source IP address can be spoofed to whatever you like. So make sure you have your ssh daemon up to date and harden your settings, for example the typical ones:

  • PasswordAuthentication no
  • PermitRootLogin no
  • AllowUsers (list of allowed users)

Update June 28th 2019: This guide also works on Ubuntu 16.04 and 18.04 without additional configuration or steps.


Add a comment

Show form to leave a comment

Comments (newest first)

Elite cell parts from USA wrote on Jul 28th, 2019:

Thanks for tutorial.


Fnaf world from Canada wrote on Mar 29th, 2019:

Nice recommendation.


ps4 emulator apk from USA wrote on Dec 26th, 2018:

Nice recommendation and tutorial.


Bella from USA wrote on Jan 3rd, 2018:

Nice work dude.


David from USA wrote on Dec 2nd, 2017:

Great and nice.