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.measurementsandratlsMesh.measurementsempty, which means "accept any attested peer". Both the CDS andratls-meshlog warnings when their allowlists are empty, andratls-meshpublishesratls_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=podthe 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
RequireCAEvidencemode 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-idchecks 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
commandprefix andargsremainder against the effective argv, and all three enforcement points —nri-image-policy, in-guestpolicy-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-keysfetches 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-apiships 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, andkube-node-leaseskip webhook injection and the pod-as-CVM RuntimeClass policy, andkube-systemalso skips image policy. Host-namespace pods are exempt from that policy too, so anyone with create-pod RBAC in an exempt namespace opts out withhostNetwork: true— and so does anyone in any namespace if you sethostNamespacePolicy.enabled=false(thedeny-host-namespacesvalidating policy is on by default). RuntimeClass enforcement is a guardrail; the boundary is per-pod attestation.
Confidential pods under Pod-as-CVM
c8s allowlist adddoes not reach running Kata guests. In-guestpolicy-monitorrefuses to refresh from the CDS unlessC8S_CDS_MEASUREMENTSpins 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 — thecds,get-cert, andc8s-operatordigests 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 rejectsconfidential.ai/c8s-secretsandconfidential.ai/c8s-volumesat admission wherever the operator has no node inventory socket (kata.enabled=true, ornri-image-policydisabled), 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-monitorSIGKILLs a non-allowlisted container's cgroup, but the init fork happens single-digit milliseconds before the kill lands. The container has no network and cannotexecvea user binary in that window. - Every Kata guest accepts inbound TCP
8443without mesh mTLS. The guest image bakesC8S_MESH_INBOUND_PASSTHROUGH=tcp:8443so 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 on8443in a Kata pod is reachable without a mesh client certificate. Don't serve mesh-trusting endpoints on8443. - 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;
imagePullSecretsonly 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.enabledisfalseby default, so the store is anemptyDirand a restart drops back to the install seed — workloads pulling those images are denied roughly one poll interval (~30s) later. Setcds.persistence.enabled=truefor an RWO PVC, or re-runc8s allowlist addafter 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.enabledon 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(renderingkata-qemu-snporkata-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 runkata-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-attestsidecar can serve TDX evidence, but the c8s-verify WASM verifier implements thesnpandaz-snppaths only;tdxis reserved in the wire protocol and not implemented. - The WASM verifier enforces fewer checks than the server verifiers. Its bare-
snppath 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.