Sign a Containment Chamber config for signed-Nitro deployment with YubiKey PIV or software Ed25519
A signed-Nitro enclave boots only from a config whose signature verifies against the
operator/CI public keys baked into the enclave image (CONFIG_SIGNERS, measured into PCR0). You
sign one config file: the enclave verifies it, and the chart reads the same file’s values to
wire the parent-side proxies — one source of truth, no parent/enclave drift.
How it flows
Section titled “How it flows”direction: down
author: "author config.yaml" { style.fill: "#FFF6EF" }sign: "sign payload (YubiKey PIV / OpenSSL Ed25519)" { style.fill: "#FFF6EF" }helm: "helm upgrade --set-file signedConfig=signed.yaml" { style.fill: "#FFF6EF" }
render: "Helm render (single input)"cm: "ConfigMap: signed bytes, verbatim"derived: "parent values: egress allowlist + ports (fromYaml)"
parent: "Parent proxy: forwards bytes, NO verify"enclave: "Enclave: verify vs PCR0-baked CONFIG_SIGNERS"
author -> signsign -> helmhelm -> renderrender -> cmrender -> derivedcm -> parent: "mount /config/config.yaml"parent -> enclave: "forward over vsock"derived -> parent: "wire vsock-proxy + ports"enclave -> enclave: "boot iff quorum verifies, else refuse"Prerequisites
Section titled “Prerequisites”-
Signer keys. An operator key on a YubiKey PIV (ECDSA P-256, slot
9c) and/or a software Ed25519 key for CI. Operators use P-256 (not Ed25519) so signing works on any YubiKey 5.x; Ed25519-in-PIV needs firmware ≥ 5.7. -
Trust manifest baked into the EIF. Your signers’ public keys, threshold, and required IDs live in
custody/config-signers.ron, baked into the enclave image at build time (CHAMBER_SIGNERS_FILE) and measured into PCR0. See the manifest format in EKS Enclave Deployment. A signed-Nitro EIF built without it refuses to boot.
Sign and deploy
Section titled “Sign and deploy”-
Freeze the config and stamp a generation.
bundle --createedits in place, normalizes the signed region to end in exactly oneLF, and writes a trailer carrying generationN(monotonic,>= 1). Keep the unsigned original.Terminal window cp config.yaml signed.yamlcontainment-chamber config bundle --file signed.yaml --create --generation N -
Emit the exact bytes to sign. This is the canonical message
DOMAIN ‖ generation ‖ signed_region— domain- and generation-bound, so a signature cannot be replayed across generations.Terminal window containment-chamber config sign-payload --file signed.yaml > payload.bin -
Sign the payload with each signer. Helper scripts wrap the exact algorithm so you cannot accidentally produce a signature the enclave will reject:
Terminal window # YubiKey PIV operator (ECDSA P-256, slot 9c) -> raw DER signaturescripts/sign-config-piv.sh payload.bin operator-a.sig# software Ed25519 (CI) -> raw 64-byte signaturescripts/sign-config-ed25519.sh ci-ed25519.key payload.bin > github-ci.sig -
Embed each signature into the trailer (base64). Repeatable — run once per signer. The
<signer-id>must match anidinconfig-signers.ron.Terminal window containment-chamber config bundle --file signed.yaml --add operator-a=operator-a.sigcontainment-chamber config bundle --file signed.yaml --add github-ci=github-ci.sig -
Verify locally (optional, but see the caveat below).
Terminal window containment-chamber config verify --file signed.yaml -
Deploy.
--set-fileloads the file’s bytes as thesignedConfigvalue, verbatim.Terminal window helm upgrade containment-chamber ./k8s/charts/containment-chamber \--set-file signedConfig=signed.yaml
Signature algorithms
Section titled “Signature algorithms”| Role | Key | Signature emitted | Helper |
|---|---|---|---|
operator |
YubiKey PIV, ECDSA P-256 | DER, over SHA-256(message) |
scripts/sign-config-piv.sh |
ci |
software Ed25519 | raw 64-byte, over message |
scripts/sign-config-ed25519.sh |
The alg is bound to the signer’s manifest entry and is never read from the wire.
How --set-file triggers a rollout
Section titled “How --set-file triggers a rollout”--set-file signedConfig=signed.yaml injects the file’s bytes as an opaque string value — it does
not parse the YAML and does not compute any hash. The rolling restart on a config change
comes from a separate, chart-side annotation on the pod template:
checksum/config: {{`{{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}`}}When signedConfig changes, the rendered ConfigMap changes, so its sha256sum changes, so the pod
template changes, so helm upgrade performs a rolling restart. Editing a ConfigMap alone never
restarts pods — this checksum is what turns a byte change into a redeploy.

