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.
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.
26 Comments
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.
FYI,
just noticed it doesn’t handle logrotate’s log handling.
It’ll freeze on the log is rotated 😉
geoip-database is outdated since long time ago.
geoip-database is already the newest version (20181108-1).
I use Centos 8, as the TCP wrapper (hosts.allow/deny)been removed here, how can I use GEOIP in centos 8 still?
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.
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
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
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.
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.
i have red all your comments all that stuff doesn’t work properly with my centos 7
Hello.
how can I use it for smtp? smtpd .. allow connection to send mail from a defined country.
thanks, Andres.
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.
For all CentOS users, spawn or aclexec does not work, the hint is already given by using iptables to block the user.
The iptables command given appends (-A) so the connection might still go through, to really block the IP you have to insert (-I) the block rule at rule #1.
You can use my altered script for a working CentOS/RHEL version: https://github.com/chiel1980/scripts/blob/master/ipfilter.sh
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 ?
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 ?
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.
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
same here.. still can access when hit on enter
Hi
may i know where i can find the log file?
Try in /var/log/secure
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!
Still same issue here 🙂
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.
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
Hi Jaysen,
Thanks, I have updated article with spawn.