feat(network): add network policy create (--stamp/--deny/--reply-stamp)#803
Merged
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.
brunodam
force-pushed
the
00758-network-policy-create
branch
13 times, most recently
from
July 9, 2026 08:23
e19db2a to
ef534db
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>
brunodam
force-pushed
the
00758-network-policy-create
branch
from
July 9, 2026 09:02
ef534db to
3d51ab6
Compare
brunodam
added a commit
that referenced
this pull request
Jul 9, 2026
…how/delete) Story 1.3 (#759) of epic TS_1 (#738), building on the merged \`create\` verb (PR #803 / Story 1.2). - \`add\`/\`remove --cidr\`: mutate a policy's live nft set directly via \`nft add/delete element\`, under the shared flock, without re-rendering the chain or touching network-weaver.nft (§8.3.1). - \`set --cidrs|--cidrs-file\`: atomic full-list replace via \`flush set + add element\` in a single \`nft -f -\` document — one kernel transaction. - \`show --name\`: prints registry config (action, class, ports, created_at) and live set membership from \`nft list set inet weaver <name>\`. - \`delete --name\`: re-renders the chain minus the policy; snapshots and restores remaining policies' live membership before/after the destructive \`delete table; add table\` apply (same pattern as Manager.Create); removes the registry file; atomically overwrites network-weaver.nft. Last-policy service teardown is deferred to Story 1.5 (#772). - Runner interface: \`DeleteElements\` and \`SetElements\` methods added to nft.go; both execRunner and fakeRunner implementations updated. - 12 new manager-level tests (manager_ops_test.go); CLI tests for all 5 verbs; 75 internal unit tests pass; Linux cross-compile clean; lint 0 issues. Closes #759 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Bruno Marques <bruno.marques@swirldslabs.com>
brunodam
added a commit
that referenced
this pull request
Jul 9, 2026
…how/delete) Story 1.3 (#759) of epic TS_1 (#738), building on the merged \`create\` verb (PR #803 / Story 1.2). - \`add\`/\`remove --cidr\`: mutate a policy's live nft set directly via \`nft add/delete element\`, under the shared flock, without re-rendering the chain or touching network-weaver.nft (§8.3.1). - \`set --cidrs|--cidrs-file\`: atomic full-list replace via \`flush set + add element\` in a single \`nft -f -\` document — one kernel transaction. - \`show --name\`: prints registry config (action, class, ports, created_at) and live set membership from \`nft list set inet weaver <name>\`. - \`delete --name\`: re-renders the chain minus the policy; snapshots and restores remaining policies' live membership before/after the destructive \`delete table; add table\` apply (same pattern as Manager.Create); removes the registry file; atomically overwrites network-weaver.nft. Last-policy service teardown is deferred to Story 1.5 (#772). - Runner interface: \`DeleteElements\` and \`SetElements\` methods added to nft.go; both execRunner and fakeRunner implementations updated. - 12 new manager-level tests (manager_ops_test.go); CLI tests for all 5 verbs; 75 internal unit tests pass; Linux cross-compile clean; lint 0 issues. Closes #759 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Bruno Marques <bruno.marques@swirldslabs.com>
brunodam
added a commit
that referenced
this pull request
Jul 9, 2026
…how/delete) Story 1.3 (#759) of epic TS_1 (#738), building on the merged \`create\` verb (PR #803 / Story 1.2). - \`add\`/\`remove --cidr\`: mutate a policy's live nft set directly via \`nft add/delete element\`, under the shared flock, without re-rendering the chain or touching network-weaver.nft (§8.3.1). - \`set --cidrs|--cidrs-file\`: atomic full-list replace via \`flush set + add element\` in a single \`nft -f -\` document — one kernel transaction. - \`show --name\`: prints registry config (action, class, ports, created_at) and live set membership from \`nft list set inet weaver <name>\`. - \`delete --name\`: re-renders the chain minus the policy; snapshots and restores remaining policies' live membership before/after the destructive \`delete table; add table\` apply (same pattern as Manager.Create); removes the registry file; atomically overwrites network-weaver.nft. Last-policy service teardown is deferred to Story 1.5 (#772). - Runner interface: \`DeleteElements\` and \`SetElements\` methods added to nft.go; both execRunner and fakeRunner implementations updated. - 12 new manager-level tests (manager_ops_test.go); CLI tests for all 5 verbs; 75 internal unit tests pass; Linux cross-compile clean; lint 0 issues. Closes #759 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Bruno Marques <bruno.marques@swirldslabs.com>
brunodam
added a commit
that referenced
this pull request
Jul 9, 2026
…how/delete) Story 1.3 (#759) of epic TS_1 (#738), building on the merged \`create\` verb (PR #803 / Story 1.2). - \`add\`/\`remove --cidr\`: mutate a policy's live nft set directly via \`nft add/delete element\`, under the shared flock, without re-rendering the chain or touching network-weaver.nft (§8.3.1). - \`set --cidrs|--cidrs-file\`: atomic full-list replace via \`flush set + add element\` in a single \`nft -f -\` document — one kernel transaction. - \`show --name\`: prints registry config (action, class, ports, created_at) and live set membership from \`nft list set inet weaver <name>\`. - \`delete --name\`: re-renders the chain minus the policy; snapshots and restores remaining policies' live membership before/after the destructive \`delete table; add table\` apply (same pattern as Manager.Create); removes the registry file; atomically overwrites network-weaver.nft. Last-policy service teardown is deferred to Story 1.5 (#772). - Runner interface: \`DeleteElements\` and \`SetElements\` methods added to nft.go; both execRunner and fakeRunner implementations updated. - 12 new manager-level tests (manager_ops_test.go); CLI tests for all 5 verbs; 75 internal unit tests pass; Linux cross-compile clean; lint 0 issues. Closes #759 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Bruno Marques <bruno.marques@swirldslabs.com>
brunodam
added a commit
that referenced
this pull request
Jul 9, 2026
…how/delete) Story 1.3 (#759) of epic TS_1 (#738), building on the merged \`create\` verb (PR #803 / Story 1.2). - \`add\`/\`remove --cidr\`: mutate a policy's live nft set directly via \`nft add/delete element\`, under the shared flock, without re-rendering the chain or touching network-weaver.nft (§8.3.1). - \`set --cidrs|--cidrs-file\`: atomic full-list replace via \`flush set + add element\` in a single \`nft -f -\` document — one kernel transaction. - \`show --name\`: prints registry config (action, class, ports, created_at) and live set membership from \`nft list set inet weaver <name>\`. - \`delete --name\`: re-renders the chain minus the policy; snapshots and restores remaining policies' live membership before/after the destructive \`delete table; add table\` apply (same pattern as Manager.Create); removes the registry file; atomically overwrites network-weaver.nft. Last-policy service teardown is deferred to Story 1.5 (#772). - Runner interface: \`DeleteElements\` and \`SetElements\` methods added to nft.go; both execRunner and fakeRunner implementations updated. - 12 new manager-level tests (manager_ops_test.go); CLI tests for all 5 verbs; 75 internal unit tests pass; Linux cross-compile clean; lint 0 issues. Closes #759 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Bruno Marques <bruno.marques@swirldslabs.com>
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.--directionflag. Every class in the §5 map has exactly one direction (§7.2.4's worked ruleset never mixes a class across both), soValidatederivesPolicy.Directionfrom--stamp's class instead of taking it as a separate flag that could contradict it.--reply-stampis checked as the mirror direction of the forward--stampclass (egress forward → ingress reply) rather than requiring a matching--directionvalue. Design doc (§5, §8.4.2, §8.4.3) and issues Epic TS_1 — solo-provisioner network command family #738/Story 1.2 — Implementnetwork policy create(--stamp/--deny/--reply-stamp) #758/Story 1.4 — Implementnetwork shapescope (tc HTB classes + device roots) #771 updated to match.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.--pod-cidris no longer required (or auto-detected) for--deny.POD_CIDRis only ever read byrenderStampRule; a deny rule (ip saddr/daddr @<name> drop) never references it.Rendernow only requires it when the merged policy set has at least one--stamppolicy (needsPodCIDR), and the CLI skips cluster-based auto-detection entirely for--deny. Caught by manually testing--denyin isolation — quarantining a CIDR shouldn't need a reachable cluster.--deny's merged chain does need a pod CIDR (a--stampsibling exists) and none was supplied,Createrecovers it from the existingnetwork-weaver.nftinstead of erroring —ExtractPodCIDR(new,parse.go) regex-extracts the literal CIDR any rendered--stamprule starts with. This mirrorsinternal/network/firewall'sParse(), which exists for the same reason ("element verbs use this to load prior state so they don't need the full flag set re-spec") —podCIDRis a deployment-wide constant, not a per-call argument, so an unrelated--denycreate shouldn't need it re-supplied just to correctly re-render an unchanged--stampsibling. No CLI change:--stamp's own auto-detect-or-hard-error path is untouched, since that policy's own correctness still depends on a fresh value, not a cached one.network-weaver.nftgenuinely missing (not just the kernel table) still errors clearly.createis create-if-missing, mirroringnetwork firewall create— not additive. An existing policy is left untouched (warn, no changes) unless--forceis passed, which replaces (not merges) its config and membership from the given flags/--cidrs. This is a deliberate divergence from an earlier iteration of this PR that madecreatediff-and-reapply automatically; matching firewall's already-established convention is simpler and more predictable.Runnerdidn't modelApply()'s destructive wipe, so it couldn't have; it does now):delete table; add table(§8.3.1: membership is never part of it), so everyApply()destroys and recreates every set in the table, not just the policy being created — any other policy's live membership (permanently so, for operator-curated policies likebn-mgmt-in, which the daemon never touches) was lost on every subsequentcreate. Fixed by snapshotting every policy's live membership (Runner.ListElements, new) beforeApply()and restoring it afterward (TestCreate_PreservesSiblingMembershipAcrossRerender).network-weaver.nftyet (Story 0.3 —inet hostreboot persistence (network-host.nft + oneshot) #780) — so "the registry has this policy" doesn't imply "the live table has it". Without--force,createnow checksRunner.Existsand self-heals a missing table from the already-registered config — never the caller's just-typed flags/--cidrs, since applying those requires--force(TestCreate_SelfHealsMissingTableWithoutForce). Membership itself can't be recovered this way (it was never persisted); it comes back empty until--forcere-seeds it or the daemon's poll loop catches up.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/parse.goExtractPodCIDR— recovers the pod CIDR from an existingnetwork-weaver.nft(mirrorsfirewall.Parse)internal/network/policy/registry.gointernal/network/policy/manager.goManager.Create(flock, idempotent, apply+persist)internal/network/policy/nft.gonftRunner (Apply/AddElements/ListElements/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;--stampderivesDirectionfrom its class;--reply-stamprequires--stampto resolve to an egress class and itself resolve to the mirror ingress class;--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;needsPodCIDRonly requirespodCIDRwhen the merged set has a--stamppolicy, since onlyrenderStampRulereads it.manager.go:Create— create-if-missing: an existing policy is a no-op without--force, regardless of whether the given flags/--cidrsdiffer;--forcere-renders and replaces (not merges) config and membership. Render happens before any disk write (a failed apply leaves the registry untouched);created_atpreserved across a--forcereplace; whole op under the shared flock.snapshotMembershipruns beforeApply()and its restore loop runs after, so siblings' membership survives the destructive re-render — a snapshot failure aborts before the kernel is touched. Without--force, a missing live table for an existing policy self-heals from the registry, ignoring the caller's new flags/--cidrs. An emptypodCIDRis recovered from the existingnetwork-weaver.nftviaExtractPodCIDRbefore falling through toRender'sneedsPodCIDRcheck.@<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)
Build and copy the binary to the VM:
Create a stamp policy (pod CIDR passed explicitly so no cluster is needed):
Verify the live chain:
Expect a
chain forwardwithpolicy drop, the setbn-publisher(type ipv4_addr; flags interval), the port setbn-publisher_portswithelements = { 40840 }, and the rule:(
nft list tablereformatsmeta priorityvalues that decode as a valid tc classid intomajor:minornotation —0x10010→1:10. This is nft's own display choice on read-back, not what we write:network-weaver.nftand the golden test both correctly show the literal0x10010we author.)The live
@bn-publisherset should contain10.1.0.1/32, but the on-disk file must not:Add a quarantine (deny) policy and confirm the drops render above
ct state established,related accept. Deliberately omit--pod-cidrhere —--denynever referencesPOD_CIDRitself, so it must not try to auto-detect it. Note that the merged chain does still includebn-publisher(a--stamppolicy, from step 2) — this call succeeding with no--pod-cidrand no cluster reachable demonstrates the recovery path: the CIDR is pulled back out of the existingnetwork-weaver.nft(10.4.0.0/24, from step 2), not re-detected or required:Expect
ip saddr @bn-restricted drop/ip daddr @bn-restricted dropto appear before thect state established,related acceptline, no pod-CIDR detection log line, no failure even if this VM has no cluster configured, andbn-publisher's rule still correctly showingip daddr 10.4.0.0/24 ...(recovered, not lost).Add a backfill reply-stamp policy and confirm the compound set + auto-rendered restore rule:
Expect the set
bn-backfill { type ipv4_addr . inet_service; }, the egress forward rule withct mark set 0x20 meta priority set 1:60, and the ingress restorect direction reply ct mark 0x20 meta priority set 1:20 accept. (ct markstays raw hex on read-back; onlymeta prioritygets the classid reformat — see the note in step 2.)Create-if-missing and
--force:network policy already exists — supplied flags/cidrs were not applied; pass --force to replace;nft list set inet weaver bn-publisherstill shows exactly10.1.0.1/32.--portsand a different--cidrs, still without--force→ same warning, same no-op. Differing flags don't matter without--force.--force:{ 40840, 40841 }, andnft list set inet weaver bn-publisherto show only10.1.0.2/32—--forcereplaces membership, it does not merge with10.1.0.1/32.Missing-table self-heal without
--force(this is what actually surfaced the bugs above). Note:nft delete tableonly deletes the kernel table — it does not touch/etc/solo-provisioner/policies/*.json(the registry). If you want a true clean-slate reset for testing (not this step, which specifically wants the registry to still think the policy exists), delete both:sudo nft delete table inet weaver && sudo rm -f /etc/solo-provisioner/policies/*.json.No
--force, and deliberately yet another different--cidrs. The registry still saysbn-publisheris configured, but the kernel table is gone — this is also what a reboot looks like before Story 0.3 —inet hostreboot persistence (network-host.nft + oneshot) #780 extends the boot oneshot. Expect success (notnft add element ... failed: No such file or directory): the table is recreated from the last registered config (ports40840,40841, from step 5's--forcecall), but10.1.0.99/32must not appear anywhere andnft list set inet weaver bn-publishercomes back empty — membership was never persisted, so it can't be recovered without--forcere-seeding it.Re-seed known membership with
--force(the table exists again now, so this is a normal replace) so the next step has something to verify:--from-entity worldfallthrough, and creation-order independence. Create the fallthrough policy first, then the specific one that shares its direction and ports, and confirm the chain still orders specific-before-fallthrough despite the reverse creation order:Expect, in this order: the
bn-partner-outrule (ip saddr @bn-partner-out ... meta priority set 1:40) before thebn-public-outrule (... meta priority set 1:50, noip daddr @setclause —--from-entity worldsuppresses it) — even thoughbn-public-outwas created first.bn-subscriber-in's rule (ip daddr 10.4.0.0/24 tcp dport ... meta priority set 1:30, noip saddr @set) confirms the same on the ingress side.Confirm
bn-publisher's membership (re-seeded at the end of step 6) survived the three brand-new-policy re-renders just triggered in step 7:sudo nft list set inet weaver bn-publisherMust still show exactly
10.1.0.1/32— this is the sibling-membership bug fixed above: eachcreatein step 7 forces a full chain re-render (delete table; add table), which previously wiped every other policy's live membership.Error paths:
--stamp bogusexits non-zero withunknown class "bogus": must be one of …and leaves the table unchanged.--stamp publisher --reply-stamp backfill-response(an ingress class with a reply-stamp) →--reply-stamp is only valid when --stamp resolves to an egress class.--stamp reserve-egress --reply-stamp partner(both egress —partneris not the mirror ingress class) →--reply-stamp class "partner" must resolve to an ingress class.sudo rm /etc/solo-provisioner/network-weaver.nft(the file itself, independent of the kernel table or the JSON registry), then re-run the step-3--denycommand again.bn-publisher's registry entry is still there, so the merged chain still needs a pod CIDR, and there's nonetwork-weaver.nftleft to recover it from → expect the samepod CIDR is required to render a --stamp policyerror, not a silent/incorrect success.--pod-cidrhard-error path (no cluster reachable from this VM): omit--pod-cidrentirely, for a new policy name so create-if-missing doesn't short-circuit first —Expect a hard error ("could not auto-detect the pod CIDR; pass --pod-cidr explicitly"), not a warning-and-continue. This is the deliberate difference from
network firewall create, which falls back best-effort when no cluster is reachable —network policy createalways requires a resolvable pod CIDR.Boot readiness:
systemctl is-enabled solo-provisioner-network-nft.service→enabled.Risks / rollback
nft list setper existing policy before every re-render; bounded by the shared flock (no concurrent mutator, including the future daemon poll loop, can interleave with it), so the snapshot can't go stale mid-Create.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).--forceto replace an existing policy). Rollback: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