Boot fallback, flowtable devices that come and go, healthz, renames
The boot unit fails closed: if ruleset.nft doesn't load, it loads ruleset-fallback.nft (render.Fallback), saved beside it from the same configuration: nothing new in from external zones, established, the ICMPv6 IPv6 needs and DHCPv6 replies let in, the inside left alone and masqueraded, and the unit marked failed with a critical message. housecarld replaces it when it applies; status, the dashboard and /healthz say while it is in force. no_external_zone now also says the fallback closes everything without an external zone. The boot ruleset has no flowtable: nftables on Debian 13's kernel refuses one naming a device that doesn't exist yet (ppp0 before PPPoE), and with it the whole file. internal/linkwatch listens to rtnetlink link messages; housecarld applies again when a flowtable device appears or comes back as a new interface (by ifindex), under the engine's lock. docs/flowtable.md: what it does, when to use it and when not, how. GET /healthz reports firewall (applied, unconfigured, fallback, missing), changes on probation and the machine's warnings, with 503 unless in force, read at most every five seconds. Renaming a zone, set or service changes what names it in the same change (one history entry, one undo); dynamic sets keep their names. The importer keeps a chain's head in order: once a would-be built-in stays a rule, the ones after it do too, so the maintainer's ICMPv6 accepts stay behind the invalid drop as written. hc settings set --flowtable none empties the list. make test (nft 1.1.3) and make e2e (242 checks, with VLAN sub-interfaces, the fallback and the flowtable) pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
835a9ba2cc
commit
9d766b9823
38 files changed
+1335
-84
No files matched your search
@@ -355,6 +355,53 @@ sleep 1
|
||||
run "the log shows it" 0 H log
|
||||
contains "hc input #.* drop.*198.18.1.10:.*198.18.1.2:8070"
|
||||
|
||||
# ---- the flowtable, and devices that appear later
|
||||
|
||||
run "a flowtable device that doesn't exist yet" 0 H settings set --flowtable wan --flowtable enp2s0 --flowtable hcft0
|
||||
contains "flowtable_device_missing"
|
||||
run "the flowtable has the devices that exist" 0 R nft list flowtables
|
||||
contains "devices = { enp2s0, wan }"
|
||||
run "forwarded through the flowtable" 0 connect net $WAN4 8096
|
||||
contains "jellyfin"
|
||||
run "the device appears" 0 R ip link add hcft0 type veth peer name hcft1
|
||||
R ip link set hcft0 up
|
||||
sleep 4
|
||||
run "applied again with it" 0 R nft list flowtables
|
||||
contains "hcft0"
|
||||
run "logged" 0 R grep -c "interfaces changed: applied the ruleset again" /w/srv.log
|
||||
run "it comes back as a new interface" 0 R sh -c 'ip link del hcft0 && ip link add hcft0 type veth peer name hcft1 && ip link set hcft0 up'
|
||||
sleep 4
|
||||
run "applied again" 0 R sh -c 'grep -c "interfaces changed: applied the ruleset again" /w/srv.log | grep -qx 2'
|
||||
run "the flowtable still has it" 0 R nft list flowtables
|
||||
contains "hcft0"
|
||||
|
||||
# ---- VLAN sub-interfaces: tagged traffic between the router and the client
|
||||
|
||||
R sh -c 'ip link add link enp2s0 name enp2s0.30 type vlan id 30 && ip addr add 10.88.30.1/24 dev enp2s0.30 && ip link set enp2s0.30 up'
|
||||
docker exec ${P}-client sh -c 'ip link add link eth0 name eth0.30 type vlan id 30 && ip addr add 10.88.30.20/24 dev eth0.30 && ip link set eth0.30 up'
|
||||
run "a zone of a VLAN" 0 H zone add iot --interface enp2s0.30
|
||||
run "nothing from the VLAN to the API yet" 1 connect client 10.88.30.1 8070
|
||||
VR=$(H -o id rule add input --in iot --service tcp/8070 --comment "e2e vlan")
|
||||
run "a rule for the VLAN's zone" 0 test -n "$VR"
|
||||
run "the VLAN reaches the API" 0 connect client 10.88.30.1 8070
|
||||
run "the rule counted it" 0 H rule list input
|
||||
contains "e2e vlan"
|
||||
run "the flowtable on the VLAN" 0 H settings set --flowtable wan --flowtable enp2s0 --flowtable hcft0 --flowtable enp2s0.30
|
||||
run "attached" 0 R nft list flowtables
|
||||
contains "enp2s0.30"
|
||||
run "the VLAN rule goes" 0 H rule delete -y "$VR"
|
||||
run "the VLAN zone goes" 0 H zone delete -y iot
|
||||
R ip link del enp2s0.30
|
||||
|
||||
# ---- renaming a zone others refer to
|
||||
|
||||
run "rename the imported dhcp zone" 0 H zone update dhcp --name lan
|
||||
run "its rules follow" 0 H rule list input
|
||||
contains "lan"
|
||||
not_contains "dhcp "
|
||||
run "the ruleset is unchanged by it" 0 R nft list chain inet housecarl input
|
||||
contains 'iifname "enp2s0" udp dport 67'
|
||||
|
||||
# ---- restart and boot
|
||||
|
||||
docker exec ${P}-router sh -c 'kill $(pidof housecarld)'; sleep 1
|
||||
@@ -369,6 +416,39 @@ run "nothing forwarded after a flush" 1 connect net $WAN4 8096
|
||||
run "the boot unit's command restores it" 0 R nft -f /w/data/ruleset.nft
|
||||
run "forwarded again" 0 connect net $WAN4 8096
|
||||
contains "jellyfin"
|
||||
run "the boot ruleset has no flowtable (its devices may not exist yet)" 1 R grep -q flowtable /w/data/ruleset.nft
|
||||
run "healthz" 0 R curl -sf http://127.0.0.1:8070/healthz
|
||||
contains '"firewall":"applied"'
|
||||
|
||||
# ---- the boot fallback: what the boot unit runs, with the lab's paths
|
||||
|
||||
UNIT=$(sed -n 's/^ExecStart=//p' "$ROOT/deploy/systemd/housecarl-ruleset.service" | sed 's|/var/lib/housecarl|/w/data|g; s|/usr/sbin/nft|nft|g')
|
||||
run "the fallback was saved" 0 R test -s /w/data/ruleset-fallback.nft
|
||||
R cp /w/data/ruleset.nft /w/ruleset.good
|
||||
R sh -c 'echo "this is not nftables" >> /w/data/ruleset.nft'
|
||||
R nft flush ruleset
|
||||
run "the boot unit fails, and loads the fallback" 1 R sh -c "$UNIT"
|
||||
contains "the saved ruleset failed to load"
|
||||
run "the fallback is in force" 0 R nft list table inet housecarl
|
||||
contains "hc:fallback-input"
|
||||
run "nothing new gets in from the internet" 1 connect net $WAN4 8096
|
||||
run "not to the firewall either" 1 connect net $WAN4 8070
|
||||
: > "$WORK/net.log"
|
||||
run "the LAN still reaches the internet" 0 connect client $NET4 9000
|
||||
sleep 1
|
||||
run "masqueraded" 0 grep -q "$WAN4" "$WORK/net.log"
|
||||
run "healthz says so" 0 R sh -c 'curl -s -w " %{http_code}" http://127.0.0.1:8070/healthz'
|
||||
contains '"firewall":"fallback"'
|
||||
contains " 503"
|
||||
run "status says so" 0 H status
|
||||
contains "NOT in force: the boot fallback"
|
||||
docker exec ${P}-router sh -c 'kill $(pidof housecarld)'; sleep 1
|
||||
run "housecarld starts" 0 start
|
||||
run "and puts the real ruleset back" 0 connect net $WAN4 8096
|
||||
contains "jellyfin"
|
||||
run "and saves a good boot ruleset" 0 R nft -c -f /w/data/ruleset.nft
|
||||
run "healthz is happy again" 0 R curl -sf http://127.0.0.1:8070/healthz
|
||||
contains '"firewall":"applied"'
|
||||
|
||||
# ---- backup
|
||||
|
||||
|
||||
Reference in new issue
Block a user