- Mastering Linux Security and Hardening
- Donald A. Tevault
- 1620字
- 2025-02-24 18:55:04
Using nft commands
My preferred method of working with nftables is to just start with a template and hand-edit it to my liking, as we did in the previous section. But for those who'd rather do everything from the command line, there's the nft utility.
Let's say that you've observed an attack in progress, and you need to stop it quickly without bringing down the system. With an nft command, you can create a custom rule on the fly that will block the attack. Creating nftables rules on the fly also allows you to test the firewall as you configure it, before making any permanent changes.
And if you decide to take a Linux security certification exam, you might see questions about it. (I happen to know.)
There are two ways to use the nft utility. For one, you could just do everything directly from the Bash shell, prefacing every action you want to perform with nft, followed by the nft subcommands. The other way is to use nft in interactive mode. For our present purposes, we'll just go with the Bash shell.
First, let's delete our previous configuration and create an inet table since we want something that works for both IPv4 and IPv6. We'll want to give it a somewhat descriptive name, so let's call it ubuntu_filter:
sudo nft delete table inet filter
sudo nft list tables
sudo nft add table inet ubuntu_filter
sudo nft list tables
Next, we'll add an input filter chain to the table that we just created (note that since we're doing this from the Bash shell, we need to escape the semicolon with a backslash):
sudo nft add chain inet ubuntu_filter input { type filter hook input priority 0\; policy drop\; }
We could have given it a more descriptive name, but for now, input works. Within the pair of curly brackets, we're setting the parameters for this chain.
Each nftables protocol family has its own set of hooks, which define how packets will be processed. For now, we're only concerned with the ip/ip6/inet families, which have the following hooks:
- Prerouting
- Input
- Forward
- Output
- Postrouting
Of these, we're only concerned with the input and output hooks, which apply to filter-type chains. By specifying a hook and a priority for our input chain, we're saying that we want this chain to be a base chain that will accept packets directly from the network stack. You will also see that certain parameters must be terminated by a semicolon, which in turn would need to be escaped with a backslash if you're running the commands from the Bash shell. Finally, we're specifying a default policy of drop. If we had not specified drop as the default policy, then the policy would have been accept by default.
Some people like to create chains with a default accept policy, and then add a drop rule as the final rule. Other people like to create chains with a default drop policy, and then leave off the drop rule at the end. Be sure to check your local procedures to see what your organization prefers.
Verify that the chain has been added. You should see something like this:
donnie@ubuntu2:~$ sudo nft list table inet ubuntu_filter
[sudo] password for donnie:
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
}
}
donnie@ubuntu2:~$
That's great, but we still need some rules. Let's start with a connection tracking rule and a rule to open the Secure Shell port. Then, we'll verify that they were added:
sudo nft add rule inet ubuntu_filter input ct state established accept
sudo nft add rule inet ubuntu_filter input tcp dport 22 ct state new accept
donnie@ubuntu2:~$ sudo nft list table inet ubuntu_filter
table inet ubuntu_filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established accept
tcp dport ssh ct state new accept
}
}
donnie@ubuntu2:~
Okay, that looks good. You now have a basic, working firewall that allows Secure Shell connections. Well, except that just as we did in the Chapter 3, Securing Your Server with a Firewall - Part 1, we forgot to create a rule to allow the loopback adapter to accept packets. Since we want this rule to be at the top of the rules list, we'll use insert instead of add:
sudo nft insert rule inet ubuntu_filter input iif lo accept
donnie@ubuntu2:~$ sudo nft list table inet ubuntu_filter
table inet ubuntu_filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state established accept
tcp dport ssh ct state new accept
}
}
donnie@ubuntu2:~$
Now, we're all set. But what if we want to insert a rule at a specific location? For that, you'll need to use list with the -a option to see the rule handles:
donnie@ubuntu2:~$ sudo nft list table inet ubuntu_filter -a
table inet ubuntu_filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept # handle 4
ct state established accept # handle 2
tcp dport ssh ct state new accept # handle 3
}
}
donnie@ubuntu2:~$
As you can see, there's no real rhyme or reason to the way the handles are numbered. Let's say that we want to insert the rule about blocking certain IP addresses from accessing the Secure Shell port. We can see that the SSH accept rule is handle 3, so we'll need to insert our drop rule before it. This command will look like this:
sudo nft insert rule inet ubuntu_filter input position 3 tcp dport 22 ip saddr { 192.168.0.7, 192.168.0.10 } drop
donnie@ubuntu2:~$ sudo nft list table inet ubuntu_filter -a
table inet ubuntu_filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept # handle 4
ct state established accept # handle 2
tcp dport ssh ip saddr { 192.168.0.10, 192.168.0.7} drop # handle 6
tcp dport ssh ct state new accept # handle 3
}
}
donnie@ubuntu2:~$
So, to place the rule before the rule with the handle 3 label, we have to insert it at position 3. The new rule that we just inserted has the label handle 6. To delete a rule, we have to specify the rule's handle number:
sudo nft delete rule inet ubuntu_filter input handle 6
donnie@ubuntu2:~$ sudo nft list table inet ubuntu_filter -a
table inet ubuntu_filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept # handle 4
ct state established accept # handle 2
tcp dport ssh ct state new accept # handle 3
}
}
donnie@ubuntu2:~$
As is the case with iptables, everything you do from the command line will disappear once you reboot the machine. To make it permanent, let's redirect the output of the list subcommand to the nftables.conf configuration file (of course, we'll want to have made a backup copy of the already-existing file, in case we want to revert back to it):
sudo sh -c "nft list table inet ubuntu_filter > /etc/nftables.conf"
Due to a quirk in the Bash shell, we can't just redirect output to a file in the /etc directory in the normal manner, even when we use sudo. That's why I had to add the sh -c command, with the nft list command surrounded by double quotes. Also, note that the file has to be named nftables.conf because that's what the nftables systemd service looks for. Now, when we look at the file, we'll see that there are a couple of things that are missing:
table inet ubuntu_filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state established accept
tcp dport ssh ct state new accept
}
}
Those of you who are sharp-eyed will see that we're missing the flush rule and the shebang line to specify the shell that we want to interpret this script. Let's add them:
#!/usr/sbin/nft -f
flush ruleset
table inet ubuntu_filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state established accept
tcp dport ssh ct state new accept
}
}
Much better. Let's test this by loading the new configuration and observing the list output:
sudo systemctl reload nftables
donnie@ubuntu2:~$ sudo nft list table inet ubuntu_filter
table inet ubuntu_filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state established accept
tcp dport ssh ct state new accept
}
}
donnie@ubuntu2:~$
That's all there is to creating your own simple host firewall. Of course, running commands from the command line, rather than just creating a script file in your text editor, does make for a lot more typing. However, it does allow you to test your rules on the fly, as you create them. And creating your configuration in this manner and then redirecting the list output to your new configuration file relieves you of the burden of having to keep track of all of those curly brackets as you try to hand-edit the file.
It's also possible to take all of the nft commands that we just created and place them into a regular, old-fashioned Bash shell script. Trust me, though, you really don't want to do that. Just use the nft-native scripting format, as we've done here, and you'll have a script that performs better and is much more human-readable.