GeoIP Blocking

Update (10/11/20): I wrote a revised guide found here for GeoIP Blocking on Ubuntu 20.04LTS.

There are some cases where I know that I won’t need for a service to be open to the entire world and I can restrict it to the country that I am in. A good example of this is a mailserver used by my family and myself, it doesn’t need to have its imaps or smtp submission port open to the world – it can easily be restricted to US-based IP addresses. A server with an ssh port open and is managed by people in a known set of countries can also be a good example of where to use GeoIP blocking.

The accuracy of GeoIP lists, MaxMind being the one that I am using, is good enough for my uses. And that is why this is only a layer of protection on top of other tools such as Fail2ban.

Setting up GeoIP blocking is easy. This guide is for Ubuntu 16.04LTS and 18.04LTS.

Install Prerequisites

sudo apt-get update; sudo apt-get -y upgrade
sudo apt-get install curl unzip perl
sudo apt-get install xtables-addons-common
sudo apt-get install libtext-csv-xs-perl libmoosex-types-netaddr-ip-perl

Get Conversion Script for MaxMind GeoLite2 Databases

The iptables module xtables-addons uses the older MaxMind GeoLite database format which was discontinued in January 2019. Thankfully, Martin Schmitt write a script to convert the new MaxMind GeoLite2 database format into the legacy format.

cd /usr/local/src
sudo git clone https://github.com/mschmitt/GeoLite2xtables.git

Making It Work

First, get a license key to use the new GeoLite2 database. Sign up here and get a license key, it’s all free.

Move the license key file for the conversion script.

sudo mv /usr/local/src/GeoLite2xtables/geolite2.license.example /usr/local/src/GeoLite2xtables/geolite2.license

Edit /usr/local/src/GeoLite2xtables/geolite2.license and swap in the license key you obtained from the MaxMind website.

Create a directory for the final database that xtables-addons will use:

sudo mkdir /usr/share/xt_geoip

Create a script to make it all work /usr/local/bin/geo-update.sh

#!/bin/bash
cd /usr/local/src/GeoLite2xtables
./00_download_geolite2
./10_download_countryinfo
cat /tmp/GeoLite2-Country-Blocks-IPv{4,6}.csv |./20_convert_geolite2 /tmp/CountryInfo.txt > /usr/share/xt_geoip/GeoIP-legacy.csv
/usr/lib/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip /usr/share/xt_geoip/GeoIP-legacy.csv

Run the script and it will populate the database with GeoIP data for first use. Setup a cronjob to update the database regularly.

Validate It All Worked

Run:

modprobe xt_geoip
lsmod | grep ^xt_geoip

Output should look like:

xt_geoip               16384  2

Run:

iptables -m geoip -h

Output should look like (truncated):

iptables v1.6.1
 
Usage: iptables -[ACD] chain rule-specification [options]
       iptables -I chain [rulenum] rule-specification [options]
       iptables -R chain rulenum rule-specification [options]
       iptables -D chain rulenum [options]
       iptables -[LS] [chain [rulenum]] [options]
       iptables -[FZ] [chain] [options]
       iptables -[NX] chain
       iptables -E old-chain-name new-chain-name
       iptables -P chain target [options]
       iptables -h (print this help information)

Adding A Rule

This will not be persistent. But, it’s a way to see that things are okay. This example rule drops all traffic from RU and CN to port 25.

iptables -A INPUT -m geoip -p tcp --dport 25 --src-cc RU,CN -j DROP

At this point, any thing done above will not be persistent, so if you end up locking yourself out of your system, reboot it and you’ll be fine. Just make sure that you have access to a way to reboot your system or have access to a system console. Anything done below this can lock you out of your system permanantly so make sure you have a way to get into the system without having network access (console, ahem).

I use a different way of doing this since my system drops packets by default, I have rules that allow traffic based on geo. This example rule allows ssh traffic only from US-based IP addresses.

iptables -A INPUT -m geoip -p tcp --dport 22 --src-cc US -j ACCEPT

I had ufw running already (which is why the system is blocking all traffic by default), so integrating the GeoIP blocking with ufw was pretty easy and it’s a simple way of making rules persist over reboots. There are two files (one if your system is not IPv6) that need to be edited:

/etc/ufw/before.rules  
/etc/ufw/before6.rules

As the naming suggests, the before6.rules file is for IPv6 and before.rules is for IPv4. Adding rules to these files is easy. Just add it before the COMMIT statement at the end of the file. An example of the ACCEPT rule above, add this like to the file(s):

-A ufw-before-input -m geoip -p tcp --dport 22 --src-cc US -j ACCEPT  

If you plan to apply the same country rules to multiple ports, then it would look like this:

-A ufw-before-input -m geoip -p tcp -m multiport --dports 22,993,587 --src-cc US -j ACCEPT  

That’s all!