I'd like to use nftables to set up a firewall in the following way:
- a basic table with a default-deny policy
- custom tables (which I'd add per service running on a machine) which allow only relevant ports
Here is an example of what I'd ideally like:
# base table, set on every machine, used to default-deny all incoming traffic table inet basic delete table inet basic table inet basic { chain input { # see example below about the priority type filter hook input priority 0 policy drop } } # table for rules required by HTTP servers, created only when needed on HTTP servers table inet http_server delete table inet http_server table inet http_server { chain input { # see example below about the priority type filter hook input priority 1 policy drop # allow http and https traffic tcp dport 80 accept tcp dport 443 accept } } Note that the reason I'm using separate tables rather than appending rules to chains is for the isolation and atomicity tables provide: a new table doesn't have to be aware of other tables, and can be deleted in one go. This gives me a nice basis for building a declarative firewall, rather than needing to imperatively add/delete rules in specific chains (I'd always delete and recreate a whole table defined in one file when I change something).
Unfortunately however, it appears that what I'm trying to achieve is impossible with nftable's design. If I understand correctly, chains in separate tables will be evaluated in order of their hook priority, with an accept rule terminating the current chain, but a drop terminating the packet evaluation globally. Basically, for a packet to be accepted, I need an accept in every chain, but one drop in any chain, including default policies is enough to stop it.
Regardless of the priority of tables, it appears that a drop verdict will always be reached, even for packets that I'd actually want to accept in specific configuration. E.g. assuming a configuration as specified above, and a packet arriving on port 80 (which I'd want to accept), this is what happens:
if priority table basic < priority table http_server:
table basic -> no match -> default drop -> *end evaluation, verdict: drop*if priority table http_server < priority table basic:
table http -> match -> accept -> next table basic -> no match -> default drop -> *end evaluation, verdict: drop*
Hence, it appears I cannot have a separate table with a chain with a default deny policy, and other tables which chains which selectively accept packets. So I am left with either a default-accept firewall, or I need to manage all rules imperatively within one hook, just like iptables.
Can anyone confirm my assumption or show me a declarative alternative?
I'm also curious as to why nftables was designed in a way that an accept in a lower priority chain does not override default policies in a higher priority chain. I can understand why you'd want a drop in any chain to prevent accepts, but not why that should also happen with the default policy.
Thanks
I'm also curious as to why nftables was designed in a wayBecause that's what would happen when you have layers of filters in reality? For something to get through them, it needs to be able to get through every layer, and as long as one layer blocks it, it goes no further.