feat(network): add network policy create (--stamp/--deny/--reply-stamp)#803
Open
brunodam wants to merge 1 commit into
Open
feat(network): add network policy create (--stamp/--deny/--reply-stamp)#803brunodam wants to merge 1 commit into
network policy create (--stamp/--deny/--reply-stamp)#803brunodam wants to merge 1 commit into
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds the solo-provisioner network policy create command and a new internal/network/policy implementation for authoring and applying tier-ordered inet weaver nftables policies (stamp/deny/reply-stamp), including on-disk persistence (network-weaver.nft) and a per-policy registry.
Changes:
- Introduce
internal/network/policywith policy model + validation, deterministic full-chain renderer (golden-file pinned), registry IO, and aManager.Createimplementation that applies vianft -fand seeds live set membership. - Wire a new
network policy createCobra verb (flags, pod-CIDR auto-detect, tests) and register it under thenetworkcommand group. - Document the new command in
docs/quickstart.mdwith examples and a flags table.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/network/policy/paths.go | Defines inet weaver constants (paths/service/lock) and package-level scope docs |
| internal/network/policy/class.go | Static QoS class → mark/priority lookup used by --stamp/--reply-stamp |
| internal/network/policy/policy.go | Policy registry model + flag-combination and input validation |
| internal/network/policy/render.go | Tier-ordered full-chain renderer + atomic file writer |
| internal/network/policy/registry.go | Read/write/load-all for per-policy registry JSON files |
| internal/network/policy/manager.go | Manager.Create orchestration (lock, merge, apply, persist, enable service) |
| internal/network/policy/nft.go | nft runner implementation (Apply, AddElements, List, Delete, Exists) |
| internal/network/policy/service_linux.go | Linux systemd unit install/enable logic for boot readiness |
| internal/network/policy/service_other.go | Non-Linux no-op service enabler for cross-platform unit tests |
| internal/network/policy/validate_test.go | Unit tests for Policy.Validate and lookupClass |
| internal/network/policy/policy_test.go | Golden render + tier invariants + create/registry behavior tests |
| internal/network/policy/testdata/network-weaver.golden.nft | Golden expected nft document contract for render output |
| cmd/cli/commands/network/policy/policy.go | Adds network policy command group and wires create |
| cmd/cli/commands/network/policy/create.go | Implements network policy create flag parsing, CIDR resolution, podCIDR detection |
| cmd/cli/commands/network/policy/policy_test.go | CLI structure/flags tests + end-to-end command execution tests (stubbed manager/detect) |
| cmd/cli/commands/network/network.go | Registers policy under the network command group |
| docs/quickstart.md | Adds quickstart docs for creating traffic policies and flag semantics |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
b300d6c to
1b0b97b
Compare
…amp) Implement the `solo-provisioner network policy create` verb (epic TS_1 #738, Story 1.2 #758): the command-driven authoring surface for the `inet weaver` classification/ACL plane (design §8.4.2, §8.4.6). `create` renders one named category's rule(s) into the inet weaver forward chain, ensures its nft set `@<name>` exists, writes a per-policy registry JSON under /etc/solo-provisioner/policies/, applies the full chain to the live kernel with `nft -f`, and atomically rewrites /etc/solo-provisioner/network-weaver.nft. It mirrors the merged internal/network/firewall package (Manager, atomic write, shared flock, shared boot oneshot) but targets the inet weaver table. - --stamp resolves the priority from an in-code map of the 6 stable §5 classes; referencing an unknown class is an error (no dependency on Story 1.4's shape state). - --deny / --reply-stamp / --from-entity world / --ports / --cidrs(-file) per the flag-validity rules in §8.4.2/§8.4.6. - The full chain is rendered in tier order (deny -> reply-restore -> specific stamp -> fallthrough -> est/rel -> drop), never creation order. Set membership is never persisted (§8.3.1); --cidrs seeds the live set only via nft add element. Scope boundary (epic #738): create only. Element verbs are #759; delete, overlap-rejection, the created_at tiebreaker and last-policy teardown are #772. Port sets are named `@<policy-name>_ports` (a mechanical choice where the design examples are inconsistent). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Bruno Marques <bruno.marques@swirldslabs.com>
1b0b97b to
a22616d
Compare
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.
Description
Implements
solo-provisioner network policy create— Story 1.2 of epic TS_1 (#738), the second scope in thenetworkcommand family afternetwork firewall(#757). This is the command-driven authoring surface for theinet weaverclassification/ACL plane (design §7.2.4, §8.4.2, §8.4.6).createrenders one named category's rule(s) into theinet weaverforward chain, ensures its nft set@<name>exists, writes a per-policy registry JSON under/etc/solo-provisioner/policies/, applies the full chain to the live kernel withnft -f, and atomically rewrites/etc/solo-provisioner/network-weaver.nft. The newinternal/network/policypackage mirrors the mergedinternal/network/firewallpackage (Manager, atomic temp+rename write, shared apply flock, shared boot oneshot) but targets theinet weavertable, which has the opposite lifecycle — its set membership churns continuously as the daemon reconciles it from statusz.Key design decisions (so reviewers don't reverse-engineer them):
--stamppriority lookup is in-code. The 6 stable QoS classes (publisher…reserve-egress) map to fixed skb->priority / ct-mark values from design §5, baked intoclass.go. Referencing an unknown class is a create-time error. This means Story 1.2 has no runtime dependency on Story 1.4 (network shape) — the name→priority encoding is static; shape only sets each class's bandwidth.network-weaver.nft(§8.3.1) — statusz is the source of truth and the daemon reconciles it.--cidrsseeds the live set only, via a separatenft add element.nft -f, not a service restart (the shared boot oneshot does not loadnetwork-weaver.nftuntil Story 0.3 —inet hostreboot persistence (network-host.nft + oneshot) #780 extends it).createstill enables the shared unit for boot readiness.Files changed
internal/network/policy/paths.goinet weaverpath/service/lock constantsinternal/network/policy/class.golookupClassinternal/network/policy/policy.goPolicyregistry model +Validate(all flag-combination rules)internal/network/policy/render.goatomicWriteFileinternal/network/policy/registry.gointernal/network/policy/manager.goManager.Create(flock, idempotent, apply+persist)internal/network/policy/nft.gonftRunner (Apply/AddElements/List/Delete/Exists)internal/network/policy/service_{linux,other}.gointernal/network/policy/*_test.go+testdata/network-weaver.golden.nftcmd/cli/commands/network/policy/{policy,create}.gonetwork policy createverb + flagscmd/cli/commands/network/policy/policy_test.gocmd/cli/commands/network/network.gopolicy.GetCmd()into thenetworkgroupdocs/quickstart.mdCreate a Traffic Policysection (examples + flags table)Review guide
Code review checklist
class.go— the 6 class priorities/marks match design §5 exactly (publisher0x10/0x10010 …reserve-egress0x60/0x10060); reply ct-mark = the reply class's mark.policy.go:Validate—--stamp⊕--deny;--reply-stamprequires--stamp+--direction egress;--from-entity world⊕--cidrsand stamp-only;--reply-stampCIDRs areip:port; IPv6 rejected (ipv4_addr sets).render.go— tier order (deny → reply-restore → specific → fallthrough → est/rel → drop); membership set schemas carry no elements, only_portssets do; compoundipv4_addr . inet_serviceset for reply-stamp;ip daddr .compound match on the egress forward rule.manager.go:Create— render happens before any disk write (a failed apply leaves the registry untouched); idempotent no-op on identical config;created_atpreserved across a config-change re-render; whole op under the shared flock.@<policy-name>_ports) — the design examples are internally inconsistent (@bn_ports_publisherby class vs@bn_ports_subscribershared across policies). I chose a mechanical per-policy set. Flag if you want shared class-grouped port sets instead.network-weaver.golden.nft— read it top-to-bottom against §7.2.4; it is the behavioral contract.Scope boundary (epic #738) — this PR is
createonly. Deferred:add/remove/set/show/deleteelement verbs → Story 1.3 — Implementnetwork policyelement verbs (add/remove/set/show/delete) #759 (Story 1.3)created_atintra-tier tiebreaker,delete+ last-policy service teardown → Story 1.5 — Policy registry and tier-order chain re-render #772 (Story 1.5)network-weaver.nft→ Story 0.3 —inet hostreboot persistence (network-host.nft + oneshot) #780network shapeclass declaration → Story 1.4 — Implementnetwork shapescope (tc HTB classes + device roots) #771 (Story 1.4)Test commands
Manual UAT (UTM VM — Debian/Ubuntu with nftables + systemd)
chain forwardwithpolicy drop, the setbn-publisher(type ipv4_addr; flags interval), the port setbn-publisher_portswithelements = { 40840 }, and the rule:@bn-publisherset should contain10.1.0.1/32, but the on-disk file must not:ct state established,related accept:ip saddr @bn-restricted drop/ip daddr @bn-restricted dropto appear before thect state established,related acceptline.bn-backfill { type ipv4_addr . inet_service; }, the egress forward rule withct mark set 0x20 meta priority set 0x10060, and the ingress restorect direction reply ct mark 0x20 meta priority set 0x10020 accept.--ports→ re-renders.nft list table inet weaverreflects the change with no duplicate rules.--stamp bogusexits non-zero withunknown class "bogus": must be one of …and leaves the table unchanged.systemctl is-enabled solo-provisioner-network-nft.service→enabled.Risks / rollback
firewall: the boot unit (Story 0.3 —inet hostreboot persistence (network-host.nft + oneshot) #780) and the apply lock (Story 1.3 — Implementnetwork policyelement verbs (add/remove/set/show/delete) #759) are shared; path/service constants are duplicated by value with a matching-values note (hoisting to a shared package is a follow-up).nft delete table inet weaver+ remove/etc/solo-provisioner/policies/and/etc/solo-provisioner/network-weaver.nft. No behavior change tofirewallor existing commands beyond the oneAddCommandline.Related Issues
network policy create(--stamp/--deny/--reply-stamp) #758🤖 Generated with Claude Code