nftables simple host config
Firewalld is a recommended default, but in some ways a bit too basic
So I’ve been trying to get to grips with nftables, because I had an issue with some host firewall setup.
We firewall every machine by default, in addition to router firewalling, and … it’s become clear in our rollout of Linux that the ‘recommended’ answer of firewalld is just not really up to the job.
So here’s a very basic ‘how to’ guide to start from scratch with a fairly typical scenario:
A standalone host
Outbound it generally permitted
Inbound is restricted by firewall - selectively to certain hosts or subnets in some cases.
Your ‘starter for 10’ config with nftables looks like this:
# Load this by calling 'nft -f /etc/nftables/main.nft'.
# drop any existing nftables ruleset
flush ruleset
# a common table for both IPv4 and IPv6
table inet my_conf {
Note: This allows _any_ source or destination on these protocols
set allowed_protocols {
type inet_proto
elements = { icmp, icmpv6 }
}
# interfaces to accept any traffic on
set allowed_interfaces {
type ifname
elements = { "lo" }
}
# services to allow
set allowed_tcp_dports {
type inet_service
elements = { ssh }
}
# this chain gathers all accept conditions
chain allow_general {
ct state established,related accept
meta l4proto @allowed_protocols accept
iifname @allowed_interfaces accept
tcp dport @allowed_tcp_dports accept
}
# place to insert new rules
chain included_rules {
}
# base-chain for traffic to this host
chain INPUT {
type filter hook input priority filter + 20
policy accept
jump allow_general
jump included_rules
# Sometimes you'll want 'drop' here instead
# But for internal use, drop causes a few too many nuisances.
reject with icmpx type port-unreachable
}
} # table definition
#include "/etc/nftables/my_conf/definitions.nft"
#include "/etc/nftables/my_conf/example_simple_rule.nft"
#include "/etc/nftables/my_conf/example_hostgroup_rule.nft"
So this is a template config, that doesn’t do anything massively clever, but is a reasonable ‘starting point’.
It isn’t stunningly secure for something on the big bad internet - it allows anyone to connect via ssh from anywhere, for example. Which is fine for an internal network, and I like building that into the core config just so there’s no accidentally firewalling yourself out!
It also permits ‘anything on loopback’ interface, which is usually 'fine’ as far as I’m concerned, as well as any icmp.
ct state established,related accept
”established” means existing sessions are permitted to continue.
”related” is sort of similar, but is mostly to cope with things like ftp (which does some slightly odd things with firewalls, so it’s helpful - I won’t go into detail, but you can look it up on the netfilter wiki).
That gives you a fairly basic ‘host firewall’ setup that works for most use cases.
Now, if you’re sharp eyed, you’ll see there’s a chain called “included rules” in there, which is currently empty.
This is my preferred way of handling “extra” rules.
E.g.:
#configure a chain for this 'rule’
create chain inet my_conf in_web
add counter inet my_conf in_web
add rule inet my_conf in_web dport { 443, 80, 8080 } counter name in_web accept
add rule inet my_conf included_rules jump in_web
You can run these as ‘nft scripts’ if you like - stick:#!/usr/sbin/nft -f
at the top.
This is similar to the commands used to check and load the config:
Process a file in ‘check mode’:
nft -c -f <filename>
Run an nft file (and modify the running firewall state):nft -f /etc/nftables/main.nft
But see those commented out “include” lines in the initial config? Well, that’s how I add this. Save the ‘incoming_web’ stuff in /etc/nfttables/incoming_web.nft then use:#include “/etc/nftables/my_conf/incoming_web.nft”
That way, it’ll modify our policy - which we can see by running ‘nft list ruleset
’ such that:
# place to insert new rules
chain included_rules {
jump in_web
}
And there’s be another chain defined:
counter in_web {
packets 0 bytes 0
}
chain in_web {
tcp dport { 443, 80, 8080 } counter name "in_web" accept
}
And that’s … it really. You’ve a rule now, that you can selectively include, that’ll enable incoming traffic on those ports.
If you want to restrict to source network (the other most common scenario IMO) you can use:
tcp dport 666 saddr 192.168.0.0/24
And the thing I feel I should definitely mention is the ability to define and use macros in these. Note that “include /etc/nfttables/my_conf/definitions.nft”? Well, that - in my case - has a bunch of groups:
define admin_workstations = { 192.168.0.99, 192.168.0.100 }
define usual_http_ports = { 443, 80 }
define site_a = { 10.2.0.0/16 }
define site_b = { 10.3.0.0/16 }
etc.
So you can do things that allow particular hosts to connect on trusted ports by doing something like:
define es_cluster = {
10.22.33.44,
10.22.33.45,
10.22.33.46,
}
create chain inet my_conf elasticsearch
add counter inet my_conf es_mgmt
add counter inet my_conf es_internal
#If this renders badly, it's a single line per 'add rule'
#Does assume you've included the above definitions first!
add rule inet my_conf elasticsearch dport 9200 saddr { $site_a, $site_b } counter name es_mgmt accept
add rule inet my_conf elasticsearch dport 9300 saddr $es_cluster counter name es_internal accept
add rule inet my_conf included_rules jump elasticsearch
There is, of course, a lot more to nftables - it’s extremely comprehensive - but this is hopefully a way to get started with the basics, without getting utterly overwhelmed. (Seriously, check out ‘sets’ and how you can add things with implicit timeouts!)
Note - you can dump a firewalld managed policy as your startpoint, but… well, you’ll quickly see I think why I didn’t use that.
The reason I’m doing things this way though, is we manage our hosts using ansible, and generating ‘rule files’ using ansible templating is actually really straightforward, and allows us to do ‘everything’ we need to, in a centrally managed way.
Finally, to use all this persistently, you will need to stop firewalld and enable nftables.
systemctl stop firewalld
systemctl disable firewalld
systemctl mask firewalld
systemctl enable nftables
systemctl start nftables
Careful with the last one if you’re connected remotely - if your config doesn’t load, you might get locked out! (but you did check it’s valid with nft -c -f <file>
first right?
Which of course should be used with caution, because turning off your firewall and installing a misconfigured one is a huge potential security vulnerability. But personally I find that firewalld for anything other than ‘trivially simple’ is working in counter-intuitive ways.
You can if you need a bit more flexibility, have multiple config files, with different table names etc. if you routinely have a different ‘home network’ vs. ‘coffee shop network’ config, and dynamically load them with ‘nft -f’. All works in the same way - the only thing special about /etc/nftables/main.nft is that it’s the ‘default’ in the systemd configuration for the nftables service.