GeoIP database has records of Geographical location based on IP address. Using this database we can search for any IP belonging to which country using the Linux command line. This article will help you to allow SSH or FTP (vsftpd) access based on the user’s country. This example uses TCP wrappers to secure your services.

Advertisement

country based ssh access

Install GeoIP and GeoIP Database

First, install GeoIP binary for Linux and their database based on your operating system. For CentOS and RedHat users GeoIP binary and database are combined in a single package.

On CentOS and RedHat:
sudo yum install GeoIP 
On Ubuntu and Debian:
sudo apt-get install geoip-bin geoip-database 

Create the SSH/FTP Filter Script

Now create a shell script that checks for all incoming connection IP addresses and searches their corresponding country using the GeoIP database and allowed only those countries whose code is defined in ALLOW_COUNTRIES variable in the script.

vim /usr/local/bin/ipfilter.sh 
#!/bin/bash
# License: WTFPL

# UPPERCASE space-separated country codes to ACCEPT
ALLOW_COUNTRIES="IN US"
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

Script srouce: https://gist.github.com/jokey2k/a74f56955124880749e7

Make this script executable

chmod +x /usr/local/bin/ipfilter.sh 

Restrict SSH/FTP Connections

Now apply SSH and FTP restrictions using TCP wrappers. First we need to deny everyone by adding below line in /etc/hosts.deny.

/etc/hosts.deny:

sshd: ALL
vsftpd: ALL

Now edit /etc/hosts.allow and allow only those ips which are allowed by your IP filter script.

/etc/hosts.allow:

sshd: ALL: spawn /usr/local/bin/ipfilter.sh %a
vsftp: ALL: spawn /usr/local/bin/ipfilter.sh %a

Above FTP restrictions are for vsftpd only. Also, make sure you have enabled (tcp_wrappers=YES) in your vsftpd configuration. You can also create similar rules for any other services supported by a TCP wrapper.

Testing

Finally, test your server by login in using SSH or FTP from different-2 locations and analyze the access log files. Below are some demo logs created by ipfilter.sh.

Feb 27 13:03:29 TecAdmin root: DENY sshd connection from 212.191.246.202 (PL)
Feb 27 13:34:28 TecAdmin root: DENY sshd connection from 212.181.246.202 (SE)
Feb 27 13:34:36 TecAdmin root: DENY sshd connection from 211.181.246.203 (KR)
Feb 27 13:35:00 TecAdmin root: DENY sshd connection from 221.191.146.204 (JP)
Feb 27 15:11:04 TecAdmin root: ALLOW sshd connection from 49.15.212.12 (IN)
Feb 27 15:11:09 TecAdmin root: ALLOW sshd connection from 149.15.212.12 (US)
Feb 27 15:11:22 TecAdmin root: ALLOW sshd connection from 49.15.156.123 (IN)
Feb 27 15:11:32 TecAdmin root: ALLOW sshd connection from 231.15.156.123 (IP Address not found)
Feb 27 15:14:04 TecAdmin root: DENY sshd connection from 111.15.15.123 (CN)
Feb 27 15:14:56 TecAdmin root: ALLOW sshd connection from 49.15.110.123 (IN)

In logs, you can say that all ips belonging to the US (United States) and IN (India) are allowed. Also if any IP does not match in the GeoIP database will be allowed by default. The rest of the matching other countries’ ips are denied.

Share.

26 Comments

  1. Thank you for posting this.

    I tried the method you posted, but the tcp wrappers functionality is deprecated in Fedora 🙁

    I’m already using the (new) Maxmind GeoIP DB in shorewall (= iptables), but I’ve noticed it isn’t very exact, even though I’ve blocked China, there’s still a ton of requests coming in from over there :/

    So I ended up signing up for ipinfo.io, which allows you to do 50k API calls per month, for free.
    There are other services offering the same, but I found their API easy to work with – you just do
    a curl call.

    I used the script above as base, when I adapted it for ipinfo.

    I had to modify the shorewall rules file to log the SSH requests, so they could be cought by my script,
    geo looked up at ipinfo, and banned.

    rules, the notice param was added:
    ACCEPT:notice net fw tcp 22
    I’m sure it can be done in raw iptables too, but I don’t know how :/

    Here’s the script, it ain’t pretty, but it works 🙂

    #!/bin/bash
    # License: WTFPL
    #
    # UPPERCASE space-separated country codes to ACCEPT
    ALLOW_COUNTRIES=”ZZ”

    tail -f -n 0 /var/log/messages | grep –line-buffered -o ‘SRC=[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}’ | while read IPADDR
    do
    IPADDR=${IPADDR:4}

    DATE=`date +”%Y-%m-%d %H:%M:%S”`

    if [[ “`echo $IPADDR | grep ‘:’`” != “” ]] ; then
    COUNTRY=`/bin/curl -s http://ipinfo.io/$IPADDR/country?token=ipinfo.token`
    else
    COUNTRY=`/bin/curl -s http://ipinfo.io/$IPADDR/country?token=ipinfo.token`
    fi

    if ! echo $COUNTRY | grep -q -v ‘”error”‘; then
    COUNTRY=”error”
    fi

    [[ $COUNTRY = “error” || $ALLOW_COUNTRIES =~ $COUNTRY ]] && RESPONSE=”ALLOW” || RESPONSE=”DENY”

    if [[ “$RESPONSE” == “ALLOW” ]] ; then
    echo “$DATE $RESPONSE sshd connection from $IPADDR ($COUNTRY)”
    else
    echo “$DATE $RESPONSE sshd connection from $IPADDR ($COUNTRY)”
    shorewall drop $IPADDR
    fi
    done

    It might be a good idea to replace shorewall drop $IPADDR, with (if installed)
    fail2ban-client set sshd banip $IPADDR, since shorewall bans won’t survive a
    system reboot/restart of shorewall, while fail2ban will.

  2. I tried this on Debian, but it was allowing connections even when the script returned ‘DENY’ instead of ‘ALLOW’! The solution is to use ‘aclexec’ instead of ‘spawn’ in the /etc/hosts.allow file. That way, the return status of the script is taken into account.

  3. Hello,
    I have followed the instruction to install the script, geoip and all thestuff.
    I get in my logs the results but ssh access is still allowed for users having an Ip not allowed.

    Centos 6

    I get this in the logs:

    Jul 1 13:27:51 db Olivier: DENY sshd connection from 43.226.150.122 (CN)
    Jul 1 13:27:54 db sshd[27318]: Invalid user ajeet from 43.226.150.122
    Jul 1 13:27:54 db sshd[27329]: input_userauth_request: invalid user ajeet
    Jul 1 13:27:54 db sshd[27318]: pam_unix(sshd:auth): check pass; user unknown
    Jul 1 13:27:54 db sshd[27318]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=43.226.150.122
    Jul 1 13:27:54 db sshd[27318]: pam_succeed_if(sshd:auth): error retrieving information about user ajeet
    Jul 1 13:27:56 db sshd[27318]: Failed password for invalid user ajeet from 43.226.150.122 port 35876 ssh2

  4. This works perfectly in CentOS / RHEL 6.x & 7.x but in CentOS 8 this method will not work because
    the TCP Wrapper been removed.

    Any solution to work on RHEL / CentOS 8.x

  5. Thanks for the script but please put
    ALLOW_COUNTRIES=”IN US”
    as
    ALLOW_COUNTRIES=”EU US”

    something other than India just because I was confused by the word “IN”. Methought it was like the word “INput” .
    thanks.

  6. Hi to every body, it’s my first pay a visit of this webpage; this web site contains
    amazing and truly fine information designed for visitors.

  7. I’m close to having a centos solution by using FUSE and python to return a rule in a virtual text file created by ‘opening’ /mnt/myfusefolder/8.8.8.8 (any ip address) where the rule is either accept or deny based on geoiplookup.

  8. There is a simple workaroud for this : using a vpn in the allowed country. Is it possible to detect the source of the IP, and refuse VPN connections ?

  9. I know this might be old, but I just can’t get it to work on CentOS …
    I’m using spawn but it seems like that the hosts.allow file does not get the response back for the actual block and just allows everything…

    Anyone got this working on CentOS ?

  10. Thanks for this, I have modified the script slightly and it works great =) Within half an hour I have already blocked a bunch of attempts to login to ssh.

  11. Yaysen, I found spawn did not work as it doesn’t’ return so everyone gets though still.

    The lack of aclexec on RHEL is an issue here.. I got around it by putting IPTABLES drop rules in the DENY block of the script. ( I had posted the code but then I couldn’t submit as it said I’d done something dodgy.) I’ll try encoding it and see if that gets it though.

    /sbin/iptables -A INPUT -i eth0 -p tcp –destination-port 22 -s $1 -j DROP

    Works, secure log is full of entries from logger, and iptables -L shows all of those IP’s being blocked.

    For added measure I put this too:
    echo “ALL: $1” >> /etc/hosts.deny

    cheers

    Frank

  12. The manual is good. In the log file everything is also displayed correctly (DENY / ALLOW). But I can still sign me even with DENY. I have a system of CentOS 7 installed. Installation was carried out as described. Any idea why it does not work? Thanks, Bruno

    • You ever figure out your problem? I have the same issue. Geoip correctly determines the source of the ip logging in, but no connections are ever denied. Thanks!

    • Not sure how people get this to work. Nothing I can find indicates the tcp wrappers spawn command returns any sort of conditional allow/deny value. I did have to disable selinux to even get the spawn command to function but then it simply allowed every connection regardless of the return value of the spawn command.

  13. Jaysen Johnson on

    Excellent tutorial. Only change I had to make for CentOS 6 was that “aclexec” did not work in the hosts.allow file. I changed it to “spawn” and it works great.

    sshd: ALL: spawn /usr/local/bin/ipfilter.sh %a
    vsftp: ALL: spawn /usr/local/bin/ipfilter.sh %a

    Thanks,

    Jaysen Johnson

Exit mobile version