SSH & iptables geo restriction

SSH brute force attacks are incredibly common.  I primarily use sshguard to auto-block individual IPs after a certain number of failed requests.  This will continue, but I wanted to add another layer to reduce the number of connections making it to my SSH server processes without either changing the port or restricting to a specific network.

Geographic Solutions

I found a number of people were using maxmind's geolite country database with tcp wrappers / hosts.allow and a shell script.  I really don't want a shell process spawned with every request, I want them blocked by the firewall.  I looked at the plain text dump of this database, and was surprised to see a number of /32 addresses in a country level database.  I didn't want a crazy number of entries added to iptables.

I then looked at using the simple IANA assignments of /8 addresses.  This would probably work for me... as to my knowledge there aren't any /8's assigned to outside regions that contain smaller assignments back to ARIN.

After a little more digging, I found a site/service that offers regularly updated aggregated IP blocks by country.  The sizes here are reasonable, with around 18,000 blocks for the US.

netfilter sets

After a little digging and performance tests, it looks like creating whitelists will work fine.  I'm not a fan of firewalld, so I will use iptables/ipset for this.

Note: All sets must be created before the iptables rules are generated.  It's OK if they are empty, with the notable exception of the whitelist, as you don't want to risk getting locked out of your server.

To prepare the sets, I created a quick bash script.  I found something similar written by someone else, but it was very slow for large numbers of networks, as it spawned a process for every row being added.

#!/bin/bash
if [ $# -ne 1 ];
	then echo "Usage $0 [2 char country code]"
	exit 1
fi

url="http://www.ipdeny.com/ipblocks/data/aggregated/$1-aggregated.zone"
setname="geo_$1"

echo "create $setname hash:net family inet hashsize 8192 maxelem 65536" > "$setname.ipset"
curl -s $url | sed "s/^/add $setname /" >> "$setname.ipset"

This will create a file that can be imported using: cat $filename | ipset restore

iptables rules

We use sshguard to handle temporary blocking for those doing brute-force attacks.

For SSH:

  • Allow IPs in the whitelist
  • Block anything in the sshguard blocklist
  • Allow anything else in the US (or your country/countries of choice)
  • Block anything in the rest of the world
*filter

# Allow all loopback (lo0) traffic and reject traffic
# to localhost that does not originate from lo0.
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -s 127.0.0.0/8 -j REJECT

# Allow ping.
-A INPUT -p icmp -m state --state NEW --icmp-type 8 -j ACCEPT

# Allow mosh
-A INPUT -p udp -m multiport --dports 60000:61000 -j ACCEPT

# Allow SSH connections from whitelist
-A INPUT -p tcp --dport 22 -m state --state NEW -m set --match-set whitelist src -j ACCEPT

# Block SSH from sshguard blocklist
-A INPUT -p tcp --dport 22 -m state --state NEW -m set --match-set sshguard4 src -j REJECT

# Allow SSH connections from usa
-A INPUT -p tcp --dport 22 -m state --state NEW -m set --match-set geo_us src -j ACCEPT

# Allow HTTP and HTTPS connections from anywhere
# Allow RTMP from anywhere
-A INPUT -p tcp --dport 80 -m state --state NEW -j ACCEPT
-A INPUT -p tcp --dport 443 -m state --state NEW -j ACCEPT
-A INPUT -p tcp --dport 1935 -m state --state NEW -j ACCEPT

# Allow inbound traffic from established connections.
# This includes ICMP error returns.
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Reject all other inbound.
-A INPUT -j REJECT

# Reject all traffic forwarding.
-A FORWARD -j REJECT

COMMIT

Making it work on CentOS (7/8)

CentOS 7+ by default uses firewalld.  It must be disabled and iptables/ipset tools and systemd unit files must be installed

yum -y install iptables iptables-services ipset ipset-service
systemctl disable firewalld
systemctl stop firewalld
systemctl mask firewalld

systemctl enable iptables
systemctl enable ipset

After you have finished creating your rules and sets, you'll want to save them.

/usr/libexec/ipset/ipset.start-stop save whitelist
/usr/libexec/ipset/ipset.start-stop save sshguard4
/usr/libexec/ipset/ipset.start-stop save geo_us
/usr/libexec/iptables-iptables.init save