Salt Phase 3: nftables firewall with default deny inbound (#5) #6

Merged
forgejo_admin merged 2 commits from 5-salt-phase-3-nftables-firewall-default-d into main 2026-03-01 02:18:06 +00:00

Summary

  • Add Salt-managed nftables firewall with default deny inbound policy (Phase 3 of salt host management plan)
  • Firewall rules are data-driven via pillar — change pillar data, run highstate, firewall updates
  • Tailscale safety guaranteed: outbound never restricted, tailscale0 allows all traffic

Changes

  • salt/pillar/firewall.sls: New pillar file with structured firewall rule definitions (allowed interfaces, CIDRs, per-port rules, conntrack states)
  • salt/states/firewall/init.sls: New state — installs nftables, renders config from Jinja template, enables service with watch-based reload on config change
  • salt/states/firewall/nftables.conf.j2: New Jinja template rendering nftables.conf from pillar data (default drop inbound, accept outbound, drop forward)
  • salt/pillar/top.sls: Added firewall pillar assignment to archbox
  • salt/states/top.sls: Added firewall state assignment to archbox

Firewall policy summary:

Rule Effect
Default inbound DROP
Default outbound ACCEPT (Tailscale depends on outbound UDP)
Default forward DROP
tailscale0 interface Allow ALL
lo interface Allow ALL
10.42.0.0/16 Allow ALL (flannel pod CIDR)
10.43.0.0/16 Allow ALL (k8s service CIDR)
10.0.0.0/24 -> port 22/tcp Allow (SSH from LAN)
established,related Allow (conntrack)
ICMP echo-request Allow (ping)
Forward: flannel/k8s CIDRs Allow (pod-to-pod, pod-to-service)

Test Plan

  • salt-call state.apply firewall test=True — dry run shows expected changes
  • nft -c -f /etc/nftables.conf — validate rendered config syntax after rendering
  • Apply with SSH revert timer: sudo nft -f /etc/nftables.conf && sleep 60 && sudo nft flush ruleset
  • Verify nft list ruleset matches pillar data
  • Verify k3s pods can communicate (flannel/service CIDRs)
  • Verify Tailscale funnels still work
  • Verify LAN SSH works (port 22 from 10.0.0.0/24)
  • Verify LAN cannot reach k8s API (port 6443 not in port_rules)
  • Modify rules manually, run highstate, verify Salt reverts them

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • issue-pal-e-platform-salt-phase-3-nftables — the issue this PR addresses
  • plan-2026-02-26-salt-host-management — Phase 3
  • host-inventory-archbox — current host state with security gaps identified
  • Blocks: plan-2026-02-26-network-security-hardening Phase 2
## Summary - Add Salt-managed nftables firewall with default deny inbound policy (Phase 3 of salt host management plan) - Firewall rules are data-driven via pillar — change pillar data, run highstate, firewall updates - Tailscale safety guaranteed: outbound never restricted, tailscale0 allows all traffic ## Changes - `salt/pillar/firewall.sls`: New pillar file with structured firewall rule definitions (allowed interfaces, CIDRs, per-port rules, conntrack states) - `salt/states/firewall/init.sls`: New state — installs nftables, renders config from Jinja template, enables service with watch-based reload on config change - `salt/states/firewall/nftables.conf.j2`: New Jinja template rendering nftables.conf from pillar data (default drop inbound, accept outbound, drop forward) - `salt/pillar/top.sls`: Added `firewall` pillar assignment to archbox - `salt/states/top.sls`: Added `firewall` state assignment to archbox **Firewall policy summary:** | Rule | Effect | |------|--------| | Default inbound | **DROP** | | Default outbound | **ACCEPT** (Tailscale depends on outbound UDP) | | Default forward | **DROP** | | `tailscale0` interface | Allow ALL | | `lo` interface | Allow ALL | | `10.42.0.0/16` | Allow ALL (flannel pod CIDR) | | `10.43.0.0/16` | Allow ALL (k8s service CIDR) | | `10.0.0.0/24` -> port 22/tcp | Allow (SSH from LAN) | | `established,related` | Allow (conntrack) | | ICMP echo-request | Allow (ping) | | Forward: flannel/k8s CIDRs | Allow (pod-to-pod, pod-to-service) | ## Test Plan - [ ] `salt-call state.apply firewall test=True` — dry run shows expected changes - [ ] `nft -c -f /etc/nftables.conf` — validate rendered config syntax after rendering - [ ] Apply with SSH revert timer: `sudo nft -f /etc/nftables.conf && sleep 60 && sudo nft flush ruleset` - [ ] Verify `nft list ruleset` matches pillar data - [ ] Verify k3s pods can communicate (flannel/service CIDRs) - [ ] Verify Tailscale funnels still work - [ ] Verify LAN SSH works (port 22 from 10.0.0.0/24) - [ ] Verify LAN cannot reach k8s API (port 6443 not in port_rules) - [ ] Modify rules manually, run highstate, verify Salt reverts them ## Review Checklist - [ ] Passed automated review-fix loop - [ ] No secrets committed - [ ] No unnecessary file changes - [ ] Commit messages are descriptive ## Related Notes - `issue-pal-e-platform-salt-phase-3-nftables` — the issue this PR addresses - `plan-2026-02-26-salt-host-management` — Phase 3 - `host-inventory-archbox` — current host state with security gaps identified - Blocks: `plan-2026-02-26-network-security-hardening` Phase 2
Data-driven firewall managed by Salt pillar + states:
- Pillar defines rules (interfaces, CIDRs, ports, conntrack)
- Jinja template renders nftables.conf from pillar data
- Salt state installs nftables, renders config, enables service
- Config changes trigger service reload via watch requisite

Policy: accept outbound, drop inbound, drop forward.
Tailscale safety: outbound never restricted, tailscale0 fully allowed.
k3s safety: flannel (10.42/16) and service (10.43/16) CIDRs allowed.
LAN SSH: port 22 open from 10.0.0.0/24.

Closes #5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pods reaching external services go through masquerade/NAT. The return
traffic needs conntrack (established/related) in the forward chain to
be properly accepted. Also drops invalid forwarded packets early.

Found during review-fix loop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Review-fix round 1: Added conntrack (ct state established,related accept + ct state invalid drop) to the forward chain.

Without this, return traffic for connections initiated by pods through masquerade/NAT could be dropped. The input chain already had conntrack, but the forward chain was relying solely on CIDR-based rules. While the CIDR rules cover most k3s forwarding scenarios, conntrack in the forward chain is standard best practice and handles edge cases more robustly.

**Review-fix round 1:** Added conntrack (`ct state established,related accept` + `ct state invalid drop`) to the forward chain. Without this, return traffic for connections initiated by pods through masquerade/NAT could be dropped. The input chain already had conntrack, but the forward chain was relying solely on CIDR-based rules. While the CIDR rules cover most k3s forwarding scenarios, conntrack in the forward chain is standard best practice and handles edge cases more robustly.
forgejo_admin deleted branch 5-salt-phase-3-nftables-firewall-default-d 2026-03-01 02:18:06 +00:00
Sign in to join this conversation.
No description provided.