Linux Firewall (Part 2) : nftables
Intro
In the previous post, we installed RHEL and setup the networks for our firewall. In this post, we will start using nftables. First we will setup a basic skeleton and then add some rules later.
nftables Setup
Before using nftables directly, we will stop and disable the default firewall
systemctl stop firewalld
systemctl disable firewalld
Next, we will create configuration file(s) for nftables
nano /etc/nftables/myrules.nft
Flush the default/previous rules. Create a new ip table named fire. Put our configuration files in it. These files will have our rule chains (Netfilter hooks). For more information about them check out Netfilter hooks.
We only have one output rule for traffic originating from the firewall, so we can add that chain directly. We will allow all traffic to go out.
/etc/nftables/myrules.nft
flush ruleset
include "./defines.nft"
table ip fire {
include "./input.nft"
include "./nat.nft"
include "./forward.nft"
chain output {
type filter hook output priority 0; policy accept; # let this machine's traffic out
}
}
Create familiar names for things we are going to use frequently
/etc/nftables/defines.nft
# interfaces
define if_wan = "enp1s0"
define if_dmz = "enp2s0"
define if_lan = "enp3s0"
# networks
define net_dmz = 10.1.1.0/24
define net_lan = 10.1.2.0/24
# machines
define desktop = 10.1.2.10
Create an input chain. This will have rules for traffic coming into the firewall. We can create smaller chains to jump to from the input chain. This will let us manage a bit more efficiently. Our default rule will be to drop traffic not explicitly allowed. We will also log the traffic that is allowed with the appropriate prefixes.
/etc/nftables/input.nft
# input from lan
chain in_from_lan {
# allow ssh port
ip protocol . th dport { tcp . 22 } log prefix "[NFTABLES] [SSH] "accept
# allow DNS ports
ip protocol . th dport { udp . 53 } log prefix "[NFTABLES] [DNS] " accept
ip protocol . th dport { tcp . 53 } log prefix "[NFTABLES] [DNS] " accept
# allow DHCP port
ip protocol . th dport { udp . 67 } log prefix "[NFTABLES] [DHCP] " accept
}
chain in_from_dmz {
# allow DNS ports
ip protocol . th dport { udp . 53 } log prefix "[NFTABLES] [DNS] " accept
ip protocol . th dport { tcp . 53 } log prefix "[NFTABLES] [DNS] " accept
# allow DHCP port
ip protocol . th dport { udp . 67 } log prefix "[NFTABLES] [DHCP] " accept
}
chain in_from_wan {
}
chain input {
# default policy drop
type filter hook input priority 0; policy drop;
# drop invalid packets
ct state vmap { established : accept, related : accept, invalid : drop }
# ping requests
icmp type echo-request limit rate 5/second accept
# allow all loopback traffic
iifname lo accept
# jump to chains based on the interface the traffic originated from
iifname vmap { $if_lan: jump in_from_lan, $if_dmz: jump in_from_dmz, $if_wg0: jump in_from_lan, $if_wan: jump in_from_wan }
}
Create chains for Network Address Translation (NAT). We can use the prerouting hook for Desination NAT (DNAT) and postrouting hook for Source NAT (SNAT). Right now, we don’t have any special rules for the prerouting hook. For postrouting hook, we will use masquerade for routing packages between different interfaces/subnets. Masquerading automatically sets the source address to the address of the output interface.
[!Note]
The post routing hooks runs after the output and forward hooks, so masquerade plays well with them.
/etc/nftables/nat.nft
chain prerouting {
# default accept all
type nat hook prerouting priority 0; policy accept;
}
chain postrouting {
# default accept all
type nat hook postrouting priority 100; policy accept;
ip saddr $net_lan oifname $if_wan masquerade
ip saddr $net_dmz oifname $if_wan masquerade
iifname $if_wan oifname $if_lan masquerade
}
Next, we will create a forward chain. Here we will create rules to decide what traffic to forward to different interfaces/subnets. Our default rule will be to drop traffic not explicitly allowed. This will allow us to create isolation between networks.
We will allow all traffic from DMZ and LAN to go out to WAN for internet access. We can also allow traffic from LAN to DMZ, so we can access the servers from our devices in LAN subnet.
/etc/nftables/forward.nft
chain forward {
# default drop all
type filter hook forward priority 0; policy drop;
ct state vmap { established : accept, related : accept, invalid : drop }
# accept lan to dmz
iifname $if_lan oifname $if_dmz accept
# accept lan to wan
iifname $if_lan oifname $if_wan accept
# accept dmz to wan
iifname $if_dmz oifname $if_wan accept
}
Enable and start nftables service
systemctl enable nftables
systemctl start nftables
When we make changes later, we can restart nftables service with
systemctl restart nftables
At this point we should have a functional firewall/router.
Applications
Let’s take a look at some common application and how to configure the firewall for it.
Remote Desktop
If we want to access our desktop from a remote location (internet), we can enable Remote Desktop on windows. Let’s add rules to enable it.
Add rule to change the destination address of the traffic coming in at port 3389 to the ip address of the desktop.
Add in prerouting chain, /etc/nftables/nat.nft
iifname $if_wan tcp dport { 3389 } dnat to $desktop:3389
[!Note]
Remember that the masquerade will automatically take care of SNAT, so we only need to worry about DNAT.
Add rule to allow the traffic from WAN interface to LAN interface with destination desktop and port 3389.
Add in forward chain, /etc/nftables/forward.nft
iifname $if_wan oifname $if_lan ip daddr $desktop tcp dport { 3389 } accept
Reverse Proxy
We can run a reverse proxy on the firewall itself (more on this in another post). We want to allow client from WAN and LAN to connect to the reverse proxy on port 80 and 443. Let’s see how to create rules for them. The reverse proxy is running on the firewall, so we will use the input chains.
Add rules in in_from_lan
chain, /etc/nftables/input.nft
ip protocol . th dport { tcp . 80 } log prefix "[NFTABLES] [HTTP] " accept
ip protocol . th dport { tcp . 443 } log prefix "[NFTABLES] [HTTPS] " accept
Add rules in in_from_wan
chain, /etc/nftables/input.nft
ip protocol . th dport { tcp . 80 } log prefix "[NFTABLES] [HTTP] " accept
ip protocol . th dport { tcp . 443 } log prefix "[NFTABLES] [HTTPS] " accept
Thank you for reading. Check out the other parts in the series below.