fix(devel): make the lab compose stack work end to end
Summary
The lab stack (devel/compose.labs.yml, the README's "Self-host / Local stack") doesn't work on current releases. The CAS crashes on startup because of two regressions, and once it starts, attestation push with a real storage backend fails because the control plane can't reach the CAS.
- The public key path points at the private key.
CAS_PUBLIC_KEY_PATHis set to/devkeys/cas.pem, which is the EC private key. The public key iscas.pub, which the CAS devel config already uses as its default. This was harmless while the key was parsed per request, but since #3225 (v1.105.0) it is parsed at startup, and the CAS panics withfailed to parse public key: x509: malformed tbs certificate. - There is no writable staging dir. #3365 (v1.110.0) made
staging_dirrequired. The image isFROM scratchwithUSER 1001and has no directory that user can write to, so the CAS panics before it reaches the key. The Helm chart mounts an emptyDir at/tmp-staging-fs; the compose file has nothing equivalent. - The control plane dials itself instead of the CAS. With a non-inline CAS backend, the control plane uploads the attestation bundle to the CAS at
cas_server.grpc.addr, whichapp/controlplane/configs/config.devel.yamlsets to0.0.0.0:9001. Inside the control-plane container that address is the container itself, soattestation pushfails with an internal error after the dial times out (creating the gRPC client: ... DeadlineExceeded). The value has no env placeholder, so compose couldn't override it. This one predates the two regressions; it only shows up with a non-inline backend.
Changes
All changes are dev-only. The production images and the Helm chart are unchanged.
devel/compose.labs.yml(CAS):- point
CAS_PUBLIC_KEY_PATHatcas.pub - mount a tmpfs owned by the CAS user at Helm's staging path (
/tmp-staging-fs:uid=1001,gid=1001) - set
CAS_STAGING_DIR: /tmp-staging-fs
- point
app/controlplane/configs/config.devel.yaml:cas_server.grpc.addrbecomes${CAS_SERVER_ADDR:0.0.0.0:9001}. The default is unchanged, so running the control plane on the host behaves as before.devel/compose.labs.yml(control plane):CP_CAS_SERVER_ADDR: cas:8001. Like the Helm chart, this targets the CAS's shared endpoint rather than the deprecated gRPC-only listener on 9001.
This works with the released :latest images, so the lab stack works as soon as this is merged.
Testing
On Linux arm64 (Docker 29.9.3, rootless), with docker compose -f devel/compose.labs.yml up and the released artifact-cas:latest and control-plane:latest images:
- The CAS loads
/devkeys/cas.puband listens on:8001and:9001. The staging dir is a tmpfs owned by 1001. - End to end with the CLI, using an S3 backend (the optional RustFS service) as the org's default:
attestation init, thenattestation addof a new 5 MB artifact: the CAS staged it, verified its digest and uploaded it to the backend.attestation push: the control plane uploaded the signed bundle to the CAS overcas:8001. The run issuccess.artifact downloadreturned the 5 MB file with a matching SHA-256.
AI disclosure
Investigation and changes were done with Claude Code (Assisted-by: Claude Code on all commits). I reviewed and tested the changes myself.
🤖 Generated with Claude Code