Open lkubb opened 2 years ago
Thank you for reporting. I noticed the hex weirdness in addition to https://github.com/saltstack/salt/pull/61975. following 👀
I ran into this today.
This happens because modules/nftables.py
in check()
uses --numeric
argument to nft
which converts many things (including connstate) to numeric.
However, removing the --numeric
arguments to nft
command does not solve this issue because when salt builds the command it encapsulates connstate and port in {}
, e.g. { new }
or {established,related}
, but when salt runs nft
to print the rules (so that it can search for exact match), the nftables removes the {}
for single connstate or port.
Consider a state element
allow_dns_udp:
nftables.append:
- save: True
- table: filter
- chain: OUTPUT
- jump: accept
- match: state
- connstate: new
- proto: udp
- dport: 53
- destination: 10.1.1.1
Thus if salt built the following rule
ct state { new } ip daddr 10.1.1.1 udp dport { 53 } accept
when it echoes the existing rules (without --numeric
), it gets
ct state new ip daddr 10.1.1.1 udp dport 53 accept
Hence it can never match the rule exactly. The rule matching logic needs to probably use regular expressions for this issue to be fixed.
To add the one thing missing in here from the duplicate issue I had filed: Due to this strange check for string booleaness:
if full in ["True", "true"]:
It's almost certain that nftables.append in line 261 will not get the string it expects.
I tried a few ways to solve this problem last night and I believe the correct way to do this is to abandon the rule matching that's currently implemented in salt.modules.nftables
and instead rely on the JSON renderer (e.g. nft -j list ruleset
) as that appears to produce a reliable representation of any rule.
Of course, nftables being nftables, there is no way to use nft's parser and renderer to convert an incoming new rule into JSON. nft
does have the -e
"echo" parameter which will output JSON, but it doesn't have a no-op "dry-run" parameter.
For example nft -ej add rule filter input tcp dport { 22 } accept
will output:
{
"nftables": [
{
"add": {
"rule": {
"family": "ip6",
"table": "filter",
"chain": "input",
"handle": 18,
"expr": [
{
"match": {
"op": "==",
"left": {
"payload": {
"protocol": "tcp",
"field": "dport"
}
},
"right": 22
}
},
{
"accept": null
}
]
}
}
}
]
}
So my current theory is that if we can create the above rendering of a rule reliably, then checking for existing rules can be done reliably. The current implementation is otherwise easily derailed, even when you remove the --numeric
mismatch explained above. For example, already a simple typo like:
my-rule:
nftables.append:
- table: filter
- family: inet
- chain: output
- jump: accept
- proto: icmp
- icmp-type: echo-reply, destination-unreachable
# ^^ mind the extra space
...
will never match in the current system. An alternative approach to solving this problem may be to sanitize all comma-separated lists in a rule to ensure that they have a single space etc. and are only wrapped in {}
when they're longer than one element, but that feels brittle to me.
Side note: There's also the python-nftables
utility (https://github.com/aborrero/python-nftables-tutorial), but it does not help with abstracting away the difficulties in the above approach.
A couple of months ago I started implementing a utility for doing the above, first with dataclasses representing the different entities of the scheme (I took https://github.com/mullvad/nftnl-rs / https://github.com/alexforster/nftables-json as a reference). Afair the mentioned issue (conversion between a rule def to its json implementation) caused me to stop it because it was too much work.
Edit: My workaround for now is to render the complete configuration: https://github.com/lkubb/salt-nftables-formula/blob/master/nftables/files/default/nftables.conf.j2
Description
nftables.append
keeps appending rules that haveconnstate
set.Edit: This also applies for
if/of
with a different cause (nft list
does not includemeta
beforeiifname/oifname
).Edit2: This also applies for
jump: dnat
withto-destination
set (nft list
printsdnat to <target>
, notdnat <target>
).Setup Salt minion on Debian 11 VM
Steps to Reproduce the behavior
Expected behavior Be idempotent.
Screenshots
Versions Report
salt --versions-report
(Provided by running salt --versions-report. Please also mention any differences in master/minion versions.) ```yaml Salt Version: Salt: 3004.2 Dependency Versions: cffi: Not Installed cherrypy: Not Installed dateutil: 2.8.1 docker-py: Not Installed gitdb: Not Installed gitpython: Not Installed Jinja2: 2.11.3 libgit2: Not Installed M2Crypto: Not Installed Mako: Not Installed msgpack: 1.0.0 msgpack-pure: Not Installed mysql-python: Not Installed pycparser: Not Installed pycrypto: Not Installed pycryptodome: 3.9.7 pygit2: Not Installed Python: 3.9.2 (default, Feb 28 2021, 17:03:44) python-gnupg: Not Installed PyYAML: 5.3.1 PyZMQ: 20.0.0 smmap: Not Installed timelib: Not Installed Tornado: 4.5.3 ZMQ: 4.3.4 System Versions: dist: debian 11 bullseye locale: utf-8 machine: x86_64 release: 5.10.0-13-amd64 system: Linux version: Debian GNU/Linux 11 bullseye ```Additional context This behavior is caused by
nftables.check
execution module not recognizing the rule correctly. It lists the rules with the--numeric
switch, which translates the connection states to hex values, but searches for them using their string representation.Example output:
Workaround possible by specifying the values in hex (which should break the analogy to iptables afaik):