WIP: feat(router): isolated guest VLAN (hardened) #559

Draft
lytedev wants to merge 1 commit from router-guest-vlan-hardened into main
Owner

Draft. Isolated, internet-only guest VLAN (VLAN 199, 192.168.199.0/24) for hosting guests, enforced on the NixOS router because the network has no UniFi gateway (controller only manages APs).

This is a hardened, fresh-from-main revision of #554 — same feature, but with the firewall isolation holes from a security audit fixed. Full rationale is in the commit message; summary:

Audit-fixed holes (vs #554):

  • Guest → router was reachable. Input-chain service accepts (SSH 2201, DNS 53, NAT ports) carry no iifname, so they matched guest traffic before the chain policy drop. Now an explicit iifname guest … drop is placed before those accepts — only DHCP is allowed back to the router.
  • Guest → LAN over IPv6 relied on the guest lacking a route, not a rule (the interface-agnostic icmpv6 … accept in forward was a latent hole). Now an explicit iifname guest forward drop kills guest→LAN for v4 and v6, future-proof against the IPv6-parity TODO.
  • dnsmasq resolver leak (guests resolving internal split-horizon names) closed by the same input drop; misleading comment corrected.

Validation: nix build .#nixosConfigurations.router…toplevel passes (incl. nftables checkRuleset); rendered ruleset confirms guest drops precede the service accepts in both chains.

Out of scope (follow-ups): net.ipv4.conf.lan.rp_filter=1 (currently 0); tightening the WAN-facing dport 53 accept.

Supersedes #554 (can be closed once this is reviewed). Not yet deployed; deploy + the UniFi guest-SSID walkthrough (VLAN 199 + client isolation) still pending.

Draft. Isolated, internet-only guest VLAN (VLAN 199, `192.168.199.0/24`) for hosting guests, enforced on the NixOS router because the network has no UniFi gateway (controller only manages APs). This is a hardened, fresh-from-`main` revision of #554 — same feature, but with the firewall isolation holes from a security audit fixed. Full rationale is in the commit message; summary: **Audit-fixed holes (vs #554):** - **Guest → router was reachable.** Input-chain service accepts (SSH 2201, DNS 53, NAT ports) carry no `iifname`, so they matched guest traffic before the chain policy drop. Now an explicit `iifname guest … drop` is placed **before** those accepts — only DHCP is allowed back to the router. - **Guest → LAN over IPv6** relied on the guest lacking a route, not a rule (the interface-agnostic `icmpv6 … accept` in forward was a latent hole). Now an explicit `iifname guest` forward drop kills guest→LAN for v4 **and** v6, future-proof against the IPv6-parity TODO. - **dnsmasq resolver leak** (guests resolving internal split-horizon names) closed by the same input drop; misleading comment corrected. **Validation:** `nix build .#nixosConfigurations.router…toplevel` passes (incl. nftables `checkRuleset`); rendered ruleset confirms guest drops precede the service accepts in both chains. **Out of scope (follow-ups):** `net.ipv4.conf.lan.rp_filter=1` (currently 0); tightening the WAN-facing `dport 53` accept. Supersedes #554 (can be closed once this is reviewed). Not yet deployed; deploy + the UniFi guest-SSID walkthrough (VLAN 199 + client isolation) still pending.
feat(router): isolated, internet-only guest VLAN (hardened)
All checks were successful
/ check-format (push) Successful in 12s
/ build (push) Successful in 7m3s
32b0f42298
Add an opt-in `lyte.router.guest` block delivering an internet-only guest
network over a tagged VLAN (default VLAN 199, 192.168.199.0/24), for hosting
guests (e.g. a home exchange) with no access to the LAN or the router.
Isolation is enforced on this router because the network has no UniFi gateway
- the self-hosted controller only manages APs, which merely tag the guest SSID
onto the VLAN.

Implementation:
- systemd-networkd VLAN netdev tagged onto the LAN trunk; IPv4-only guest
  network (inline notes document adding IPv6 parity later).
- dnsmasq guest DHCP scope matched by the interface tag; guests handed upstream
  DNS via dhcp-option.
- nftables: guest -> WAN accept, plus explicit guest drops (see below).
- LAN tightened from the over-broad /16 default to /23 (512 addresses); DHCP
  already hands out /24 so existing clients are unaffected, and this removes the
  latent overlap with 192.168.199.0/24.

Incorporates a security audit of the firewall (vs. the initial cut in #554),
fixing real isolation holes:
- Guest -> router was NOT blocked: the input chain's service accepts (SSH 2201,
  DNS 53, NAT ports) carry no iifname, so they matched guest traffic before the
  chain policy drop. An explicit `iifname guest ... drop` is now interpolated
  BEFORE those accepts, allowing only DHCP back to the router.
- Guest -> LAN over IPv6 relied on the guest lacking a route rather than a rule,
  and the interface-agnostic `icmpv6 ... accept` in forward was a latent hole.
  An explicit `iifname guest` forward drop now guarantees guest -> LAN is dead
  for v4 and v6 and stays correct if the IPv6-parity TODO is enabled.
- The dnsmasq-resolver leak (guests resolving internal split-horizon names) is
  closed by the same input-chain drop; the misleading comment claiming dnsmasq
  doesn't bind the guest interface is corrected.

Validated: `nix build .#nixosConfigurations.router...toplevel` passes including
the nftables checkRuleset; rendered ruleset confirms guest drops precede the
service accepts in both chains.

Follow-ups (out of scope to keep this PR focused): set
net.ipv4.conf.lan.rp_filter=1 (currently 0) as anti-spoof hardening, and scope
the WAN-facing dport 53 accept.
All checks were successful
/ check-format (push) Successful in 12s
Required
Details
/ build (push) Successful in 7m3s
Required
Details
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin router-guest-vlan-hardened:router-guest-vlan-hardened
git switch router-guest-vlan-hardened
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lytedev/nix!559
No description provided.