Components & Data Flow

The c8s components, how they fit together, and the difference between the two trust boundaries — node-as-CVM and pod-as-CVM (Kata) — including the certificate and attestation flows.

c8s adds five capabilities to a Kubernetes cluster: TEE attestation verification, certificate management (RA-TLS + a mesh CA), RA-TLS mesh networking, container-image policy enforcement, and an attested public front door. This page describes the components that provide them and how requests flow between them.

The components

ComponentImageNode-as-CVM (--cvm-mode=node)Pod-as-CVM (--cvm-mode=pod)
CDS — Certificate Distribution Servicecdsrunc containerits own Kata CVM
c8s operator + admission webhookc8s-operatorrunc container (untrusted host)runc container (untrusted host, exempt)
attestation-apiattestation-apibaked into the node image, host loopback :8400baked into the guest image, 127.0.0.1:8400
ratls-meshratls-meshhost DaemonSet (L4 proxy)in-guest, baked into the guest image
nri-image-policynri-image-policybaked into the node image (NRI plugin)in-guest policy-monitor, baked into the guest image
get-cert — the c8s-cert sidecarc8s-operatorwebhook-injected native sidecarwebhook-injected native sidecar
get-secret — the c8s-secret sidecarc8s-operatorwebhook-injected native sidecar, on confidential.ai/c8s-secretsrejected at admission
get-volume — the c8s-volume sidecarc8s-operatorwebhook-injected native sidecar, on confidential.ai/c8s-volumesrejected at admission
tls-lb — the public front doorc8s-operator + upstream nginxDeployment: nginx, cds-attest, allowlist proxysame, pinned to a Kata CVM
volumed — encrypted-volume node agentvolumednode DaemonSet, off by defaultnode DaemonSet, off by default; no pod can request a volume here

Every c8s image ships the same multi-mode c8s binary and differs only in its entrypoint subcommand and base layer, so a role is a command, not a separate program — the chart runs c8s-operator for the controller, the injected c8s-cert, c8s-secret, and c8s-volume sidecars, and the tls-lb sidecars alike. Most images are distroless; ratls-mesh is debian-slim because it needs iptables, and volumed is debian-slim because it needs cryptsetup/veritysetup. All are published under ghcr.io/confidential-dot-ai/, alongside a standalone get-cert image for use outside the chart. Two images come from elsewhere: attestation-api is built from the Rust attestation-rs tree, and tls-lb's web server is upstream nginxinc/nginx-unprivileged.

Where those services live depends on the shape. Under --cvm-mode=node the confidential node image (c8s-base, built and published by c8s CI on every merge to main) bakes the attestation-api and a fail-closed nri-image-policy, so the chart installs neither as a workload; ratls-mesh is not baked and stays a DaemonSet. Under --cvm-mode=pod the same services are baked into the measured Kata guest image instead. On the managed shapes (--cvm-mode=gke, --cvm-mode=aks) the node image is the cloud's, not ours, so the chart installs attestation-api and nri-image-policy as ordinary host workloads — and there the chart requires nri-image-policy: nriImagePolicy.enabled=false fails the render, because it is the only image-admission gate that would be left running.

The CDS is the trust root. It performs attestation verification, mesh-CA signing, and optional CA handoff all in one process, so there is no internal RA-TLS hop to an external signer and the CA private key never leaves CDS memory.

The c8s operator and its admission webhook are untrusted. Even under pod-as-CVM (Kata) they stay in a plain runc container on the host — outside the per-pod TEE boundary — and they are exempt from the workload enforcement they configure. They are control-plane orchestration, not part of the trust boundary: a compromised operator can disrupt scheduling (e.g. refuse to inject get-cert, which fails closed under the webhook's Fail policy) but it cannot forge attestation, mint certificates, or read TEE memory — those happen inside the CDS, in the TEE. The cluster's confidentiality never depends on trusting the operator.

The admission inventory

The image-policy component does double duty, and the second job is easy to miss. Whichever enforcer admitted a pod's containers — nri-image-policy on the node, policy-monitor inside the Kata guest — is also the admission inventory: the authority on which pod sandbox a process belongs to, and on what is actually running in that sandbox.

It serves two disjoint surfaces. A local token surface mints an inventory-signed sandbox token for the calling process, where the caller is identified by kernel peer credentials (node-as-CVM) or by the guest boundary itself (pod-as-CVM, one pod per guest) — never by anything the caller sends. A separate network surface, on a privileged port reachable only from the node's own network namespace and over mutually-attested RA-TLS, answers CDS's queries for the inventory's signing key and for a sandbox's current image digests.

CDS uses both when issuing a leaf: it verifies the token, gates issuance on every running image being allowlisted, and stamps the sandbox ID into the certificate's signed area. That is what lets a relying party ask "which workload is behind this key?" rather than only "is this a genuine TEE?". The mesh CA signature — not hardware evidence — is what vouches for that ID; see Sandbox identity for the full treatment.

Node-as-CVM and Pod-as-CVM

The trust boundary is a deployment-time choice — see Pod-as-CVM vs Node-as-CVM for the full tradeoff. c8s install requires you to state it with --cvm-mode (pod, node, gke, or aks; there is no default), and Kata is the mechanism c8s uses to give each pod its own CVM — not the name of a mode:

  • Node-as-CVM (--cvm-mode=node, and the managed gke / aks variants) — the c8s services run as ordinary node containers and the mesh and attestation operate at node level. The node itself is a confidential VM (an Azure AKS or GKE confidential node, or a bare-metal AMD SEV-SNP / Intel TDX host), so the whole node is the trust boundary and everything on it inherits it. One launch digest covers the lot, which is why c8s install --measurements <M> can pin the whole mesh in a single step here.
  • Pod-as-CVM (--cvm-mode=pod) — each workload pod runs in its own confidential VM, launched via Kata Containers. The security services (attestation-api, ratls-mesh, policy-monitor) are baked into the measured guest image, so they are covered by the launch measurement and the host — adversarial here, since it sits outside every pod CVM — cannot MITM them. Both AMD SEV-SNP (kata-qemu-snp) and Intel TDX (kata-qemu-tdx) are wired, selected per install by --hardware-platform; a cluster runs one CPU TEE, not both.

Pod-as-CVM enforcement is two cooperating pieces — both, or nothing:

  1. A mutating webhook injects a runtimeClassName into workload pods that lack one — the confidential class (kata-qemu-snp or kata-qemu-tdx, and their -nvidia variants for GPU pods) for pods annotated confidential.ai/cw, or kata-qemu otherwise.
  2. A ValidatingAdmissionPolicy (c8s-kata-enforcement) rejects workload pods that request a non-Kata runtimeClassName.

The webhook's failure policy is Fail: if the webhook is unreachable, pod creation is rejected, so an unmutated runc pod can never slip through. Two further policies back it up in both shapes — c8s-cw-label-integrity denies a confidential.ai/cw pod that has shed its injected c8s-cert sidecar, and c8s-deny-host-namespaces denies tenant pods hostNetwork, hostPID, hostIPC, and hostPath volumes, which is what keeps the admission inventory's privileged port out of a tenant's reach.

Trust boundaries

Under node-as-CVM, the boundary is the node. The whole node — c8s services and workload pods alike — sits inside one TEE the host cannot read:

 ╔═ TEE BOUNDARY · the whole node is one CVM, host cannot read ═══════════════╗
 ║                                                                            ║
 ║   ┌──────────────── Kubernetes node = one confidential VM ─────────────┐   ║
 ║   │  attestation-api · nri-image-policy                                │   ║
 ║   │    (baked into the node image on --cvm-mode=node;                  │   ║
 ║   │     chart DaemonSets on gke / aks)                                 │   ║
 ║   │  chart-installed:                                                  │   ║
 ║   │    CDS · ratls-mesh · operator + webhook · tls-lb                  │   ║
 ║   │  workload pods (runc), each with a get-cert sidecar                │   ║
 ║   └────────────────────────────────────────────────────────────────────┘   ║
 ╚════════════════════════════════════════════════════════════════════════════╝

   host side (adversarial): hypervisor · cloud or bare-metal operator

Where those two components live depends on whether you control the node image. On --cvm-mode=node you build and measure it yourself, so they are baked in and covered by the node's launch digest. On the managed shapes (gke, aks) the provider owns the image and c8s cannot add to it, so the chart runs them as DaemonSets inside the same CVM boundary instead.

One launch digest covers everything on the node, including the kubelet.

The corollary is that pods inside the node are separated only by the kernel. That is fine — node-as-CVM is a single-tenant shape and the adversary is outside the node — but see the threat model for what c8s does enforce between workloads on one node.

Under pod-as-CVM, the boundary is each pod's own CVM; the host is outside every one of them:

 ╔═ TEE BOUNDARY · per-pod confidential CVM, host cannot read ════════════════╗
 ║                                                                            ║
 ║   ┌──── kata-qemu-snp/tdx CVM ────┐    ┌──── kata-qemu-snp/tdx CVM ────┐   ║
 ║   │ CDS                           │    │ workload                      │   ║
 ║   │   RA-TLS serving cert         │    │   + c8s-cert sidecar          │   ║
 ║   │   (SNP / TDX evidence)        │    │   (leaf cert from CDS)        │   ║
 ║   │ baked in: attestation-api ·   │    │ baked in: attestation-api ·   │   ║
 ║   │   ratls-mesh · policy-monitor │    │   ratls-mesh · policy-monitor │   ║
 ║   └───────────────────────────────┘    └───────────────────────────────┘   ║
 ╚════════════════════════════════════════════════════════════════════════════╝

   host side (adversarial): containerd · kata-runtime · kata-shim · operator

Every pod carries its own launch digest, so each attests independently.

Certificate flow — node-as-CVM

A workload obtains a TEE-bound certificate from the CDS in one issuance. The nonce CDS mints for the challenge is reused as the sandbox token's nonce, so one single-use value binds both the hardware evidence and the identity claim — there is no clock and no replay window:

   Workload             CDS                attestation-api   admission
   [c8s-cert]                              [same TCB]        inventory
      │                    │                     │                 │
      │ POST /authenticate │                     │                 │
      │───────────────────►│                     │                 │
      │ single-use nonce   │                     │                 │
      │◄ ─ ─ ─ ─ ─ ─ ─ ─ ─ │                     │                 │
      │                    │                     │                 │
      │ POST /sandbox — CSR public key + that nonce                │
      │───────────────────────────────────────────────────────────►│
      │ inventory-signed sandbox token           │                 │
      │◄ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
      │                    │                     │                 │
      │   report_data = SHA-384(CSR public key ‖ nonce)            │
      │                    │                     │                 │
      │ POST /attest — evidence + CSR + token    │                 │
      │───────────────────►│                     │                 │
      │                    │ verify(evidence, report_data)         │
      │                    │────────────────────►│                 │
      │                    │ valid · launch_digest                 │
      │                    │◄ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│                 │
      │                    │ GET /identity · GET /digests/{sandbox}│
      │                    │──────────────────────────────────────►│
      │                    │                     │                 │
      │   check measurement · every running image allowlisted      │
      │   validate CSR · sign with the mesh CA   │                 │
      │                    │                     │                 │
      │ leaf cert + CA chain, sandbox ID in the signed area        │
      │◄ ─ ─ ─ ─ ─ ─ ─ ─ ─ │                     │                 │

get-cert never reports its pod's images — CDS reads them live from the component that admitted them.

Certificate flow — pod-as-CVM (Kata)

Under pod-as-CVM the same flow happens inside the pod's TEE boundary. The CDS mints its own RA-TLS serving cert from its hardware evidence, the workload reaches it over RA-TLS, and the attestation-api is the in-guest service at 127.0.0.1:8400. The inventory is policy-monitor in the same guest, reached on guest loopback rather than a mounted socket, so there is no caller to disambiguate — one pod per guest. The trust anchor is the launch measurement: CDS, attestation-api, ratls-mesh, and policy-monitor are all baked into the guest image and therefore covered by it.

 ╔═ SEV-SNP / TDX BOUNDARY · HOST CANNOT READ ══════════════════════════════╗
 ║                                                                          ║
 ║    Workload CVM          CDS CVM                in-guest services        ║
 ║    [c8s-cert]            [mesh CA]              [127.0.0.1]              ║
 ║        │                     │                       │                   ║
 ║        │ RA-TLS connect, verify CDS measurement      │                   ║
 ║        │────────────────────►│                       │                   ║
 ║        │                     │                       │                   ║
 ║        │ sandbox token, over guest loopback          │                   ║
 ║        │◄────────────────────────────────────────────►                   ║
 ║        │                     │                       │                   ║
 ║        │ POST /attest — evidence + CSR + token       │                   ║
 ║        │────────────────────►│                       │                   ║
 ║        │                     │ verify in-process     │                   ║
 ║        │                     │──────────────────────►│                   ║
 ║        │                     │ verdict + launch_digest                   ║
 ║        │                     │◄ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│                   ║
 ║        │                     │                       │                   ║
 ║        │ leaf cert from the mesh CA                  │                   ║
 ║        │◄ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│                       │                   ║
 ╚══════════════════════════════════════════════════════════════════════════╝

Nothing in this exchange crosses the host — the whole issuance is inside measured memory.

The public front door

External traffic enters through tls-lb, a Deployment whose pod holds nginx alongside three c8s containers: a c8s-cert sidecar that keeps nginx's serving certificate fresh, and the two below. Under Kata the whole pod is pinned to a confidential RuntimeClass, because it terminates client TLS with mesh-issued keys and that plaintext must stay inside the TEE.

  • cds-attest (tlsLb.attest.enabled, on by default) serves the browser-facing c8s-verify/v1 protocol on loopback: the per-session attestation challenge and the post-quantum over-encryption handshake. nginx reverse-proxies /.well-known/c8s/ to it. Exposing it does not weaken the server — it publishes a signed quote of its own TEE and nothing else. Trust is established on the client, which must pin expected measurements.
  • The allowlist proxy (tlsLb.allowlist.enabled, on by default) publishes CDS's complete /allowlist API through the front door. It verifies CDS's RA-TLS evidence before forwarding, so the public hop and the in-cluster hop are both attested. Reads stay unauthenticated at CDS and are rate-limited per client; writes still require the short-lived, body-bound operator token, and the operator private key never enters the cluster. See Allowlist.

The protocol endpoints serve wide-open CORS by default.

tlsLb.cors.protocolEndpoints defaults to true, which sends Access-Control-Allow-Origin: * (with no credentials) on /.well-known/c8s/, the discovery document, the certificate endpoints, and the built-in /allowlist route. Those responses are self-authenticating — hardware evidence, CDS-signed certificates, sealed tunnel records — and no request on them relies on ambient browser credentials, so an origin allowlist would block third-party verifiers without protecting anything. Your workload routes and the catch-all upstream are never covered by this flag; set tlsLb.cors.enabled if you want an origin policy of your own, and it then governs every location.

Install & admission ordering

The install sequence is load-bearing, and the operator — not a Helm hook — closes the loop:

  1. Helm creates the MutatingWebhookConfiguration along with the rest of the release. Its caBundle is empty at this point, so the API server cannot yet reach the webhook.
  2. The operator starts, mints a long-lived webhook CA and a short-lived serving leaf, and patches the caBundle onto the webhook configuration. The patch is idempotent, and an in-process rotator re-mints the leaf under the stable CA so the bundle never needs re-patching.
  3. Once the operator's cache is up it runs a one-shot reinject sweep: any pod annotated confidential.ai/cw that was admitted during the gap — and so never got its c8s-cert sidecar — is deleted so its controller recreates it through the webhook. Pods with no controller owner are logged and left running, because deleting them would destroy the workload with no recreate path.
  4. On uninstall, where the chart installed the host NRI plugin, a pre-delete hook runs a privileged DaemonSet that removes it from every node's containerd configuration before the release goes away.

Next: the threat model makes the trust assumptions explicit and lists what is — and isn't — enforced today.