Attestation TLS (aTLS)
Containment Chamber supports two TLS modes: file for traditional certificate-based HTTPS, and atls (attestation TLS) for deployments in AWS Nitro Enclaves. Either mode adds an HTTPS listener on port 9443 alongside the existing HTTP listener on port 9000.
When TLS is enabled, the HTTP listener stays up for health probes. Kubernetes liveness and readiness probes can hit /upcheck on port 9000 without needing client certificates or TLS at all. The Prometheus metrics endpoint (:3000) is always HTTP-only regardless of TLS mode.
Modes at a Glance
Section titled “Modes at a Glance”| Mode | Certificate source | Rotation | Use case |
|---|---|---|---|
disabled | — | — | Default; no HTTPS |
file | PEM files on disk | File replacement + poll | cert-manager, Vault PKI, self-signed |
atls | Auto-generated, bound to enclave attestation | Automatic | AWS Nitro Enclave deployments |
File Mode
Section titled “File Mode”File mode reads a certificate and private key from disk and polls for changes on a configurable interval. Cert rotation doesn’t require a restart — replace the files atomically and the signer picks them up on the next poll.
Generate a self-signed certificate (dev/test)
Section titled “Generate a self-signed certificate (dev/test)”openssl req -x509 -newkey rsa:4096 \ -keyout tls.key -out tls.crt \ -days 365 -nodes \ -subj "/CN=containment-chamber"Configure
Section titled “Configure”tls: mode: file listen_port: 9443 file: cert_path: /etc/certs/tls.crt key_path: /etc/certs/tls.key reload_interval_seconds: 60 # 0 = disable pollingOr via environment variables:
CONTAINMENT_TLS__MODE=fileCONTAINMENT_TLS__LISTEN_PORT=9443CONTAINMENT_TLS__FILE__CERT_PATH=/etc/certs/tls.crtCONTAINMENT_TLS__FILE__KEY_PATH=/etc/certs/tls.keyKubernetes: mount a TLS Secret
Section titled “Kubernetes: mount a TLS Secret”Create a Secret from your cert and key, then mount it into the pod:
apiVersion: v1kind: Secretmetadata: name: containment-chamber-tlstype: kubernetes.io/tlsdata: tls.crt: <base64-encoded cert> tls.key: <base64-encoded key>Reference it in the Pod spec:
spec: containers: - name: containment-chamber volumeMounts: - name: tls mountPath: /etc/certs readOnly: true env: - name: CONTAINMENT_TLS__MODE value: file - name: CONTAINMENT_TLS__FILE__CERT_PATH value: /etc/certs/tls.crt - name: CONTAINMENT_TLS__FILE__KEY_PATH value: /etc/certs/tls.key volumes: - name: tls secret: secretName: containment-chamber-tlsWith cert-manager, annotate the Secret and let the controller rotate it. Point cert_path and key_path at the mounted volume path and set reload_interval_seconds: 60 — the signer will pick up renewed certs without a restart.
Cert rotation
Section titled “Cert rotation”Replace the cert and key files atomically (write to a temp file, then mv) and wait up to reload_interval_seconds for the signer to reload. The old cert continues serving existing connections until the poll fires.
aTLS Mode
Section titled “aTLS Mode”Attestation TLS generates an ephemeral key pair at startup and binds its public key hash (SPKI) into an AWS Nitro Secure Module (NSM) attestation document. That document is embedded in the X.509 certificate as a custom extension.
Clients that perform attestation verification get three guarantees from the TLS handshake:
- The server is running in a genuine AWS Nitro Enclave
- The enclave binary, kernel, parent IAM role, and signing certificate match the expected measurements (PCR0, PCR1, PCR2, PCR3, PCR8)
- The TLS session goes to that specific enclave — not an interceptor
aTLS requires the signer compiled with the nitro feature and must run inside an actual enclave. See the Enclave deployment guide for infrastructure setup.
Configuration
Section titled “Configuration”tls: mode: atls listen_port: 9443 atls: cert_validity_seconds: 86400 # cert lifetime: 24 hours rotation_interval_seconds: 3600 # rotate ephemeral key pair every hourNo cert files to manage. The signer generates and rotates certs automatically.
How cert rotation works
Section titled “How cert rotation works”Every rotation_interval_seconds, the signer generates a new ephemeral key pair, obtains a fresh NSM attestation document binding the new public key hash, and starts serving the updated certificate to new connections. Active TLS sessions continue using the previous cert until they complete — there’s no connection drop.
Client-side verification (TOFU model)
Section titled “Client-side verification (TOFU model)”Clients that support aTLS verify the attestation document before trusting the connection:
- The client receives the TLS certificate during the handshake
- It extracts the attestation document from the X.509 extension
- It verifies the attestation with the AWS Nitro attestation service
- It checks the PCR measurements match the expected enclave binary
- It records those PCR values — future connections from a different binary are rejected
The first connection establishes trust. Subsequent connections verify the PCRs match. A cert from a different binary (even with a valid attestation) won’t be accepted.
Skip attestation verification (debugging only)
Section titled “Skip attestation verification (debugging only)”During local development or testing outside a Nitro Enclave, you can disable attestation verification on the client side:
containment-chamber operator status \ --signer-url https://localhost:9443 \ --danger-skip-attestation-verificationDual-Listener Architecture
Section titled “Dual-Listener Architecture”When TLS is enabled, two listeners run side by side:
| Port | Protocol | Purpose |
|---|---|---|
| 9000 (default) | HTTP | Health probes, all API routes |
| 9443 (default) | HTTPS | All API routes (encrypted) |
Both listeners serve the full API. The HTTP listener stays up so Kubernetes probes and Prometheus scrapers don’t need TLS configured. Use your ingress controller or network policy to route validator client traffic to the HTTPS port only.
Troubleshooting
Section titled “Troubleshooting”“certificate file not found”
Check that cert_path and key_path are absolute paths and the signer process can read them. In Docker and Kubernetes, verify the volume is mounted at the expected path and the file names match exactly.
“TLS handshake failed” on the validator client side
The client doesn’t trust the signer’s certificate authority. For self-signed certs, either add the cert to the client’s trust store or configure the client to skip cert verification (acceptable in private networks, not for public endpoints).
aTLS: “attestation verification failed”
The PCR measurements in the attestation document don’t match what the client expects. This happens after a binary update (PCRs change) or when running outside a Nitro Enclave. Use --danger-skip-attestation-verification for local development only; binary upgrades require the client to re-establish trust via a new TOFU handshake.
HTTPS listener not starting
Check the startup logs for TLS initialization errors. Common causes: cert and key are from different key pairs, the key file has wrong permissions (must be readable by the signer process), or the cert PEM is malformed.
Port 9443 already in use
Set tls.listen_port to a free port and update your Kubernetes Service, ingress rules, and validator client configuration to match.