Repository navigation
test(edge): Prove MASQUE packet access in the lab - #1140
Conversation
The MASQUE packet-access checks need a CONNECT-IP server that runs as an ordinary Galactic Edge provider. internal/edge/connectip has the protocol component and a TUN packet backend, but nothing connected them to EdgeInstance, EdgeAttachment and EdgeRoute. tests/edgeconformance/masqueprovider is that fixture. It registers its pod's EdgeInstance and holds the instance lease through provider.InstanceStore and claim.Instance with the cell's timing. It requests one PacketAccess attachment per configured EdgeService and claims one EdgeRoute per connector /96. Once core accepts a route, it carries that connector's packets through connectip.TUNBackend on the device galactic-edge created in the pod. It uses no host namespace, hostPath, NET_ADMIN or BPF. Admission is a test fixture, not a product authority. Each connector is a static bearer credential bound to one service's VPC and one /96. A connector is admitted only while the lease is held and its route is accepted on a programmed attachment, and losing any of those ends that connector's sessions without touching others. Packets stop at the lease deadline on CLOCK_BOOTTIME even while the reconcile loop waits on the API. On SIGTERM the fixture ends sessions before it releases routes, deletes attachments and releases the lease. The Dockerfile builds a scratch image that runs as 65532, and deploy/ holds its registration and an unprivileged Deployment.
The MASQUE fixture provider has no proof yet that the foundation carries its packets correctly. TestMASQUE runs it on the lab's dfw cluster and checks what the packet-access milestone requires. - M16: the fixture pod shares no host namespace, mounts no hostPath and adds no capability, and its process holds none. galactic-edge creates each TUN device in the pod, owned by 65532, and the fixture opens it. An unknown credential gets 401, a credential presented for the other VPC gets 403, and a connector whose route core refused gets 503. - M07: VPCs 10 and 20 hold the same connector /96 in one provider process. Neighbour caches are flushed first. Each tenant pings its own workloads on the compute node and on the fixture's node, and the other tenant's. Each client uses its own ICMPv6 echo identifier, so captures on every workload link show which client a packet came from. The own-tenant capture is the positive control. - NAT egress: both tenants reach the off-fabric host through the NAT shards from the attachment node. - Partial setup: a second instance whose only route is refused is killed without shutdown. The first instance keeps answering, and core reaps the attachment, instance, lease and route, and the node removes the host link. - Recreation: the fixture's pod is deleted, and the replacement serves both tenants in isolation. The old pod's objects and host links are reaped, and the replacement's routes stay accepted. - Teardown fails the run unless the provider and core have removed every attachment, instance, lease and route before the namespace is deleted, no fixture advertisement or host link remains, and the tenants' edge-client pods are unchanged and still reach each other. Every packet row also requires an accepted request with the configured /96 that sent through its whole window, so a refused or cut-short request cannot pass on its reply ratio. edgeprobe gains a connectip client and an ICMPv6 echo capture. The client runs on the fixture's node, because the lab's pod network does not carry UDP from a node to a pod on another node. Where the client runs does not change the gVPC path behind the fixture. task verify:masque-conformance runs the rows, and build:masque-provider builds and loads the image.
|
Validation run locally. The lab run is from a clean checkout of 98f59d6, with the fixture image rebuilt from it. The repository checks ran at 8e84b06, and 98f59d6 only adds the teardown's removal of the fixture CA from the nodes. Lab run,
Repository checks:
Not run: the target-platform rows. No harness exists for a cluster of real nodes, so #974 stays open. The lab's pod network does not carry UDP from a node to a pod on another node, so the CONNECT-IP client runs on the fixture's node. That changes only the client-to-fixture leg, not the gVPC path behind the fixture. Settled here from the #989 findings in the acceptance matrix:
Still open, and outside this change:
|
TestMASQUE copies its fixture CA onto the three dfw nodes so the CONNECT-IP client can verify the fixture's certificate. Teardown now deletes it, so the run leaves nothing of its own behind on the nodes.
|
Review record for this PR, covering two passes, with the ready phase run at the requester's direction after a split second pass. First pass at 8e84b06: the adversarial review held, because the archived lab run was made from an uncommitted tree, and it raised a nit that teardown left the fixture CA on the lab nodes. The conventions review returned merge with nits (title length, commit subject length). Fixes: a clean rerun from 98f59d6 passed every lab row, 98f59d6 removes the CA at teardown, and the title was shortened. Not applied: two commit subjects over 50 characters, because shortening them needs a force-push of published commits. Second pass at 98f59d6: the re-review returned merge, confirming all three fixes against the clean run's recorded commit and dirty flag and the nodes' state. The conventions review held on the body's commit count, since corrected, and noted it did not run the lab itself. CI on 98f59d6: all checks passed, with publish-service-api skipped. Auto-merge is enabled with the merge method, the only one the main ruleset allows. The ruleset requires one approving review, code owner review, and approval of the last push, so the PR waits for a human approval before it merges. |
ecv
left a comment
There was a problem hiding this comment.
Approve.
Test-only change. Nothing in it reaches production, and the suite asserts isolation in both directions with positive controls.
- Nothing ships:
publish.yamlbuilds named images fromcontainers/*/Dockerfileand bundles onlyconfig/, so the fixture Dockerfile and the manifests undertests/edgeconformance/masqueprovider/deployare never built or published. The image is built locally by the lab Taskfile and loaded withimagePullPolicy: Never. - The unprivileged claim holds in the manifest (non-root 65532,
drop: ["ALL"],allowPrivilegeEscalation: false, read-only root, no host namespaces or hostPath) androwUnprivileged(masque_test.go:489-501) asserts it against the live pod, including no added capabilities and not privileged. - Isolation is not vacuous: each tenant's workloads must answer at least 90% (positive control), echoes to the other tenant's workloads must be zero, the client must receive nothing that is not its own reply, and sniffers on all four workload links count foreign-tenant requests and must also see about half of their own tenant's requests. It runs cold, with flushed neighbor caches, and again after pod recreation.
- Credentials are random per run (
rand.Text()), TLS material is generated per run, and the wrong-VPC credential case expects 403. acceptance.mdis consistent with the suite: M16 and M07 map toM16-unprivileged-providerandM07-isolation, and the paragraph says a lab result does not satisfy rows that need the target platform.
Follow-ups:
build:masque-providerjoins the lab-wide build and load list, so every lab bring-up builds and loads this image onto the edge nodes. Consider making it opt-in withverify:masque-conformance.- The suite takes about 15 minutes and is outside
verifyand CI, so nothing catches a regression until someone runs it. Worth a nightly job once the lab runner exists.
Summary
The MASQUE milestone needs proof that a CONNECT-IP server can run as an unprivileged Galactic Edge provider and keep tenants with identical addresses apart.
This adds a test-only fixture provider and a lab suite that checks isolation in both directions, NAT egress, a half-set-up instance killed without shutdown, pod recreation and asserted cleanup.
The proof covers the containerlab lab only. The target-platform run the issue also requires has no harness yet, so the issue stays open.
Compatibility and rollback
Test code, one lab image and one doc paragraph. Core behavior does not change. Revert the PR's commits to roll back.
Test plan
Related to #974