Threat Model

The adversary c8s defends against, where the trust boundary sits, which gates are enforced today, and why measurement pinning is mandatory for production.

c8s is designed around a single, demanding assumption: the infrastructure operator is adversarial. That includes the cloud provider, the bare-metal operator, and anything that can read raw host memory or inspect a QEMU command line. The trust boundary is the TEE — the node (an AMD SEV-SNP or Intel TDX confidential VM) under node-as-CVM, or each pod's own confidential VM under pod-as-CVM (Kata).

The Kubernetes control plane is on the far side of that boundary too. etcd sees only ciphertext and public material for the keys c8s holds in the TEE — the mesh CA, the EAR (Entity Attestation Result) issuer, RA-TLS leaf keys — because none of them is ever written to a Kubernetes Secret. Ordinary Kubernetes Secrets are not covered: image-pull credentials, the webhook caBundle, and any tenant Secret are plaintext to whoever reads etcd.

What the adversary can and cannot do

The left column is the baseline you are coming from: an ordinary Kubernetes cluster on non-confidential hardware. Node-as-CVM gives the same protections as the right column, but at node granularity rather than per pod.

Ordinary KubernetesPod-as-CVM (Kata)
Read workload memoryYes (host-trusted)No — the TEE encrypts pod memory
Tamper with the runtime undetectedn/aNo — tampering changes the launch measurement; attestation fails
See the unpacked workload rootfsYesNo — images are pulled and unpacked inside the guest
See which image a pod runsYesYes — the host brokers the pull, so it observes the reference and layers (metadata, not content)
Observe inter-node trafficMitigated by RA-TLS meshMitigated by RA-TLS mesh
Read a running container's stdoutYesNo — the locked guest policy denies ReadStreamRequest and ExecProcessRequest, so kubectl logs is empty by design

Under pod-as-CVM, if the host tampers with the Kata runtime, QEMU, or the shim, it changes the pod's launch measurement, and attestation fails. The host can break a pod, but it cannot break it silently — detection is by attestation, not by trust.

A co-tenant pod is not the confidentiality adversary under node-as-CVM

Node-as-CVM is a single-tenant shape. The boundary is the node, the adversary (the cloud or bare-metal operator) sits outside it, and pods inside the node are separated only by the kernel — as they would be on any Kubernetes cluster.

What c8s does enforce inside the node is least privilege between workloads: a compromised pod must not be able to obtain another pod's identity. When CDS issues a certificate it asks the node's admission inventory which pod is behind the requesting key, and the inventory answers on a privileged port that only the node's own network namespace can bind. The c8s-deny-host-namespaces ValidatingAdmissionPolicy (hostNamespacePolicy.enabled, on by default) denies tenant pods hostNetwork, hostPID, hostIPC, and hostPath volumes, which is what keeps a tenant pod out of that namespace and stops it from answering as the inventory.

Privileged node DaemonSets — a CNI, a CSI driver, the NVIDIA GPU operator — can bind that port. They are already root inside the node CVM and can read another pod's memory directly, so c8s assumes them to be part of the node's trusted computing base rather than checking them. The policy exempts the release namespace, kube-system, and anything you add to hostNamespacePolicy.exemptNamespaces, so admitting one of those DaemonSets is a visible decision in your values file.

Under pod-as-CVM each pod is its own CVM and the separation is hardware-backed instead.

Where the boundary sits

  OUTSIDE the TEE, and still deciding what the TEE will accept:
    CI · ghcr.io ────────────►  the image digests baked into the measurement
    your operator key ───────►  who may rewrite the served allowlist
    your measurement pins ───►  which builds CDS and the mesh talk to

 ╔═ TEE BOUNDARY · HOST CANNOT READ ══════════════════════════════════════════╗
 ║                                                                            ║
 ║  attestation-api           CDS                         workload            ║
 ║  verifies hardware         mesh CA + EAR issuer,       prompts · weights   ║
 ║  evidence                  one process                 KV cache            ║
 ║       │                         │                           │              ║
 ║       └───── verdict ──────────►│──── leaf cert ───────────►│              ║
 ║                                 │                                          ║
 ║                                 │ CA bundle                                ║
 ║                                 ▼                                          ║
 ║                            ratls-mesh  ◄── attested mTLS ──►  peers        ║
 ╚════════════════════════════════════════════════════════════════════════════╝

  ALSO outside, and assumed hostile:
    hypervisor · host OS · kubelet · containerd · Kubernetes API · etcd · CNI

Two different kinds of "outside". The supply chain never touches your data but decides what the platform will accept, so it is load-bearing; the host decides nothing and is assumed hostile.

What is enforced today

GateEnforced bySource of truth
TEE evidence is validattestation-api, co-located with CDS in the same TCBhardware evidence verification
A CSR can be signedCDSverified evidence, plus cds.measurements when set
An image — and the argv it runs with — is allowednri-image-policy on the host, or in-guest policy-monitor under KataCDS-served allowlist, plus the seed baked into the measured image
Mesh peer chains to the mesh CAratls-meshmesh CA bundle (chain only — the peer's measurement is not pinned)
Pod is an injection candidatemutating admission webhook, failurePolicy: Failpod annotation confidential.ai/cw
A confidential.ai/cw pod cannot shed its injected c8s-cert sidecarc8s-cw-label-integrity ValidatingAdmissionPolicyAPI server
A tenant pod cannot take the node's network, PID, or IPC namespace, or a hostPathc8s-deny-host-namespaces ValidatingAdmissionPolicyAPI server
The front door's attestation is bound to this cluster's identityc8s cds-attest sidecara TEE report whose report_data transcript commits the session keys, the client nonce, the exact mesh leaf, and its issuing CA

Two of these carry a caveat worth stating. The attestation-api's verdict is unsigned, so the gate holds because the verifier runs inside the same TCB as CDS — not because the response is authenticated. And the image gate covers the digest and the effective argv (see Allowlist); it does not cover env, mounts, capabilities, or the rest of the pod spec.

Measurement pinning is mandatory for production

The default chart pins no launch measurements. Both cds.measurements and ratlsMesh.measurements default to empty, which means "accept any attested peer". That is fine for a demo or a dev loop, but it is not the production trust model:

With empty measurements, an attacker on the pod network can stand up their own genuine TEE enclave (same open-source image, valid measurement, real silicon) and MITM the bootstrap — every signature checks out, but you are talking to an attacker-operated enclave. Pin both cds.measurements and ratlsMesh.measurements to your known-good launch digests in production — see Obtaining launch measurements for how to get those values.

Under node-as-CVM, c8s install --measurements <M> pins both values in one step: M is the node image's launch measurement, published in its manifest.json, and it is known before the cluster runs. The flag is rejected with --cvm-mode=pod, where each Kata guest is measured separately and the pins are per-guest.

Empty is not a bug — it is "pin nothing yet", and only you know your measurements. CDS and ratls-mesh both log loudly when their allowlist is empty, and ratls-mesh publishes ratls_mesh_measurement_pinning=0 for alerting. Neither signal makes accept-any safe.

Proving it is your cluster

A launch measurement proves "genuine audited code on real silicon". It does not prove "my cluster" — the open-source images are deliberately fungible across deployments, so anyone can boot the same build and produce a valid measurement. An outside verifier therefore has to pin a cluster-unique anchor as well: today, the mesh CA certificate.

Those two pins now authenticate a cluster. The front door's attestation binds the exact mesh leaf and its issuing CA into the report_data transcript, and signs that transcript with the leaf's private key, so copying a target cluster's public certificate chain is not enough to impersonate it. The client verifies the hardware signature, the measurement against its allowlist, the transcript, the chain to the pinned mesh CA, and the possession proof before any application traffic flows. The proof is ECDSA, so cluster authentication is classical; the session keys are X25519 + ML-KEM-768 hybrid, which covers confidentiality but is not a post-quantum authentication claim.

See consumer verification for the client side of this.

The production direction

The chart-managed default keeps the CA key in CDS process memory: any restart generates a new CA and invalidates old leaves, and there is a single CDS. The production direction holds the CA key only inside attested CVM memory and lets replicas join via attested key handoff (cds.handoff.enabled=true), so Kubernetes only ever sees ciphertext and public material. A joining replica must present a launch digest in cds.measurements and a REPORTDATA-bound hash of the operator-key set that exactly matches the serving CDS, so a hostile control plane cannot hand the mesh CA to a replica running a substituted allowlist-write policy. The chart refuses to render if either value is empty while handoff is enabled. See CDS bootstrapping.

Out of scope (today)

These are known gaps, tracked but not yet enforced — see Limitations for the full list:

  • The image gate covers the digest and argv, but not env, mounts, capabilities, or the rest of the pod spec.
  • Mesh peer verification chains to the mesh CA but does not pin the peer's measurement.
  • The sandbox ID stamped into a leaf is vouched for by the mesh CA signature and the node's admission inventory, not by hardware evidence.
  • No per-workload peer allowlists in the mesh; no multi-tenant isolation between workloads on the same node CVM beyond what admission policy provides.

And these are permanent non-goals: side-channel attacks (micro-architectural, timing, power), denial of service and availability, application-layer vulnerabilities in your own workload, model distillation or dataset reconstruction from legitimate query access, physical attacks on the host, and a compromised hardware manufacturer.