2020-12-11

Allow Access by Country with Firewalld

Tags: linux . security . index

shield icon

There are various guides on the web, regarding how to block a whole country, or a group of IP addresses using various firewall methods.

But, what if we need to do the opposite. Allow only one country, and block everyone else.

This may be particularly useful for personal services, like Nextcloud, that you know will only be used by yourself.

Of course, using the same method, it is also possible to allow only a few IP addresses, a city, or just one ISP. All that is needed are the group of IP blocks.

For countries, we can get them from the IPdeny lists. The aggregated files will be more efficient, since they will have less blocks and thus fewer rules.

Since this article uses firewalld, some quick notes on how it works.

By default, firewalld will have one active zone, with some default services attached.

# get all active zones
$ firewall-cmd --get-active-zones
public
  interfaces: eth0

# list all rules on the public zone
$ firewall-cmd --zone=public --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources: 
  services: dhcpv6-client ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

The above rules mean, that everyone coming through the interface eth0 will be allowed on the services ssh and dhcpv6-client.


Now back to our goal. We will need to do the following:

# download the country IP blocks
wget "https://www.ipdeny.com/ipblocks/data/aggregated/am-aggregated.zone"

Here we create an ipset with the name allowlist (could be anything), and we populate it with the downloaded list.

# create list
firewall-cmd --permanent --new-ipset=allowlist \
    --type=hash:net --option=family=inet \
    --option=hashsize=4096 --option=maxelem=200000

# populate list
firewall-cmd --permanent --ipset=allowlist \
    --add-entries-from-file=./am-aggregated.zone
    
# we can display the list like this
firewall-cmd --ipset=allowlist --get-entries

Then, we create a new zone allowzone, assign the allowlist to it, and enable some services and ports.

# create zone
firewall-cmd --permanent --new-zone allowzone

# assign the list to the new zone
firewall-cmd --permanent --zone=allowzone --add-source=ipset:allowlist

# allow services
firewall-cmd --permanent --zone=allowzone --add-service ssh
firewall-cmd --permanent --zone=allowzone --add-service http
firewall-cmd --permanent --zone=allowzone --add-service https

Now, allowzone will only allow the added services for the allowlist, and no one else.

# check all the rules on the new zone
$ firewall-cmd --permanent --zone=allowzone --list-all
allowzone (active)
  target: default
  icmp-block-inversion: no
  interfaces: 
  sources: ipset:allowlist
  services: http https ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

We still need to remove any enabled services from the public zone, otherwise it will keep allowing everyone on eth0.

Up until this point, since we are using the --permanent option, nothing will be applied until we reload or restart firewalld. And since we added a new zone, it is not yet dangerous to do so now. However, once we remove services like ssh from the active public zone, we may lock ourselves out. So, some common sense care should be applied here. Maybe start by removing a less dangerous port first.

# remove services from public zone
firewall-cmd --permanent --zone=public --remove-service ssh

Finally, reload or restart firewalld.

firewall-cmd --reload