pythonjsonnetfilternftables

Adding a policy to my custom chain in nftables via python libnftables is failing, can you tell me why?


I'm trying to create a table, chain and ruleset for nft in python to act as a whitelist.

So I need a chain with policy of "drop" to drop all outbound packets that do not match approved destinations set out in the rules of that chain.

I want to use a custom table to keep maintenance easier and so that fail2ban and other utilities can operate alongside this whitelist (which will be for packets incoming from one specific interface only ).

I'm using python and the nft json library, I can see my table and chain and rule being created, but no policy is listed in the ruleset output after running my program, so I assume it will default to "accept".

I am using a json string to set up a new table and a chain within, currently it just has a very simple rule for test purposes. It passes validation by libnftables, but when I list the ruleset after running my python code I see the table, chain and rules, but no policy applied at all.

here's the setup string:

 {'nftables':
    [{'add': {'table': {'family': 'ip', 'name': 'O365'}}}, 
     {'add': {'chain': {'family': 'ip', 'table': 'O365', 'name': 'O365WhiteList', 'policy': 'drop'}}}, 
     {'add': {'rule': {'family': 'ip', 'table': 'O365', 'chain': 'O365WhiteList', 
       'expr': [{'match': {'op': '==', 'left': {'payload': {'protocol': 'tcp', 'field': 'dport'}}, 'right': 22}}, {'accept': None}
               ]
     }}}]}

And here's the ruleset output:

# Warning: table ip filter is managed by iptables-nft, do not touch!
table ip filter {
        chain f2b-sshd {
                counter packets 92105 bytes 13096562 return
        }

        chain INPUT {
                type filter hook input priority filter; policy accept;
                meta l4proto tcp tcp dport 22 counter packets 132201 bytes 15881220 jump f2b-sshd
        }
}
# Warning: table ip nat is managed by iptables-nft, do not touch!
table ip nat {
        chain POSTROUTING {
                type nat hook postrouting priority srcnat; policy accept;
                oifname "ens19" counter packets 983 bytes 70843 masquerade
        }
}
table ip O365 {
        chain O365WhiteList {
                tcp dport 22 accept
        }
}

As you can see, I'm not actually hooking into my table yet, I just want to see it created properly first. The table, chain, and rule are there, but no policy is given.

Any ideas? The man page for libnftables-json isn't helpful as far as I can see, listing "policy" only as STRING.

Below is the full code for the above:

import json
import nftables

#some objects to be turned into json for nftables to apply as commands

delTable =  {"nftables": [{ "delete": { "table": { "family": "ip", "name": "O365" }}}]}


setupTablesCmds= """
  { "nftables": [
    { "add": { "table": { "family": "ip", "name": "O365" }}},
    { "add": { "chain": {
        "family": "ip",
        "table": "O365",
        "name": "O365WhiteList",
        "policy": "drop"
    }}},
    { "add": { "rule": {
        "family": "ip",
        "table": "O365",
        "chain": "O365WhiteList",
        "expr": [
            { "match": {
                "op": "==",
                "left": { "payload": {
                    "protocol": "tcp",
                    "field": "dport"
                }},
                "right": 22
            }},
            { "accept": null }
        ]
    }}}
  ]}
 """


nft = nftables.Nftables()
try:
    nft.json_validate(json.loads(setupTablesCmds))
except Exception as e:
    print(f"ERROR: failed validating initial setup json schema: {e}")
    exit(1)
print(" base config data passed validation, yay!" )
print()
print("Removing old O365 table")

rc, output, error = nft.json_cmd(delTable)
if rc != 0:
  # error here is probably  because table doesn't exist, which is fine, so no exit on error for now
  print(f"ERROR: running json cmd: {error}")

print(" setting up new O365 table " )
print( "json commands = :", json.loads(setupTablesCmds))

rc, output, error = nft.json_cmd(json.loads(setupTablesCmds))
if rc != 0:
  print(f"ERROR: running json cmd: {error}")
  exit(1)

if len(output) != 0:
   print(f"WARNING: output: {output}")

print("  Base config applied ok, O365 table created" )


Solution

  • I got an answer from the helpful folks at the netfilter-user mailing list.

    Turns out the docs were wrong on the wiki ( they have been corrected now ), policy can only be applied to base chains with hooks. The solution to my problem is to put an explicit drop rule at the end of my regular chain.

    Annoyingly, the python library seems to accept the policy in the regular chain creation command without throwing any error. But when using the shell nft command to attempt the same thing I get an "operation not supported" error returned when attempting to apply a policy to a regular chain.