fix(networking): enforce Windows container egress at the Hyper-V firewall - #140
Merged
Conversation
…wall #136 blocked container->LAN egress with host Windows Defender Firewall rules (netsh advfirewall) scoped to the container subnet as source. On real hardware this is a no-op: the host firewall (WFP/MPSSVC) evaluates NATed container egress post-NAT, where the source is the host's own LAN address, so a rule scoped to 10.88/16 matches nothing. Confirmed against a live v0.1.6 Windows node -- the containment suite still reached Proxmox, Incus, and Grafana. Replace it with Hyper-V firewall rules (New-NetFirewallHyperVRule), which filter at the container vNIC boundary BEFORE NAT, where the packet still carries the container's own address. Rules are scoped by -VMCreatorId (discovered at runtime via Get-NetFirewallHyperVVMCreator, excluding the well-known WSL creator) so they apply to job containers, not the host or unrelated Hyper-V workloads. The gateway (DNS/NAT/default route) and the container-to-container range are carved out of every blocked range up front (subtractCIDR), so they never appear in a Block rule -- no reliance on rule-priority ordering, which is the kind of unverified firewall semantic that sank #136. The internet, including GitHub/Docker Hub/registries, is untouched. Idempotent (remove-before-add), findable/removable by the ephemerd- name prefix, and graceful: hosts without the Hyper-V firewall cmdlets fall back to the netsh rules, and any failure warns instead of failing daemon startup. netsh construction is retained as that fallback. Needs a live run on the Server 2025 node to confirm; the containment suite's Windows step is the regression gate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
#136 tried to block container→LAN egress on Windows with host Windows Defender Firewall rules (
netsh advfirewall), scopedlocalip=<container subnet 10.88.0.0/16>on the theory that MPSSVC would see the container's source IP on the WinNAT-forwarded flow.On real hardware it does nothing. Windows Firewall (WFP/MPSSVC) evaluates forwarded container→LAN traffic post-NAT at the host endpoint, where the source is the host's own LAN address — so a rule scoped to the
10.88/16container source matches nothing. #136's own PR body flagged this assumption as unverified-without-hardware. Now verified false: the containment suite (.github/workflows/containment.yml, Windows "Fleet management planes must be unreachable") ran on a live v0.1.6 node (where #136'sinstallFirewallRules()executes at startup) and still reached:The host firewall is simply the wrong enforcement point for NATed container egress.
Fix:
New-NetFirewallHyperVRuleThe node is Windows Server 2025 (build 26100), which ships the Hyper-V firewall cmdlets. The Hyper-V firewall filters at the container's vNIC boundary, BEFORE NAT, where the packet still carries the container's own address — the correct enforcement point for Hyper-V-isolated Windows containers, and Microsoft's purpose-built mechanism for exactly this.
-VMCreatorIdso they apply to container ports, not the host or unrelated Hyper-V workloads. There is no publicly documented fixed GUID for the Windows container creator (only WSL's{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}is documented), and HNS assigns it per host — so rather than hardcode an unverifiable constant, the creator is discovered at runtime viaGet-NetFirewallHyperVVMCreator, excluding the well-known WSL creator. (Regular Hyper-V VMs aren't filtered by the Hyper-V firewall at all — it governs container-class workloads — so this can't touch real VMs.)Blockrules for RFC1918 + link-local (10/8,172.16/12,192.168/16,169.254/16, matchingegressBlockedCIDRsand the Linux posture).-RemoteAddressesaccepts CIDRs andstart-endranges as astring[], rendered as a real PowerShell array.What's allow-listed (and why)
10.88.0.1) — DNS, NAT, default route, module-proxyGatewayPorts. Blocking it bricks all container networking.10.88.0.0/16).Allow.Rather than rely on Hyper-V-firewall rule-priority ordering (allow-above-deny) to rescue the gateway — an unverified semantic, and unverified assumptions are exactly what sank #136 — the container subnet (which contains the gateway) is subtracted from every blocked range up front (
subtractCIDR), so the gateway never appears inside a Block rule in the first place.10/8becomes10.0.0.0-10.87.255.255, 10.89.0.0-10.255.255.255. The narrow container→gateway control-plane blocks (containerd / dispatch gRPC / debug exec, one TCP port each) mirror the Linux INPUT drops and leave DNS (53) and NAT intact.Idempotency, cleanup, graceful degradation
ephemerd-egress-prefix; removed on Cleanup (by prefix, catching stale rules from a crashed run).Tests
Table-style unit tests assert the rule spec (VMCreatorId scoping, ranges, Outbound/Block, control ports, the gateway/subnet never blocked) and the exact PowerShell rendering — without executing anything.
go build ./...,GOOS=windows go vet,GOOS=linux go vet, andgo test ./pkg/networking/...all pass.Not verified here
I could not run this against real hardware. The container VMCreatorId resolves at runtime, the
Set-NetFirewallHyperVVMSettingenable step, and actual enforcement all need a live run on the Server 2025 node. The containment suite's Windows step is the regression gate — it currently FAILs and must go green once this deploys.