Skip to content

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.

ModeCertificate sourceRotationUse case
disabledDefault; no HTTPS
filePEM files on diskFile replacement + pollcert-manager, Vault PKI, self-signed
atlsAuto-generated, bound to enclave attestationAutomaticAWS Nitro Enclave deployments

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)”
Terminal window
openssl req -x509 -newkey rsa:4096 \
-keyout tls.key -out tls.crt \
-days 365 -nodes \
-subj "/CN=containment-chamber"
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 polling

Or via environment variables:

Terminal window
CONTAINMENT_TLS__MODE=file
CONTAINMENT_TLS__LISTEN_PORT=9443
CONTAINMENT_TLS__FILE__CERT_PATH=/etc/certs/tls.crt
CONTAINMENT_TLS__FILE__KEY_PATH=/etc/certs/tls.key

Create a Secret from your cert and key, then mount it into the pod:

apiVersion: v1
kind: Secret
metadata:
name: containment-chamber-tls
type: kubernetes.io/tls
data:
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-tls

With 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.

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.


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:

  1. The server is running in a genuine AWS Nitro Enclave
  2. The enclave binary, kernel, parent IAM role, and signing certificate match the expected measurements (PCR0, PCR1, PCR2, PCR3, PCR8)
  3. 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.

tls:
mode: atls
listen_port: 9443
atls:
cert_validity_seconds: 86400 # cert lifetime: 24 hours
rotation_interval_seconds: 3600 # rotate ephemeral key pair every hour

No cert files to manage. The signer generates and rotates certs automatically.

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.

Diagram

Clients that support aTLS verify the attestation document before trusting the connection:

  1. The client receives the TLS certificate during the handshake
  2. It extracts the attestation document from the X.509 extension
  3. It verifies the attestation with the AWS Nitro attestation service
  4. It checks the PCR measurements match the expected enclave binary
  5. 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:

Terminal window
containment-chamber operator status \
--signer-url https://localhost:9443 \
--danger-skip-attestation-verification

When TLS is enabled, two listeners run side by side:

PortProtocolPurpose
9000 (default)HTTPHealth probes, all API routes
9443 (default)HTTPSAll 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.

Diagram

“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.