Skip to main content
RH134

Using nftables in CentOS 8

By December 25, 2019September 12th, 2022No Comments

RHCSA 8 Study Guide

Using  nftables in CentOS 8 is the lesson we look at today.The default backend firewall module used by the Linux kernel 4.18 in Red Hat Enterprise Linux 8 and CentOS 8 is nftables. Although this can be managed by firewalld experienced Linux administrators may prefer to use the native nft command. The nftables is able to collapse firewall management for IPv4, IPv6 and bridging into the single command line utility: nft.

Disable Firewalld Before Using nftables in CentOS 8

Both Red Hat Enterprise Linux 8 and CentOS 8 have firewalld enabled by default, this will manage nftables in the backend for us. If we want to manage nftables natively we must disable firewalld:

# systemctl disable --now firewalld
# systemctl mask firewalld
# reboot

Creating Tables and Chains

Rebooting the system after disabling firewalld will ensure that we have no remnants of the tables, chains and rules added by firewalld. We start up with nothing in-place. Unlike iptables, nftables does not have any tables or chains created by default. Managing nftables natively requires us to create these objects:

protocol family: At the top level of the configuration tree we have protocol families: ip, ip6, arp, bridge, netdev and inet. Using inet we can cater for both IPv4 and IPv6 rules in the one table.

table: Below the protocol family we define tables to group chains together. The default table was the filter table in iptables but we also had other tables such as the nat table.

chain: A chain presents a set of rules that are read from the top down. We stop reading once we match a rule. When creating a chain in nftables we can specify the type of chain – we use the filter type, the hook – we will be working with input hook and a priority. The lower the number the higher the priority and the highest priority chain takes precedence. We will also specify the optional policy which defines the action if no rule is met.

To list existing tables, we shouldn’t have any, we use the following:

# nft list tables

To create a new table below the inet family:

# nft add table inet filter
# nft list tables
table inet filter

The filter table is now created within the inet family but will be empty, it does not contain and chains:

# nft list table inet filter
table inet filter {
}

We will now define a new chain which we will call INPUT, the name though is not so important as it is the type and hook that define the chain’s function.

# nft add chain inet filter INPUT \
{ type filter hook input priority 0 \; policy accept \; }
# nft list table inet filter
table inet filter {
  chain INPUT {
    type filter hook input priority 0; policy accept;
  }
}

Having created the chain, we can start to see the content of the table growing. When creating the chain, we escape the enclosed semi-colons so they are not expanded by the bash shell.

Adding Rules to Nftables

Now that the chain is in-place we can start adding rules to the chain. With the policy set to accept everything is allowed by default. This may seem strange but works best. If we denied by default we would lose the SSH connect we have to the system as soon as the chain was created. We add those entries we want to accept and end the chain with a drop entry for everything else. This is then matched before the policy is implemented.

If we only want to allow inbound SSH to the system then we can add that rule but it would be naïve to imagine this is all we needed. Generally, we allow all local traffic, to lo interface and we allow inbound traffic that is in response to outbound requests that we have initiated.

# nft add rule inet filter INPUT iif lo accept
# nft add rule inet filter INPUT ct state established,related accept
# nft add rule inet filter INPUT tcp dport 22 accept
# nft add rule inet filter INPUT counter drop
# nft list table inet filter
table inet filter {
  chain INPUT {
    type filter hook input priority 0; policy accept;
    iif "lo" accept
    ct state established,related accept
    tcp dport ssh accept
    counter packets 0 bytes 0 drop
  }
}

We add 4 rules in the order that we want them read before listing the table contents again. It is important to ensure that the last rule that is used to match all remaining traffic is placed at the end of the chain. Anything we add after this will be ignored as all traffic will match the drop action. We could just use drop but we also add the counter so that we have a way of viewing dropped packets.

Placing Rules Accurately

Using the add keyword we are appending, without any specific number, rules are appended to the end of the chain. To place a rule in a specific location in the chain we need to view these numbers known as handles, adding the option -a will help:

# nft list table inet filter -a
table inet filter { # handle 4
  chain INPUT { # handle 1
  type filter hook input priority 0; policy accept;
  iif "lo" accept # handle 2
  ct state established,related accept # handle 3
  tcp dport ssh accept # handle 4
  counter packets 2328 bytes 481896 drop # handle 5
  }
}

If needed to add another protocol to be allowed before the drop rule we could use the position keyword. In conjunction with the keyword add, rules are placed after the handle listed as the position. The SSH rule is at handle 4 so adding to position 4 will add after the SSH rule :

# nft add rule inet filter INPUT position 4 tcp dport 80 accept
# nft list table inet filter -a
table inet filter { # handle 4
  chain INPUT { # handle 1
    type filter hook input priority 0; policy accept;
    iif "lo" accept # handle 2
    ct state established,related accept # handle 3
    tcp dport ssh accept # handle 4
    tcp dport http accept # handle 7
    counter packets 2344 bytes 485208 drop # handle 5
  }
}

The accept rule for HTTP traffic is now after the SSH rule and before the drop rule and will subsequently we allowed. We can remove this rule using the handle, it has the handle of 7.

# nft delete rule inet filter INPUT handle 7

Another mechanism to add rules to a chain is using the insert keyword. Like add, insert can use the position number for exact placement but his time before the handle not after. Inserting the HTTP rule before SSH we could use:

# nft insert rule inet filter INPUT position 4 tcp dport 80 accept
# nft list table inet filter -a
table inet filter { # handle 4
  chain INPUT { # handle 1
    type filter hook input priority 0; policy accept;
    iif "lo" accept # handle 2
    ct state established,related accept # handle 3
    tcp dport http accept # handle 8
    tcp dport ssh accept # handle 4
    counter packets 2361 bytes 488727 drop # handle 5
  }
}

The HTTP rule is now placed before the SSH Rule.

Persisting Nftables

Everything that we have managed so far is memory resident and will be lost after a reboot. Saving the ruleset to a file allows us to load the same rules again. The ruleset sub-command is a great way to list rules from all tables and chains:

# nft list ruleset
table inet filter {
  chain INPUT {
    type filter hook input priority 0; policy accept;
    iif "lo" accept
    ct state established,related accept
    tcp dport http accept
    tcp dport ssh accept
    counter packets 2384 bytes 493488 drop
    }
}

# nft list ruleset > myrules

To clear the table, we can flush it and the delete it:

# nft flush table inet filter

# nft delete table inet filter

# nft list tables

#

We are now back to having nothing in nftables but we can reload the file we saved the ruleset to.

# nft -f myrules
# nft list table inet filter
table inet filter {
  chain INPUT {
    type filter hook input priority 0; policy accept;
    iif "lo" accept
    ct state established,related accept
    tcp dport http accept
    tcp dport ssh accept
    counter packets 2386 bytes 493902 drop
  }
}

Everything is now restored. Using out owned named files is one thing but if we wanted to use this with systemd and the nftables.service unit then, in CentOS 8 and RHEL 8 we must save the configuration to /etc/sysconfig/nftables.conf:

# nft list ruleset > /etc/sysconfig/nftables.conf
# systemctl enable nftables.service
# reboot
# nft list table inet filter
table inet filter {
  chain INPUT {
    type filter hook input priority 0; policy accept;
    iif "lo" accept
    ct state established,related accept
    tcp dport http accept
    tcp dport ssh accept
    counter packets 2393 bytes 495351 drop
  }
}

On reboot the saved ruleset is now loaded and the firewall operational