This article is a step-by-step tutorial that shows you how to configure your iptable firewall with firewalld to allow only requests originating from a country
firewalld is based on iptable and therefore supports the same concept such as:
To filter by country, you need to:
This article gives you a detailed step-by-step on how to do it.
With the same method, you can also filter by provider in order to allow only Cloudflare to reach HTTP and HTTPS
GeoDNS is also another way to block by country.
Each set of ip has a storage format called the type that defines :
You can't change this format once the ip set is created.
To store the IP of a country, we will use the hash:net format because it store the information by range ( not by ip as the hash:ip format is doing)
The file format for a hash:net type is:
ip
ip[/cidr]
fromaddr-toaddr
You create them from a IP geolocalization database.
For example from the GeoIp database, in Postgress, we have just run this command to get all Netherlands IP subnet.
SELECT
inet_merge(
'0.0.0.0'::inet + ip_from,
'0.0.0.0'::inet + ip_to
) from ip
where ctry = 'NL'
The output was used to create this file called ipset_nl.txt in this step by step.
2.56.16.0/22
2.56.32.0/22
2.56.56.0/22
2.56.100.0/22
...
The below command create a ipset name of the type hash:ip
firewall-cmd \
--new-ipset=nl \
--type=hash:net \
--permanent \
--option=family=inet
where:
The command should return:
success
To load the file created from the geo database, you would performed this command.
firewall-cmd \
--ipset=nl \
--permanent \
--add-entries-from-file=/tmp/ipset_nl.txt
success
Reload to load the ipset in memory and therefore make it visible to the ipset command
systemctl reload firewalld
Check that the ipset is not full
ipset -t list nl
Name: nl
Type: hash:net
Revision: 6
Header: family inet hashsize 2048 maxelem 65536
Size in memory: 135032
References: 0
Number of entries: 5139
Their is one line by entry (ie 5139) and the memory size is 135032/1024=131kb
While with a hash:ip, you're list would be full because the record is not at the net level but at the ip. It creates therefore all IP for each subnet.
Name: nl
Type: hash:ip
Revision: 4
Header: family inet hashsize 524288 maxelem 1000000
Size in memory: 22553880
References: 0
Number of entries: 1000000
You can test your IP.
ipset test nl 167.114.98.233
167.114.98.233 is NOT in set nl.
ipset test nl 143.176.205.82
Warning: 143.176.205.82 is in set nl.
Adding a rule that drops all packet for the ssh Firewalld (port 22) if they are not from Netherlands
firewall-cmd \
--zone=public \
--permanent \
--add-rich-rule='rule family="ipv4" source not ipset="nl" service name="ssh" drop'
success
When your restart you are making the rules active. If you are locked out because of any manipulation, check this article that will help you recover your system.
Made the rule permanent.
systemctl stop firewalld
systemctl start firewalld
# systemctl reload firewalld
Test:
Check the log;
systemctl status firewalld
# or tail -20 /var/log/firewalld
Check that the rule is still present
firewall-cmd --list-rich-rule
ERROR: COMMAND_FAILED: '/usr/sbin/ipset restore' failed: ipset v7.1: Error in line 45: Hash is full, cannot add more elements
If you use the ipset type hash:ip and that you use a subnet as entry, each subnet is transformed to create an entry for each IP and you can quickly be full.
Example:
ipset -t list nl
Name: nl
Type: hash:ip
Revision: 4
Header: family inet hashsize 32768 maxelem 65536
Size in memory: 1444728
References: 0
Number of entries: 65536
Solution: