Search This Blog

Thursday, March 17, 2016

Block traffic to your server from a particular Country



Create a file where we can declare some rules to use:


sudo nano /etc/iptables.firewall.rules


Inside there you'll want to paste the following:


*filter
# Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0

-A INPUT -i lo -j ACCEPT

-A INPUT -d 127.0.0.0/8 -j REJECT

# Accept all established inbound connections

-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow all outbound traffic - you can modify this to only allow certain traffic

-A OUTPUT -j ACCEPT

# Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL).

-A INPUT -p tcp --dport 80 -j ACCEPT

-A INPUT -p tcp --dport 443 -j ACCEPT

# Allow SSH connections

#

# The -dport number should be the same port number you set in sshd_config


-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT

# Allow ping

-A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# Log iptables denied calls

-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

# Drop all other inbound - default deny unless explicitly allowed policy

-A INPUT -j DROP

-A FORWARD -j DROP

COMMIT

Save that. Next, we need to apply those rules – this is just a text file, and we need to instruct iptables to actually use it.


sudo iptables-restore < /etc/iptables.firewall.rules


That should have loaded the rules and applied them; you can check by


iptables -L


The output of that command ought to look like


Chain INPUT (policy ACCEPT)


target prot opt source destination


ACCEPT all -- anywhere anywhere


REJECT all -- anywhere 127.0.0.0/8 reject-with icmp-port-unreachable


ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED


ACCEPT tcp -- anywhere anywhere tcp dpt:http


ACCEPT tcp -- anywhere anywhere tcp dpt:https


ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:ssh


ACCEPT icmp -- anywhere anywhere


LOG all -- anywhere anywhere limit: avg 5/min burst 5 LOG level debug prefix "iptables denied: "


DROP all -- anywhere anywhere






Chain FORWARD (policy ACCEPT)


target prot opt source destination


DROP all -- anywhere anywhere






Chain OUTPUT (policy ACCEPT)


target prot opt source destination


ACCEPT all -- anywhere anywhere


Great, it's working! But if you reboot the server it won't be. So lets fix that by creating a file which will run at boot.


sudo nano /etc/network/if-pre-up.d/firewall


Inside that file paste:


#!/bin/sh


/sbin/iptables-restore < /etc/iptables.firewall.rules


Save it. Now we must make sure it's allowed to execute:


sudo chmod +x /etc/network/if-pre-up.d/firewall


Done. The firewall is now running with those rules applied and those rules will be re-applied every time the server reboots. But it's not blocking China yet; it's only blocking anything not on port 80 or 443 (http and https).
Using ipset to block China


You can't manually add a few thousand IP addresses to your iptables, and even doing it automatically is a bad idea because it can cause a lot of CPU load (or so I've read). Instead we can use ipset which is designed for this sort of thing. ipset handles big lists of ip addresses; you just create a list and then tell iptables to use that list in a rule.


Note; I assume that the entirety of the following is done as root. Adjust accordingly if your system is based on sudo.


apt-get install ipset


Next, I wrote a small Bash script to do all the work, which you should be able to understand from the comments in it. Create a file:


nano /etc/block-china.sh


Here's what you want to paste into it:


# Create the ipset list


ipset -N china hash:net






# remove any old list that might exist from previous runs of this script


rm cn.zone






# Pull the latest IP set for China


wget -P . http://www.ipdeny.com/ipblocks/data/countries/cn.zone






# Add each IP address from the downloaded list into the ipset 'china'


for i in $(cat /etc/cn.zone ); do ipset -A china $i; done






# Restore iptables


/sbin/iptables-restore < /etc/iptables.firewall.rules


Save the file. Make it executable:


chmod +x /etc/block-china.sh


This hasn't done anything yet, but it will in a minute when we run the script. First, we need to add a rule into iptables that refers to this new ipset list the script above defines:


nano /etc/iptables.firewall.rules


Add the following line:


-A INPUT -p tcp -m set --match-set china src -j DROP


Save the file. To be clear, my full iptables.firewall.rules now looks like this:


*filter






# Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0


-A INPUT -i lo -j ACCEPT


-A INPUT -d 127.0.0.0/8 -j REJECT






# Accept all established inbound connections


-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT






# Block anything from China


# These rules are pulled from ipset's china list


# The source file is at /etc/cn.zone (which in turn is generated by a shell script at /etc/block-china.sh )


-A INPUT -p tcp -m set --match-set china src -j DROP






# Allow all outbound traffic - you can modify this to only allow certain traffic


-A OUTPUT -j ACCEPT






# Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL).


-A INPUT -p tcp --dport 80 -j ACCEPT


-A INPUT -p tcp --dport 443 -j ACCEPT






# Allow SSH connections


#


# The -dport number should be the same port number you set in sshd_config


#


-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT






# Allow ping


-A INPUT -p icmp -j ACCEPT






# Log iptables denied calls


-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7






# Drop all other inbound - default deny unless explicitly allowed policy


-A INPUT -j DROP


-A FORWARD -j DROP






COMMIT


Right now, nothing has changed with the server because no new rules have been applied; to do so, run the block-china.sh script:


/etc/block-china.sh


This should show some output as it pulls a fresh list of Chinese based IPs and then, after a few seconds or so, it will complete and drop you back to a command prompt.


To test if it worked, run:


iptables -L


You should now see a new rule blocking China – the output ought to look like this:


Chain INPUT (policy ACCEPT)


target prot opt source destination


ACCEPT all -- anywhere anywhere


REJECT all -- anywhere loopback/8 reject-with icmp-port-unreachable


ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED


DROP tcp -- anywhere anywhere match-set china src


ACCEPT tcp -- anywhere anywhere tcp dpt:http


ACCEPT tcp -- anywhere anywhere tcp dpt:https


ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:ssh


ACCEPT icmp -- anywhere anywhere


LOG all -- anywhere anywhere limit: avg 5/min burst 5 LOG level debug prefix "iptables denied: "


DROP all -- anywhere anywhere






Chain FORWARD (policy ACCEPT)


target prot opt source destination


DROP all -- anywhere anywhere






Chain OUTPUT (policy ACCEPT)


target prot opt source destination


ACCEPT all -- anywhere anywhere


Almost done! This works, and will continue to work on re-boots. But, IP addresses change and that list will grow stale over time. If you want to pull and apply an updated list of IPs you can just run the block-china.sh script again.






Configure your websever:


We use Ngnix, So steps to block traffic from China do as follow :


Check ip modules are enabled






nginx -V






If you see --with-http_geoip_module in the output, you are ready to use the GeoIP database with nginx:


root@server1:~# nginx -V
nginx version: nginx/1.2.1
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-log-path=/var/log/nginx/access.log --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --lock-path=/var/lock/nginx.lock --pid-path=/var/run/nginx.pid --with-pcre-jit --with-debug --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_realip_module --with-http_stub_status_module --with-http_ssl_module --with-http_sub_module --with-http_xslt_module --with-ipv6 --with-sha1=/usr/include/openssl --with-md5=/usr/include/openssl --with-mail --with-mail_ssl_module --add-module=/build/buildd-nginx_1.2.1-2.1-amd64-fMGfEu/nginx-1.2.1/debian/modules/nginx-auth-pam --add-module=/build/buildd-nginx_1.2.1-2.1-amd64-fMGfEu/nginx-1.2.1/debian/modules/nginx-echo --add-module=/build/buildd-nginx_1.2.1-2.1-amd64-fMGfEu/nginx-1.2.1/debian/modules/nginx-upstream-fair --add-module=/build/buildd-nginx_1.2.1-2.1-amd64-fMGfEu/nginx-1.2.1/debian/modules/nginx-dav-ext-module
root@server1:~#





Installing The GeoIP Database


On Debian/Ubuntu, the GeoIP database can be installed as follows:


apt-get install geoip-database libgeoip1


This places the GeoIP database in /usr/share/GeoIP/GeoIP.dat.


It is possible that it is a bit outdated. Therefore we can optionally download a fresh copy from the GeoIP web site:


mv /usr/share/GeoIP/GeoIP.dat /usr/share/GeoIP/GeoIP.dat_bak


cd /usr/share/GeoIP/
wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
gunzip GeoIP.dat.gz





Configuring nginx


Open /etc/nginx/nginx.conf...


vi /etc/nginx/nginx.conf


... and place this in the http {} block, before any include lines:



[...]
geoip_country /usr/share/GeoIP/GeoIP.dat;
map $geoip_country_code $allowed_country {
default yes;
FK no;
FM no;
EH no;
}
[...]




This allows all countries, except the three countries set to no (you can find a list of country codes here). To do it the other way round, i.e. block all countries and allow only a few, you'd do it this way:



[...]
geoip_country /usr/share/GeoIP/GeoIP.dat;
map $geoip_country_code $allowed_country {
default no;
FK yes;
FM yes;
EH yes;
}
[...]




Now, this actually doesn't block any country, it just sets the $allowed_country variable. To actually block countries, you must open your vhost configuration and place the following code in the server {} container (this can go inside and also outside any location {} block):



[...]
if ($allowed_country = no) {
return 444;
}
[...]




This returns the 444 error code to any visitor from a blocked country. What this does is it closes the connection without sending any headers. You can also use another error code like 403 ("Forbidden") if you like.


Reload nginx afterwards:


/etc/init.d/nginx reload





4 Links


· nginx: http://nginx.org/


· nginx Wiki: http://wiki.nginx.org/


· HttpGeoipModule: http://wiki.nginx.org/HttpGeoipModule