Apache and GeoIP2 module

Often, the Internet can seem like the wild wild west (or the wild wild east, depends on where you are located), and you can be fairly certain that any public facing service will draw some amount of brute force attacks sooner rather than later. My preference is to avoid any public facing services unless it’s really needed. Firewall is your friend here, but then again, there are some services where limiting access to just a few selected IPs simply won’t do. Roundcube webmail would be a good example – it’s a very convenient way of accessing your email box from a random location when you’re on vacation or something. Having access to the Roundcube interface limited to just a handful of IP addresses kind of beats its purpose.

On the other hand, sometimes an interface gets accessed primarily just from a single geographic area and it’s very unlikely to be accessed from other areas. If you live and spend most of your time within boundaries of a small country like I do, it might make sense to limit access to some services just to that single area, thus preventing possible brute force password guessing from the other 99 % of geolocations that do exist around the globe.

That being said, geolocation is not a silver bullet; firewall or even fail2ban are still your friends, but when combined with fail2ban, you can potentially take away a lot of unnecessary CPU workload from fail2ban this way.

So how do you go about this? Roundcube is a website that gets served by a webserver, Apache in my case. But Apache itself doesn’t have a mechanism to determine what country or city an IP address belongs to, it simply doesn’t have that information.

We’ll therefore need something that’s able to provide this information to the webserver. The one service that I know of is MaxMind. I’m not sure if I remember it correctly, I think that it was possible to download their lowest tier geolocation databases for free without an account in the past. Nowadays, however, they require that you create an account with them to be able to get hold of the basic geolocation databases (the ones that give you country and city location level, I think). So I went ahead, created an account and got an account ID and a license key.

Then you had to install the geoipupdate package on your system and place the above mentioned account ID and license key to:

/etc/GeoIP.conf

The purpose of the geoipupdate package is to pull the latest geoip data on a regular basis, which is ensured by:

# cat /etc/cron.weekly/geoipupdate.sh 
#!/bin/bash
# top of crontab
MAILTO=email@example.com

/usr/bin/geoipupdate

More information on creating an account with them and updating the databases is available here: https://dev.maxmind.com/geoip/updating-databases

With MaxMind’s GeoIP2 databases set up on your machine, the webserver now has a pool of information on individual IP addresses that it can tap into finally. We just need to inform the webserver about this option somehow and we also need to instruct it on what to do with the information that it’s going to acquire.

First, to be able to read the databases, Apache needs a module. It doesn’t seem to be packaged in any repository so it had to be built manually, I followed the how-to here:

https://maxmind.github.io/mod_maxminddb/

After installing the module, it’s enabled in the Apache config:

# cat /etc/httpd/conf/httpd.conf

...

<IfModule mod_maxminddb.c>

    MaxMindDBEnable On
    MaxMindDBFile COUNTRY_DB /usr/share/GeoIP/GeoLite2-Country.mmdb

    MaxMindDBEnv MM_COUNTRY_CODE COUNTRY_DB/country/iso_code
    MaxMindDBEnv MM_COUNTRY_NAME COUNTRY_DB/country/names/en

</IfModule>

...

After enabling the module on the Apache global level and after reloading, it’s finally possible to use the geolocation information on the individual virtual host level like:

<Directory /var/www/example.com/roundcube>
    order deny,allow
    deny from all
    SetEnvIf MM_COUNTRY_CODE ^CZ let_cz_user_in
    allow from env=let_cz_user_in
</Directory>

This would limit access to the given directory only to IP addresses that are located in the Czech Republic – the ^CZ part. This can be obviously changed to whatever country codes you’d need, or it can be even narrowed down to the city level, I believe.

To explain this part a little bit more, for the /var/www/example.com/roundcube directory, we first define that access is denied by default. Then there’s a SetEnvIf conditional which checks whether the country code variable MM_COUNTRY_CODE (this environment variable is provided by the MaxMind module for each request) begins with the CZ characters, i.e. the ^CZ expression. If this condition is met, a new variable called let_cz_user_in is set. On the next line, access is allowed if the variable called let_cz_user_in is defined in the environment. If it is, the content is served to the client. If not, they will get a 403 http code response.

To conclude this topic, Roundcube is just one example here, there may be other uses cases such as WordPress administration and others. But I’d like to stress again that the geolocation of an IP address is not by any means a 100 % reliable piece of information, it can be forged, the website can be accessed via a proxy or vpn, so please consider this to be one of the many possible tools in your tool belt rather than a silver bullet to solve everything.

Update for Apache 2.4

As of now (year 2023), the Apache 2.4 version is more common. It has seen some big changes in the configuration format so while the above Directory configuration hasn’t been completely deprecated yet, it should look like this in future:

<Directory /var/www/example.com/roundcube>
    Require all denied
    SetEnvIf MM_COUNTRY_CODE ^CZ let_cz_user_in
    Require env let_cz_user_in
</Directory>