Limitations

Known gaps and deliberate non-goals in the current c8s milestone — what is not yet enforced, and what to plan around.

c8s is built around a strong threat model, but not every gap is closed yet. This page is the honest list of what the current milestone does not do, so you can plan around it. Several items are deliberate non-goals for now; others are tracked work.

Trust and enforcement gaps

  • Measurements are not pinned by default. The chart ships cds.measurements and ratlsMesh.measurements empty, which means "accept any attested peer". Both the CDS and ratls-mesh log warnings when their allowlists are empty, and ratls-mesh publishes ratls_mesh_measurement_pinning=0, but the accept-any policy still stands until you set the values. On a node-shaped install (--cvm-mode=node, gke, aks), c8s install --measurements <LAUNCH_DIGEST> fans one digest into both; under --cvm-mode=pod the flag is rejected, because each Kata guest is measured separately — set the values directly. See the threat model.
  • Mesh peer verification doesn't pin the peer measurement. A mesh peer is accepted if its certificate chains to the mesh CA. Issued leaves do now carry the evidence the CDS (Certificate Distribution Service) verified at issuance, and the RA-TLS (Remote-Attestation TLS) verify policy has a RequireCAEvidence mode that re-checks it — measurement included — on every connection, but no shipped profile enables it.
  • A workload's sandbox identity is CA-vouched, not hardware-bound. c8s verify --sandbox-id checks the CRI sandbox ID stamped into a leaf, but that ID is signed in by the mesh CA on the word of an on-node admission inventory; it is not folded into the Trusted Execution Environment (TEE) report. Any process that can bind the node's privileged inventory port — including a privileged node DaemonSet — can vouch for a sandbox it does not run, and a compromised mesh CA can mint any ID. Treat it as CA-vouched identity, not attested identity.
  • The allowlist gates each container's digest and command line — not the rest of the pod spec. Floor entries admit an image by digest alone. Workload entries additionally pin each container's command prefix and args remainder against the effective argv, and all three enforcement points — nri-image-policy, in-guest policy-monitor, and the CDS at certificate issuance — apply it. Env, mounts, capabilities, and the remaining pod-spec fields are not checked.
  • Nothing enforces which images run together. Every running image must be allowlisted, but no gate requires the set that runs in one pod to match a single workload entry. Container-start gates see one container at a time and cannot detect a missing one; the CDS sees the whole reported set but only at issuance, mid-lifecycle, so it checks membership rather than composition. The one decision that does require a whole entry to match is secret release, which lands after every main container is up — but it withholds a secret, it does not stop the wrong set from running.
  • Allowlist write authorization is pinned-key, not PKI. Writes are authorized by long-lived operator public keys pinned in cds.operatorKeys. Revocation is coarse — remove the key and re-install, with no CRL/OCSP. Write tokens are bound to body, method, and path with a five-minute server cap, but carry no cluster binding, so clusters pinning the same operator key accept each other's captured tokens within that window — pin distinct keys per cluster. The pinned list itself is host-supplied config that the CDS serving certificate does not commit: c8s cds verify --operator-keys fetches the served set over the attested serving cert and fails closed against your own bundle, but that protects only the verifier that runs it — in-cluster enforcers run none, so run it continuously, not just at bootstrap. The applied allowlist seed is not covered by any attestation. A CA with short-lived operator certificates is the planned replacement. See Authorizing mutations.
  • Certificate revocation is not checked. The attestation-api ships with CRL checking off (require_crl=false, and the chart exposes no value to turn it on), and the in-process Go verification path for AMD SEV-SNP performs no revocation check at all. An adversary who blackholes the AMD CRL endpoint gets a revoked AMD attestation key (VCEK) accepted.
  • No per-workload peer allowlists in the mesh. Any attested mesh peer can talk to any other; there is no per-workload restriction on who may connect to whom.
  • No SPIFFE-style URI SAN identities. Workload identity is carried by the CDS-issued cert's SANs/CN, not a SPIFFE URI scheme.
  • Exempt namespaces bypass injection and image policy. The release namespace, kube-system, kube-public, and kube-node-lease skip webhook injection and the pod-as-CVM RuntimeClass policy, and kube-system also skips image policy. Host-namespace pods are exempt from that policy too, so anyone with create-pod RBAC in an exempt namespace opts out with hostNetwork: true — and so does anyone in any namespace if you set hostNamespacePolicy.enabled=false (the deny-host-namespaces validating policy is on by default). RuntimeClass enforcement is a guardrail; the boundary is per-pod attestation.

Confidential pods under Pod-as-CVM

  • c8s allowlist add does not reach running Kata guests. In-guest policy-monitor refuses to refresh from the CDS unless C8S_CDS_MEASUREMENTS pins the CDS launch digest, and no shipping path delivers that pin: baking it is self-referential (under Kata the CDS runs from the same guest image, so the value would change the measurement it pins), and per-pod cloud-init is host-controlled. The refresh is therefore disabled on every default install and each guest enforces only the seed baked into its measured image — the cds, get-cert, and c8s-operator digests resolved at guest-image build time. Operator additions reach host-side enforcement and the CDS, but not a running guest, so admitting a new workload image inside a confidential pod means rebuilding the guest image (which moves its launch measurement). This is deliberately fail-closed: "any attested TEE" is not good enough for the refresh, because the host can boot its own confidential VM (CVM) from the same guest image and serve an attacker-chosen allowlist.
  • Secrets and encrypted volumes are unavailable under pod-as-CVM. Both injected fetchers redeem their sandbox token over the node's mounted admission-inventory socket — unlike get-cert, neither has a guest-loopback path — and the volume agent mounts into the pod's kubelet directory, which does not exist for a Kata guest. The webhook therefore rejects confidential.ai/c8s-secrets and confidential.ai/c8s-volumes at admission wherever the operator has no node inventory socket (kata.enabled=true, or nri-image-policy disabled), so the pod is refused outright rather than left blocking on a file or a mount that can never land. See Secrets and Encrypted volumes.
  • A small post-start kill window exists. policy-monitor SIGKILLs a non-allowlisted container's cgroup, but the init fork happens single-digit milliseconds before the kill lands. The container has no network and cannot execve a user binary in that window.
  • Every Kata guest accepts inbound TCP 8443 without mesh mTLS. The guest image bakes C8S_MESH_INBOUND_PASSTHROUGH=tcp:8443 so the front doors (the tls-lb nginx, the CDS) can terminate their own attestation-anchored TLS for external clients. Because that value is a single baked default, any workload listening on 8443 in a Kata pod is reachable without a mesh client certificate. Don't serve mesh-trusting endpoints on 8443.
  • Root workloads bypass the in-guest mesh. The in-guest mesh exempts all UID-0 egress so the attestation service can reach the AMD KDS, so a container running as root egresses in plaintext. Run workloads non-root.
  • Workload images must be pullable anonymously. Under guest-pull the kata-agent fetches image layers inside the guest, and there is no in-guest registry-auth path. A private image 401s the in-guest layer fetch; imagePullSecrets only covers the host-side manifest resolution. Publish the workload image, or host it on a registry the guest reaches without credentials.
  • One vCPU per confidential pod on SEV-SNP. Both SEV-SNP shims — CPU and GPU — pin default_vcpus = default_maxvcpus = 1, because the SEV-SNP launch digest measures one VMSA per boot-time vCPU and a floating count would fragment the reference measurement per pod shape. CPU hotplug cannot raise it at runtime. This is a policy pin, not a hard limit: any fixed count works if you re-predict and re-pin the launch digest. Intel TDX installs need no pin.

Availability and operations

  • The CDS is a singleton by default. The mesh CA key lives only in CDS process memory, so a restart generates a new CA and invalidates existing leaves. Restart continuity via attested CA handoff is opt-in (cds.handoff.enabled=true) — see CDS handoff. True active/active is still blocked by per-pod EAR (Entity Attestation Result) signing keys; replicas stay fixed at one.
  • Operator-added allowlist entries don't survive a CDS restart. cds.persistence.enabled is false by default, so the store is an emptyDir and a restart drops back to the install seed — workloads pulling those images are denied roughly one poll interval (~30s) later. Set cds.persistence.enabled=true for an RWO PVC, or re-run c8s allowlist add after a restart. A planned handoff roll is the exception: it carries the complete allowlist inside the encrypted snapshot, so freeze allowlist writes for the duration of the rollout.
  • Flipping kata.enabled on a live cluster is disruptive. Node-as-CVM and pod-as-CVM move components between host and guest; switching in place is not a seamless toggle.

Platform constraints

  • Pod-as-CVM is unavailable on Azure. Azure does not expose nested virtualization, so AKS runs node-as-CVM only — all pods on a node share one confidential boundary. See Provisioning on Azure.
  • One CPU TEE per cluster for confidential pods. The per-pod runtime (--cvm-mode=pod) supports SEV-SNP and TDX, but an install picks exactly one via --hardware-platform (rendering kata-qemu-snp or kata-qemu-tdx), and the webhook promotes every confidential pod to that one class — mixed SNP + TDX clusters are not supported. On a host with neither TEE you can still run kata-qemu (isolation without confidentiality).
  • Confidential GPU ships, with gaps. Kata clusters run GPU pods as confidential VMs with a Blackwell-class NVIDIA GPU passed through over VFIO in NVIDIA Confidential Computing (CC mode) — request an nvidia.com/* resource and the webhook injects the confidential GPU RuntimeClass. Current gaps: the GPU's own attestation (SPDM) is not collected in the guest or required at certificate issuance, so no positive GPU evidence reaches the relying party — a non-CC GPU fails closed at guest boot, which is a guard, not a proof. The GPU guest boots kata's GPU kernel with the NVIDIA modules grafted from kata's rootfs, so kernel and driver provenance is the kata release rather than the c8s build (everything grafted is inside the measured verity root). GPU pods take no memory limits, and one pod targets one GPU model. Host GPU provisioning (vfio-pci binding, GPU CC mode, BAR resize) is assumed done before install; the NVIDIA GPU Operator is not used on this path.
  • Don't run an external service mesh alongside c8s. c8s installs its own pod-to-pod RA-TLS mesh — transparent iptables redirection plus attestation-gated mTLS. A second service mesh (Istio, Linkerd, or any sidecar / mTLS plumbing) double-intercepts the same traffic, collides with the RA-TLS handshake, and injects un-attested proxies into the confidential path — breaking the trust model rather than adding to it. Run c8s without one; it already provides the mTLS layer, rooted in hardware attestation.

Browser and out-of-cluster verification

  • Browser verification is SEV-SNP only. The c8s cds-attest sidecar can serve TDX evidence, but the c8s-verify WASM verifier implements the snp and az-snp paths only; tdx is reserved in the wire protocol and not implemented.
  • The WASM verifier enforces fewer checks than the server verifiers. Its bare-snp path omits the VMPL-0 check, debug-policy rejection, minimum-TCB enforcement, VEK validity, and CRL. A browser client would accept a debug-enabled or non-VMPL-0 guest — one whose memory the host can read — if its measurement is allowlisted.
  • The over-encrypted channel is not streaming. The sidecar buffers each sealed request and each upstream response into a single tunnel envelope, so chunked transfer from the upstream does not bypass the buffering. Uploads are bounded by the sidecar's request-record cap, and upstream responses over 32 MiB fail rather than stream. Large transfers need application-level range or chunk APIs.

Out of scope for this milestone

  • Pod-spec integrity checking beyond the per-container digest and argv policy.
  • Active/active CDS high availability.
  • Multi-tenant isolation between workloads on the same cluster beyond the CVM boundary, and federated multi-cluster control planes.