Iptables – Block incoming traffic

I have setup a small home Linux server, based on Debian Linux (stable) and a Lenovo ThinkCentre hardware. Now, I wanted to have an ssh access from outside and I have encountered a few problems that I would like to talk about here.

CG NAT

Carrier-grade NAT, in essence, means that your home network is a part of your carrier’s private network and you share some exit point with others. It is a way of saving IPV4 addresses. To cite Wikipedia: “Carrier-grade NAT (CGN), also known as large-scale NAT (LSN), is an approach to IPv4 network design in which end sites, in particular residential networks, are configured with private network addresses that are translated to public IPv4 addresses by middlebox network address translator devices embedded in the network operator’s network, permitting the sharing of small pools of public addresses among many end sites. This shifts the NAT function and configuration thereof from the customer premises to the Internet service provider network.” If you are lucky enough, as I have been, one call to your carrier and they will remove you from the cg-nat. Otherwise, you will have to find another solution for connecting to your home PC. I would not know what, perhaps reverse ssh tunnel or a third party service (there should be many), something like ngrok.

HOME NETWORK SETUP

My home network setup is simple enough. Main router, the one connected to the Internet, forwards SSH port (22) to another home router (this one has a fixed IP address). Second router forwards SSH port to a home Linux machine, where we have a custom firewall. So, in essence, here we have two routers NATing the traffic to a Linux machine.

THE PROBLEM?

So, what is the issue here, you might ask? Of course, this setup should be secure enough not to give us any problems (use strong passwords). However, if we take a look at the auth logs, soon we will see automated scripts trying to connect to our SSH service, trying all kind of combinations of username and password (admin admin, raspberry pi etc…). Lets see some logs (real IP addresses left on purpose)… Logs are in /var/log/auth.log file and you can tail them like this:

$ sudo tail -f /var/log/auth.log | grep sshd

Oct 29 04:30:28 ThinkCentre sshd[18084]: Invalid user admin from 113.172.136.162 port 51419
Oct 29 04:30:28 ThinkCentre sshd[18084]: pam_unix(sshd:auth): check pass; user unknown
Oct 29 04:30:28 ThinkCentre sshd[18084]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=113.172.136.162
Oct 29 04:30:30 ThinkCentre sshd[18084]: Failed password for invalid user admin from 113.172.136.162 port 51419 ssh2
Oct 29 04:30:33 ThinkCentre sshd[18084]: Connection closed by invalid user admin 113.172.136.162 port 51419 [preauth]
Oct 29 04:32:57 ThinkCentre sshd[18089]: Received disconnect from 110.185.166.137 port 55620:11: Bye Bye [preauth]
Oct 29 04:32:57 ThinkCentre sshd[18089]: Disconnected from 110.185.166.137 port 55620 [preauth]
Oct 29 05:44:39 ThinkCentre sshd[18161]: Did not receive identification string from 183.136.236.43 port 53246
Oct 29 05:44:40 ThinkCentre sshd[18162]: Did not receive identification string from 183.136.236.43 port 54122
Oct 29 05:44:45 ThinkCentre sshd[18163]: Did not receive identification string from 183.136.236.43 port 59204
Oct 29 06:41:42 ThinkCentre sshd[18223]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=129.204.74.15  user=root
Oct 29 06:41:44 ThinkCentre sshd[18223]: Failed password for root from 129.204.74.15 port 34018 ssh2
Oct 29 06:41:48 ThinkCentre sshd[18223]: Failed password for root from 129.204.74.15 port 34018 ssh2
Oct 29 06:41:50 ThinkCentre sshd[18223]: Received disconnect from 129.204.74.15 port 34018:11: disconnected by user [preauth]
Oct 29 06:41:50 ThinkCentre sshd[18223]: Disconnected from authenticating user root 129.204.74.15 port 34018 [preauth]
Oct 29 06:41:50 ThinkCentre sshd[18223]: PAM 1 more authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=129.204.74.15  user=root
...
Oct 29 11:32:47 ThinkCentre sshd[19636]: pam_unix(sshd:auth): check pass; user unknown
Oct 29 11:32:47 ThinkCentre sshd[19636]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=5.225.141.51
Oct 29 11:32:47 ThinkCentre sshd[19637]: pam_unix(sshd:auth): check pass; user unknown
Oct 29 11:32:47 ThinkCentre sshd[19637]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=5.225.141.51
Oct 29 11:32:49 ThinkCentre sshd[19636]: Failed password for invalid user pi from 5.225.141.51 port 54688 ssh2
Oct 29 11:32:49 ThinkCentre sshd[19637]: Failed password for invalid user pi from 5.225.141.51 port 54692 ssh2
Oct 29 11:32:51 ThinkCentre sshd[19636]: Connection closed by invalid user pi 5.225.141.51 port 54688 [preauth]
Oct 29 11:32:51 ThinkCentre sshd[19637]: Connection closed by invalid user pi 5.225.141.51 port 54692 [preauth]

So, most of the traffic is not from my home country (Croatia). What we would like to do here is, if possible, simply block all traffic outside of our own country (or even allow just some specific address ranges), at least that is what I would like to do.

FIREWALL SETUP

Little investigation revealed some pre-made solutions for Linux iptables, such as xt_geoip module for iptables. You can find out more about it in this article. I have decided to go with a simpler route and simply allow IP addresses specific to the country of residence. I found out this convenient website that will give a list of country specific IP address ranges in the iptables firewall format. Format provided is actually for blocking the specific IP address ranges, but we can update it from this:

iptables -A INPUT -s 2.57.172.0/22 -j DROP
iptables -A INPUT -s 2.58.32.0/24 -j DROP
iptables -A INPUT -s 2.58.48.0/22 -j DROP
iptables -A INPUT -s 2.255.252.20/32 -j DROP

To this:

iptables -A INPUT -s 2.57.172.0/22 -j ACCEPT
iptables -A INPUT -s 2.58.32.0/24 -j ACCEPT
iptables -A INPUT -s 2.58.48.0/22 -j ACCEPT
iptables -A INPUT -s 2.255.252.20/32 -j ACCEPT

If you are not that familiar with iptables (Its been years since I have used it extensively, so I am rusty too) then you can read more about it. Here is one useful article and here is one more.

Anyway, first thing we want to do is reset the firewall rules. We need to reset the default policies of INPUT, OUTPUT and FORWARD chain’s to ACCEPT (If you are working remotely via SSH, you will get locked out if you do not do this, default policies are DROP) and flush the chains. I’ve put this into a simple bash script:

#!/bin/bash

# Set default chain policies
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

iptables -F

After this one, you are wide open (in fact, if your setup is like mine, only port 22 is open) to the world. Now you want to block all incoming traffic, except the one that was initiated by you (an established connection) and perhaps some specific IP address ranges. You could use something like this:

# Allow established sessions to receive traffic
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow my home network and localhost (loopback address)
iptables -A INPUT -p tcp --dport 22 -s 192.168.0.0/16 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -s 127.0.0.0/8 -j ACCEPT

# Allow Croatian IP addresses
iptables -A INPUT -s 2.57.172.0/22 -j ACCEPT
iptables -A INPUT -s 2.58.32.0/24 -j ACCEPT
iptables -A INPUT -s 2.58.48.0/22 -j ACCEPT
iptables -A INPUT -s 2.255.252.20/32 -j ACCEPT
...
# Set default chain policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Accept on localhost
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

If you’d like for iptables to remember these rules, after the server restart, then you’d have to use (Debian system) something like $sudo /sbin/iptables-save.

I am keeping my rules on github.

DYNAMIC DNS

Of course, when connecting to your home network, you would need to know the IP. For this purpose, you can use some dynamic dns service. Here is a list of some free services. You will get a username and a token that you can use on your home router setup.

TESTING

Now, its time to test if thing work properly or not. For a remote connection, I am using Windows machine and Putty. From the Croatian IP address (another carrier) when I try to connect I get an ssh login.

Putty remote SSH connection – Login

So, that works, but what If I try from another country? It shouldn’t work. For trying it out, I created an account on ProtonVPN (they are offering free basic VPN usage, so you can try it out), connected to another country and then tried to connect to my home machine… it times out… which is exactly what we want.

Putty remote SSH connection – Connection from another country times out

Also, by looking at the auth logs, this look much calmer now.