Use application secrets
Give a confidential workload an attestation-gated secret — annotate the pod, wait for the file, supply a value as the operator, replace one, and read c8s secrets explain when the CDS refuses.
c8s releases application secrets under attestation: a pod gets a value only if the images
running in its sandbox match an allowlist entry that grants
the path. This page is the task. For what the CDS checks, when it will not serve /secrets at
all, and why a CDS restart is disruptive, see
Application secrets.
The file appears after your container starts. The CDS releases only once every main container in the pod is running, so a consumer that reads its secret path at startup finds nothing there. Wait for the file. This is structural — an init container cannot fix it. See the timing constraint.
Grant the path first: an entry with no secrets grant releases nothing. See
Grant secret paths.
Request the secret in the pod
Annotate the pod alongside confidential.ai/cw:
| Annotation | What it does |
|---|---|
confidential.ai/c8s-secrets | Comma-separated NAME=/store/path pairs. NAME is the file each value is written to; /store/path is where the value lives in the CDS store. |
confidential.ai/c8s-secret-dir | Where the files land. Default /run/c8s/secrets. |
apiVersion: v1
kind: Pod
metadata:
name: api
annotations:
confidential.ai/cw: api
confidential.ai/c8s-secrets: "DB=/tenant-a/db,HF=/tenant-a/hf-token"
spec:
containers:
- name: api
image: example.com/api@sha256:<DIGEST>confidential.ai/cw is required — the secret annotations without it are rejected at admission,
since there is no workload identity to release against.
Each NAME becomes a filename, so it may not contain a path separator and may not be . or
.., and two secrets may not share one. Each store path must be absolute, clean, free of a
trailing slash, free of percent-encoding, and free of wildcards. A path that is not already
canonical is rejected rather than repaired, so the bytes matched against the grant and the bytes
used as the store key are the bytes you wrote.
Operator-supplied values
c8s secrets put /tenant-a/hf-token \
--url "https://<CDS_HOST>:<PORT>" \
--measurements <SHA384_LAUNCH_DIGEST> \
--operator-key operator.key < token.txt<CDS_HOST>:<PORT> is the CDS or the CDS-issued-TLS tls-lb endpoint; <SHA384_LAUNCH_DIGEST> is
the launch measurement that endpoint must present.
The value is read from stdin or --from-file and stored exactly as read — a trailing newline is
part of the value — and the byte count is printed so you can confirm which bytes were sent.
--dry-run prints the intended change without calling the CDS.
Writes are authorized by the operator key the CDS already pins for allowlist mutations
(--operator-keys), which is the same key the grants themselves are rooted in. The signed token
binds the method, the path, and the body, so a captured one cannot be replayed against a
different path or a different value.
Replacing a value
A path that already holds a value is refused, and the CLI names what put it there — a
workload-generated value or an earlier operator write. --overwrite replaces it and prints what
it is replacing before it does:
~ /tenant-a/db (replaces a workload-generated value)
wrote 24 bytes to /tenant-a/dbThe store has no versioning and no delete, so a displaced value is gone.
A workload reads its secret into a file once, at startup. A replacement therefore reaches a pod only when that pod next restarts: a Deployment holding the old value keeps it until you roll it. Replacing a path a workload created is worth pausing over for that reason — the pods that generated the value go on using it.
Diagnosing a refusal
A refused pod is told only that it was refused, and the input that decides the matter — what the
sandbox is running — is visible only to the CDS. c8s secrets explain is where that is read:
c8s secrets explain --sandbox <SANDBOX_ID> \
--url "https://<CDS_HOST>:<PORT>" \
--measurements <SHA384_LAUNCH_DIGEST> \
--operator-key operator.keysandbox 0123456789abcdef…
inventory 10.0.0.7
reported 3 container(s)
dropped 1 injected by c8s
candidates 2
sha256:1111… [/serve]
- sha256:9999… [get-secret]
sha256:8888… [sh -c sleep 1]
vllm-llama NEAR MISS
foreign sha256:8888… [sh -c sleep 1]
no container in this entry declares it
nothing is released: no entry describes the candidate setThe report is laid out in the order the CDS decides, so the first thing that goes wrong is the first thing you read. It uses the same inventory, binding, and allowlist the release path uses, and measures entries with the same matcher — it reports the decision rather than a reconstruction of it.
<SANDBOX_ID> is on the pod's certificate; c8s verify prints it (see
Verify a workload). --json emits the report as
it arrives. It answers to the operator key, since it describes a pod the caller may not own. The
report carries grant paths; a value never appears in it.
See also
- Application secrets — the release decision, the limits, and what a CDS restart destroys.
- Workload annotations — every annotation, and the reserved container and volume names.
- CLI reference →
c8s secrets— every flag. - Create an encrypted volume — for data too large to be a secret.