feat: fix nftables boot ordering — start after tailscaled #81
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/pal-e-platform!81
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "80-feat-fix-nftables-boot-ordering-start-af"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
tailscale0didn't exist at service start timeINPUT ACCEPT) since March 4 due to this ordering raceChanges
salt/states/firewall/init.sls— 3 new states:nftables-after-tailscale: deploys/etc/systemd/system/nftables.service.d/after-tailscale.confwithAfter=tailscaled.service+Wants=tailscaled.servicenftables-daemon-reload:cmd.waitrunssystemctl daemon-reloadwhen drop-in changesnftables-serviceandnftables-reloadboth requirenftables-daemon-reloadRoot Cause
nftables.service starts at
sysinit.target(very early). tailscaled.service starts afterNetworkManager.service(later). The interfacetailscale0is created by tailscaled — it doesn't exist when nftables tries to load rules.Validated
salt-call state.apply firewall— 6 succeeded, 0 failuressystemctl daemon-reloadexecutedTest Plan
salt-call state.apply firewallshows 0 failures/etc/systemd/system/nftables.service.d/after-tailscale.confsystemctl status nftablesshows successnft list rulesetshows rulesNote
The actual rule loading + verification (revert timer pattern) is a separate manual step — Phase 8c deliverable 2. This PR only fixes the boot ordering so rules can load successfully.
Closes #80
Review Checklist
salt-call state.apply firewallRelated
plan-pal-e-platform— Phase 8c (Host Firewall Verification)bug-nftables-service-running-oneshot— original bug (service.running fix, resolved)phase-pal-e-platform-network-security— Phase 8 parentTofu Plan Output
PR #81 Review
DOMAIN REVIEW
Tech stack: Salt (SaltStack state files), systemd, nftables
This PR adds a systemd drop-in override via Salt to fix an nftables boot ordering race condition. The
tailscale0interface is referenced innftables.conf.j2(line 32-33, via pillarallowed_interfaces) but is created bytailscaled.serviceat startup. Without the drop-in,nftvalidates interface names at load time and fails becausetailscale0does not exist yet.Salt state analysis:
nftables-after-tailscale(file.managed) -- Correctly deploys a systemd drop-in at/etc/systemd/system/nftables.service.d/after-tailscale.confwithAfter=tailscaled.serviceandWants=tailscaled.service. Themakedirs: trueis correct since the.ddirectory likely does not exist yet. Permissions (root:root, 0644) are appropriate for systemd unit drop-ins.nftables-daemon-reload(cmd.wait) -- Usescmd.waitwith awatchon the drop-in file. This is the correct Salt pattern:cmd.waitonly fires when the watched state reports changes, sosystemctl daemon-reloadruns only when the drop-in is created or modified. Idempotent on subsequent runs.Requisite wiring -- Both
nftables-serviceandnftables-reloadnowrequirenftables-daemon-reload. This ensures the daemon-reload has completed (or been skipped as no-op) before the service is enabled or rules are reloaded. The ordering chain is:nftables-package->nftables-after-tailscale->nftables-daemon-reload->nftables-service->nftables-reload. This is correct.systemd analysis:
After=tailscaled.service-- Correct. Ensures nftables starts after tailscaled, sotailscale0exists.Wants=tailscaled.service-- Appropriate soft dependency. If tailscaled is not installed (hypothetical), nftables still starts.Requires=would be too strong here since the host could conceivably run without Tailscale in a degraded scenario./etc/systemd/system/nftables.service.d/after-tailscale.conffollows systemd conventions correctly.Root cause verification:
Confirmed.
salt/pillar/firewall.slsline 17 liststailscale0inallowed_interfaces, and the Jinja template atsalt/states/firewall/nftables.conf.j2line 32-33 rendersiif "tailscale0" accept. The nft binary validates interface names at load time, so the interface must exist before rules are loaded.BLOCKERS
None.
salt-call state.apply firewallas documented in the PR body. The Test Plan includes both pre-reboot (Salt apply) and post-reboot (systemctl status + nft list ruleset) verification steps. Two post-reboot items remain unchecked, which is expected -- they require the operator to reboot and verify. This is appropriate for host-level systemd changes.require, but this is requisite wiring, not duplicated logic.NITS
Comment header style -- The existing states use
# ---section dividers consistently. The new states follow this convention. No issue.Drop-in file trailing newline -- The
contents: |block produces a trailing newline afterWants=tailscaled.service, which is correct for systemd unit files. No issue.Consider
Requires=vsWants=--Wants=is the softer dependency, meaning nftables will still attempt to start even if tailscaled fails. Given that nftables rules referencetailscale0, if tailscaled fails, nftables will also fail (same error as before the fix). ARequires=would make the failure explicit rather than letting nft fail with a confusing interface error. However,Wants=is the conventional choice for drop-ins and avoids creating a hard dependency that could cascade failures. Either is defensible. Non-blocking.SOP COMPLIANCE
80-feat-fix-nftables-boot-ordering-start-afreferences #80)plan-pal-e-platform-- Phase 8c)Closes #80present in PR bodyfeat: fix nftables boot ordering -- start after tailscaled)PROCESS OBSERVATIONS
INPUT ACCEPTwas the effective policy. Fixing this restores the intendedpolicy dropfirewall posture.systemctl status nftablesandnft list rulesetbefore considering Phase 8c fully complete.VERDICT: APPROVED