Repository navigation
feat: P6 on Kubernetes, slice 2 — external P4 hosts over TLS (#424) - #437
Conversation
…r TLS (rossoctl#424) Registers it as P6.2. Part of epic rossoctl#426. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
… on each attach (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…octl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…TLS (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…est server (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…assthrough Route (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…er stack (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…bug, exit status (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…validation (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…tl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…d host bundles (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
… failure (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ON Patch in mock (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…t's commands (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
… an in-cluster relay (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…rejected token fails (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ss the settle window (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ay Route probe, no-restart add (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
… after a failed P7 add, tells a failed moca-setup read from an empty one (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…on (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…CH_* validated; DIR refused (rossoctl#424) verify_attached_remote counted polls, so a churning worker (each poll spending a whole settle window) could run TIMEOUT x (1 + SETTLE) seconds. It now polls until a SECONDS deadline, always looking at least once, so it ends within about TIMEOUT + SETTLE + 1. preflight refuses a MICROVM_ATTACH_TIMEOUT or MICROVM_ATTACH_SETTLE that is not a whole number >= 1 before anything is written; the settle must outlast the worker's first reconnect backoff (at most 750ms). The reserved sandbox ID DIR is refused: the relay refuses it before any lookup. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…relay pod to its relay container (rossoctl#424) - setup.sh refuses the reserved P4 ID DIR (SH_RELAY_TOKEN_DIR is the relay's token-directory setting), and print_access prints `ssh <kvm-host> rm -r moca-p4-<id>` before the scp, since scp -r into an existing target nests the copy. - p4-relay: kubectl.kubernetes.io/default-container: sandbox-relay on the relay pod template. - base/relay.yaml: the attach-rule comment no longer promises a P4 CIDR there. - smoke.sh: note that createSsl(null) uses Node's bundled roots, the worker the host's pool. - README §11: DIR reserved, re-copying a bundle, the objects left behind after clearing the IDs (with the delete line), the Revoke item folded, the default-container troubleshooting note. - Spec: DIR in §4.1, the Revoking sync delay, the annotation, the printed rm, the settle minimum. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…JSON envelope (rossoctl#424) A real model's tool_result preview is {"content":[{"type":"text","text":"..."}]}, not plain text. P3 took the whole envelope as the guest kernel, so "not a node's kernel" held even for a turn that ran on a node. Decode the envelope (plain previews pass as is), take the first non-blank line trimmed, and require it to look like a kernel release and to be no node's kernel. smoke.test.sh: the curl mock streams $MOCK_TURN_SSE for /v1/turn, and two cases drive P3 with the live envelope format: a guest kernel passes naming 6.18.44+, a node kernel fails. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…l P4 host (rossoctl#424) Live results: rossoctl#424 (comment) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ault-container test (rossoctl#424) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
pdettori
left a comment
There was a problem hiding this comment.
Solid, thoroughly tested slice. The relay's token-directory lookup is carefully hardened (ENOENT-only fallthrough, empty-file and exec-token collision refusal, reserved DIR, constant-time compare, regex-gated path access), tokens never touch argv or child-process environments, bundles are 0700/0600, and the ghostunnel sidecar is digest-pinned with a hardening securityContext and an attach-only target. Test coverage is unusually comprehensive — real TLS handshakes in tls_test.go, argv-hygiene assertions, SSA-prune idempotency, bash 3.2 paths, and failure-injection cases for each claimed behavior.
Two nits inline. One design note, not blocking: the Route path uses --disable-authentication on ghostunnel, so L4 TLS is encryption-only and authentication rests entirely on the 64-hex constant-time-compared attach token with no rate limiting — entropy is the mitigation, which the spec accepts.
(Posted as COMMENT rather than APPROVE — GitHub does not allow approving one's own PR.)
| claim P3 "an authenticated turn runs in a P4 microVM: a kernel that is no node's, and a file written" | ||
| SID='' | ||
| if new_session && turn p4write "$SID" "$(ask K8S-SMOKE-P4-WRITE 'uname -r; echo p4-proof | tee proof.txt')" && | ||
| grep -q p4-proof <(tool_out "$OUT/p4write.sse"); then |
There was a problem hiding this comment.
for id in ${ids//,/ } splits $ids unquoted — but here ids comes from the cluster ConfigMap (moca-setup), not from the validated SH_P4_SANDBOX_IDS input, so it's not covered by normalize_p4_ids's identifier check. A tampered/garbage ConfigMap value containing a glob would undergo pathname expansion against the CWD. Low severity (cluster-local, worst case a bogus P2 failure), but read -ra with IFS=',' would be tighter and revalidate the identifier here.
There was a problem hiding this comment.
Fixed in ea18150. P2 now splits the list with IFS=',' read -ra, so there is no pathname expansion, and re-checks each id against ^[A-Za-z_][A-Za-z0-9_]*$. An invalid id is a distinct FAIL (moca-setup SH_P4_SANDBOX_IDS holds invalid id(s): '*'). The expansion uses ${a[@]+…}, so it's safe on bash 3.2.
The regression test in smoke.test.sh sets MOCK_P4_IDS='moca_microvm_0,*'. Against the old code it reproduced exactly what you described: the * expanded to the repo's own filenames (buf.gen.yaml buf.yaml CLAUDE.md …), reported as missing records. It passes now under bash 5 and /bin/bash 3.2.
| } | ||
| pool, err := x509.SystemCertPool() | ||
| if err != nil || pool == nil { | ||
| pool = x509.NewCertPool() |
There was a problem hiding this comment.
If x509.SystemCertPool() errors, the code silently falls back to NewCertPool() — the system roots are dropped with no log line, so a worker could start trusting only the relay CA without any trace of why. Practically unreachable on Linux, but a one-line log (or folding it into the returned error) would make the failure mode visible.
There was a problem hiding this comment.
Fixed in 2503419. The fallback is now logged: session: WARNING the system cert pool is unavailable (<err>); RELAY_CA_FILE <path> is the only trusted CA. I kept the fallback rather than failing, because trusting the given CA is still correct. The log makes the "system roots dropped" case visible.
x509.SystemCertPool is now a package variable, so a new subtest can make it fail. The subtest asserts that the warning names the error and the file, and that a real TLS dial still succeeds with the CA file alone. It fails with the log line removed.
…g and re-validates each id (rossoctl#437 review) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…RELAY_CA_FILE is the only trusted CA (rossoctl#437 review) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
|
Thanks for the review. Both inline nits are fixed: ea18150 (P2 splits the list without globbing and re-checks each id) and 2503419 (the system-pool fallback is logged). Each has a regression test that fails without its fix. On the design note about
If we want more than entropy, two options fit without changing the shape:
Either can be a follow-up issue if you'd like one. |
|
Opened #441 for the mTLS follow-up (client certificates at the ghostunnel sidecar, with the revocation-without-restart and cert-to-token binding questions spelled out). |
Summary
Slice 2 of epic #426 (closes #424). P6 runs in the cluster; only the P4 microVM tier needs a KVM host. A host outside the cluster runs
microvm-workeragainst the in-cluster relay over TLS. It connects through an L4 sidecar (ghostunnel) behind a passthrough Route. The cluster issues each host's token, and the relay reads it from a mounted Secret on every attach, so adding a host restarts nothing.Spec:
docs/specs/2026-10-04-p6-on-kubernetes-slice2-design.md. It is updated in this branch to match what was built; the corrections are listed under "Spec corrections".Live acceptance passed on OpenShift 4.20.8 with the shared KVM rig. Results: #424 (comment)
What's in it
Relay (
packages/sandbox-relay)SH_RELAY_TOKEN_DIRis read on each attach, so the file<dir>/<id>takes effect without a restart. The lookup order is:SH_RELAY_TOKEN_<id>;SH_RELAY_TOKEN.ENOENTfalls through to the next step. Any other read error refuses the attach, and so does an empty file.^[A-Za-z_][A-Za-z0-9_]*$. Others, such as the container tier's dashedmoca-sandbox-N, take the environment path exactly as before.DIRis refused outright, becauseSH_RELAY_TOKEN_DIRsits in theSH_RELAY_TOKEN_<id>namespace (found in review).Workers (
remote-worker)RELAY_CA_FILEis supported in bothmicrovm-workerandworker, through one shared helper,session.TransportCredentials.Manifests
base/relay.yaml: an optional mount of Secretmoca-relay-sandbox-tokensat/run/relay-tokens, mode 0400.overlays/ocp/p4-relay/, rendered only when there are P4 IDs::8444 → 127.0.0.1:9443, the attach listener only, so SandboxExec is unreachable from outside. It runs with--alpn h2, which grpc-go ≥ 1.67 requires;sandbox-relay-tls;moca-relaywithtimeout: 5m;sandbox-relay-from-router, admitting the router on TCP 8444 only;default-container: sandbox-relay, so plainkubectl logsstill reaches the relay rather than the sidecar.deploy/k8s/setup.shSH_P4_SANDBOX_IDSis sticky, and an explicitly empty value clears it.DIRare refused.SH_SANDBOX_COUNT > 0is refused, naming the fix. P4 on kind is refused.--relay-tls-cert/--relay-tls-keysupply an operator certificate.route_certthat the supervisor's TLS now uses too..generated/ocp/p4/<id>/(0700/0600), holdingworker.envand, for a self-issued certificate,relay-ca.crt.setup.shprints the exact copy and install commands.deploy/microvm/setup-microvm.sh --remote <bundle>RELAY_ADDRashost:port.50-moca-p6.conffor50-moca-remote.conf.InvocationID, with a settle window.attachedwhen the stream opens, before the relay checks the token. A rejected token shows up asstream endedwithin milliseconds, and the check catches that (found in review).render_envnow passes values through the environment, notawk -vargv.--remoteswitches back to the local relay and restores the local token.deploy/k8s/smoke.sh --tier p4(OpenShift only)UNIMPLEMENTED.Docs:
deploy/k8s/README.mdgains §11, "P4 on Kubernetes" (switch, install, verify, add/revoke and their timing, cleanup, troubleshooting).deploy/microvm/P4-ON-P6.mdgets a pointer section and token-source corrections.Spec corrections (made deliberately, all in the spec now)
--alpn h2.InvocationIDplus a settle window, not a--sincetimestamp. Local mode restores the token from the relay's copy.stream.end()), notUNAUTHENTICATED.relay-ca.crtships whenever the certificate is self-issued.Test plan
test/k8smanifest tests (60),make test,make typecheck,make lintgo test ./...inremote-workersetup.test.sh,smoke.test.sh, and everydeploy/microvm/tests/*.test.sh, under bash 5 and macOS/bin/bash3.26.18.44+vs the nodes'5.14;attach=0 exec=12;h2was negotiated live;mocactlusers;Follow-ups (not in this PR)
setup-microvm.test.sh, which could flake on a loaded CI host.🤖 Generated with Claude Code