Verify a deployment
Verify CDS identity, compare its admitted node policy, and check router or workload evidence against independent image and mesh CA pins.
Use c8s verify to check c8s evidence against the identities and policies you intend to
trust. Keep the Certificate Distribution Service (CDS) pin, its admitted node policy, and
the router's or workload node's pin separate. See
the verification model for what each check proves.
Requirements
Run these commands with the c8s CLI on PATH and network reach to the named endpoint.
Prepare these files through your trusted build and deployment process:
| File | Required contents |
|---|---|
cds-pin.json | Approved CDS image policy, with every entry binding the exact launch configuration file |
peers.json | Independently intended current set of node identities CDS admits, for the optional served-policy comparison |
router-pin.json | Approved identity policy for the node running the router |
node-pin.json | Approved identity policy for the node running the workload, when checking workload evidence |
operator.pub | Independently trusted operator public-key PEM bundle, when checking operator signatures or the CDS key set |
Use launch measurements and
complete image policies from reviewed artifacts.
On Intel TDX, include the firmware measurement (MRTD) and runtime measurement registers
(RTMR[1] and RTMR[2]) from the same build. Include the applicable launch bindings.
mesh-ca.pem is the trusted mesh certificate authority (CA) bundle. Bootstrap it through
pinned CDS verification below, or use your retained trusted copy. Never take a root supplied by
the router as proof that it belongs to your cluster. Verification may need vendor collateral;
collateral retrieval failures return exit 3. This guide does not establish offline operation
or vendor revocation coverage.
For saved-certificate verification, obtain a previously captured,
evidence-bearing public PEM certificate from your deployment operator: cds-cert.pem for CDS
or workload-cert.pem for the workload. Obtaining that certificate is an external prerequisite.
What you can verify
--kind | Default evidence mode | Target |
|---|---|---|
cds | armtls-cert | Actual CDS Remote-Attestation TLS (RA-TLS) endpoint |
lb | discovery | Router's /v1/discovery document |
workload | armtls-cert | Explicitly available workload RA-TLS evidence endpoint |
auto | auto | Discovery first, then serving-certificate evidence |
Override the mode with --mode, or verify saved evidence.
Mesh private keys stay isolated from application containers. The mesh presents its public
leaf during TLS and requires client authentication; see workload verification and
reachability.
Verify the CDS
Check the CDS image and configuration
c8s cds verify "https://<CDS_HOST>:8443" \
--image-policy-file cds-pin.jsonReplace <CDS_HOST> with the actual CDS host supplied to installation as --cds-address;
use its configured port if different. CDS is an independent VM endpoint, not a Kubernetes
Service discovered through an EndpointSlice. c8s cds verify selects --kind cds.
The pin must bind the CDS image and exact launch configuration. A CDS target rejects bare
--measurements pins and --image-manifest. Its self-signed serving certificate carries
attestation evidence; --mesh-ca applies to certificates CDS issues, not this endpoint.
Compare the node identities CDS admits
c8s cds verify "https://<CDS_HOST>:8443" \
--image-policy-file cds-pin.json \
--served-policy-file peers.jsonThis additionally requires equality with the policy served at /measurements. Without
--served-policy-file, the verdict warns that this set was not checked.
Use peers.json only while it describes your intended current node set. The measured CDS
configuration supplies the singleton's node policy and the default for HA Genesis, the
signed command that starts the cluster. Genesis can select another set, and later signed
commands can change it. A measured CDS configuration alone does not establish the current HA
node policy.
Obtain the mesh CA through the pinned CDS
Keep any retained mesh-ca.pem unchanged while fetching a candidate bundle:
c8s cds ca --url "https://<CDS_HOST>:8443" \
--image-policy-file cds-pin.json --out mesh-ca.candidate.pemRecord the printed certificate fingerprints. For an existing cluster, compare the candidate with the retained anchor before accepting it:
cmp mesh-ca.pem mesh-ca.candidate.pemExit 0 means the files match exactly. Investigate any difference before promotion:
certificate renewal can change fingerprints while retaining the CA key; follow
mesh CA bootstrap when checking continuity.
After accepting the candidate, or recording the anchor at first bootstrap, install it for
the following checks:
cp mesh-ca.candidate.pem mesh-ca.pemThe router does not relay /ca.
Check the operator keys CDS pins
The measured CDS configuration binds the operator keys authorized to change the image allowlist. Add an explicit comparison with your expected PEM bundle:
c8s cds verify "https://<CDS_HOST>:8443" \
--image-policy-file cds-pin.json \
--served-policy-file peers.json \
--operator-keys operator.pub -o jsonThe /operator-keys read uses the attested CDS connection. The check requires exact key-set
equality and fails if the fetch fails. A 404 compares as an empty set. The text renderer
still labels these keys “NOT covered by the measurement”; that label does not describe the
launch-configuration binding enforced by cds-pin.json.
The operator_keys JSON field contains SHA-256 fingerprints of SPKI DER public keys. For a
single-key operator.pub, reproduce the fingerprint with OpenSSL and sha256sum:
openssl pkey -pubin -in operator.pub -outform DER | sha256sumVerify the router
c8s verify "https://<ROUTER_HOST>:443" --kind lb \
--image-policy-file router-pin.json \
--mesh-ca mesh-ca.pemReplace <ROUTER_HOST> with the router's public hostname. This fetches /v1/discovery;
--discovery-path changes that path. CA-issued discovery evidence requires the pinned mesh CA.
The verdict also checks whether the observed TLS serving certificate is the attested one;
a different serving key leaves the result partial.
A discovery document and a serving certificate carry issuance-time evidence, not a nonce from this verification attempt. Use the following modes for fresh evidence.
Check the route manifest the router applied
With the router's attestation endpoints enabled, choose attest-lb to bind fresh evidence to
the TLS serving leaf observed by this check; it requires front-door TLS mode cds or acme.
Choose attest-pq to check a fresh responder identity/session transcript; it also works with
webpki front doors. CLI attest-pq does not authenticate the observed public TLS leaf or
exercise the inner application channel.
Either mode requires one route check. To pin a reviewed manifest digest:
c8s verify "https://<ROUTER_HOST>:443" --kind lb --mode attest-lb \
--image-policy-file router-pin.json \
--mesh-ca mesh-ca.pem \
--route-digest "<ROUTE_DIGEST>"Replace <ROUTE_DIGEST> with the 64-hex digest from your approved route publication, as
reported by c8s routes show or c8s routes digest <FILE>. <FILE> is your route manifest.
These modes bind a verifier-generated nonce and the applied route digest into REPORTDATA,
the hardware evidence's caller-data field. Repeat --route-digest to accept both reviewed
digests during a rollout, then remove the outgoing digest.
When the router publishes /.well-known/c8s/routes, you can instead check its operator
signature and a retained rollback floor:
c8s verify "https://<ROUTER_HOST>:443" --kind lb --mode attest-pq \
--image-policy-file router-pin.json \
--mesh-ca mesh-ca.pem \
--operator-keys operator.pub \
--routes-floor "<REVISION>:<ROUTE_DIGEST>" \
-o json > routes-verdict.jsonReplace <REVISION> and <ROUTE_DIGEST> with your last accepted route revision and digest.
For the first signature-based check, omit --routes-floor if you have no retained floor.
The command verifies the published manifest's signature and its digest's evidence binding;
it rejects a lower revision or the same revision with a different digest. Only after accepting
an exit-0 verdict, retain route_revision and route_digest from routes-verdict.json as the
next run's floor. The CLI retains no floor itself.
--operator-keys and --route-digest are mutually exclusive here.
A fresh transcript with only a responder-selected CA remains partial. Keep --mesh-ca in
both commands. An attest-lb verdict covers the serving leaf observed on that check's TLS
connection to one replica at one instant. An attest-pq verdict covers the responder's fresh
identity/session transcript. Application clients must verify their own connection or inner
application channel. See
consumer verification.
Verify a workload
Under Node-as-CVM, pods on one node share its launch identity. To narrow a leaf to an expected
workload, prepare allowlist.json: the exact reviewed canonical allowlist bytes containing the
expected entry. Preserve its bytes when saving it; --allowlist compares their SHA-256 with
the leaf's stamped issuing-policy digest.
If your deployment supplies an independently reachable RA-TLS evidence endpoint, use:
c8s verify "https://<WORKLOAD_HOST>:<PORT>" --kind workload \
--image-policy-file node-pin.json \
--mesh-ca mesh-ca.pem \
--workload "<ALLOWLIST_ENTRY>" \
--allowlist allowlist.json \
--sandbox-id "<CRI_SANDBOX_ID>"Replace <WORKLOAD_HOST>:<PORT> with that endpoint, <ALLOWLIST_ENTRY> with the reviewed
allowlist entry name, and <CRI_SANDBOX_ID> with the expected CRI pod sandbox ID. Omit
--sandbox-id if you do not have an independently expected sandbox ID. The workload name is
an allowlist entry, not a Service name or ConfidentialWorkload name.
These stamps require the CA chain: they are signed by CDS, not folded into REPORTDATA. CDS matches the cumulative sandbox admission record, including exited or removed containers; required main-container policies must appear in that record. A match does not prove which containers are currently running.
For ordinary injected pods, c8s-mesh keeps its private key in a credential volume unavailable
to the application. It presents its public leaf during the incoming TLS handshake and
authenticates mesh clients before delivering plaintext to the application. c8s verify has
no mesh client-credential option; public-leaf presentation alone does not establish a working
CLI verification or capture path through an ordinary application URL.
Verify the saved leaf when your deployment operator supplies
the previously captured, evidence-bearing workload-cert.pem required above. Without that
artifact, use router verification for a routed application.
A router verdict describes the router; it is not a direct workload-leaf verdict.
Check measured policy history
This additional check applies to Intel TDX nodes configured to measure pulled allowlist
policies. The stock baked image leaves measurement off, and Helm rejects enabling it
with node.baked. On AMD SEV-SNP the setting is a no-op.
For a supported chart-managed TDX deployment, enable nriImagePolicy.measurePolicies in its
deployment values. This requires attestationApi.enabled=true, the runtime directory
/run/nri-image-policy, and the journal/API mounts used by evidence producers. The node checks
journal capacity and calls MeasureOnce before accepting a policy; measurement failure retains
the previous policy. At capacity, a digest absent from the journal is refused until reboot.
Before the following steps, ensure the matching entry in router-pin.json includes the exact
launch public key as approver_key. Alternatively, replace --image-policy-file router-pin.json
with --image-manifest <TDX_MANIFEST> --operator-pkey <LAUNCH_PUBLIC_KEY>: those files are the
reviewed TDX build manifest and the verbatim public-key PEM bytes measured at launch. This
replay anchor is separate from the keys used to accept policy signatures.
These commands inspect the router node's measured history and the issuing policy of the router's mesh leaf. They do not inspect the application backend's leaf or policy history.
Fetch and review the measured policies
c8s verify "https://<ROUTER_HOST>:443" --kind lb --mode attest-pq \
--image-policy-file router-pin.json \
--mesh-ca mesh-ca.pem \
--route-digest "<ROUTE_DIGEST>" \
--fetch-allowlists reviewed-policies -o json > history.jsonUse the independently approved route digest from the router check. Read measured_policies
in history.json: it lists the prefix replayed from the operator-key seed to attested RTMR[3].
The command saves each replayed policy to reviewed-policies/<HEX>.json only after checking its
SHA-256 digest. Review every saved policy before accepting its digest. Without an acceptance
option, a nonempty replayed history yields exit 4, with partial: true and not_proven.
Fetching alone does not accept a policy.
Also review the router leaf's issuing policy, named by workload_allowlist_digest (bare hex).
If it is absent from measured_policies, obtain its exact bytes from
https://<ROUTER_HOST>/allowlist/objects/sha256/<HEX> or your retained archive and compare its
SHA-256 with that stamped digest before review. <HEX> is the digest being checked. The
fetch-only command saves replayed policies, so it need not save this additional document.
Choose explicit pins or trusted signatures
To accept only reviewed policy digests, repeat --pin-policy for every replayed digest and
the router leaf's issuing-policy digest:
c8s verify "https://<ROUTER_HOST>:443" --kind lb --mode attest-pq \
--image-policy-file router-pin.json \
--mesh-ca mesh-ca.pem \
--route-digest "<ROUTE_DIGEST>" \
--pin-policy "sha256:<REVIEWED_POLICY_HEX>" \
--pin-policy "sha256:<ISSUING_POLICY_HEX>" -o jsonReplace the policy placeholders with reviewed 64-character lowercase SHA-256 digests. Add
more --pin-policy arguments for other reviewed history entries; one argument suffices if
the history and issuing policy share a single digest.
Alternatively, accept policies signed by your trusted operator public-key bundle:
c8s verify "https://<ROUTER_HOST>:443" --kind lb --mode attest-pq \
--image-policy-file router-pin.json \
--mesh-ca mesh-ca.pem \
--route-digest "<ROUTE_DIGEST>" \
--trust-operator --trust-operator-keys operator.pub -o jsonThis fetches and re-hashes every replayed and issuing policy, then checks each stored write
token against operator.pub. Seed policies and noncanonical writes may lack signatures.
--trust-operator excludes --pin-policy; --operator-keys does not supply its trust keys.
Require a complete, fresh verdict
Require exit 0, verified: true, and fresh: true for the history acceptance check. Inspect
measured_policies, workload_allowlist_digest, leaf_stamped, not_proven, and warnings.
Missing acceptance, an unstamped leaf, an untrusted leaf stamp, or accepted history from
nonfresh certificate/discovery evidence can leave exit 4. Explicit history acceptance with
no replayed history fails with policy_history_absent (exit 2). Unpinned policies, missing
signatures, or digest mismatches also fail with exit 2. Network failures fetching policy
objects or signatures return 3; missing/pruned objects are policy-check failures (2).
A fetch or local write failure is not a successful verdict.
Verify saved evidence
For the operator-supplied CDS serving certificate cds-cert.pem, keep the complete CDS pin:
c8s cds verify --from-file cds-cert.pem --image-policy-file cds-pin.jsonA file-only check cannot compare the live served node policy or operator-key endpoint.
For the operator-supplied, evidence-bearing workload mesh leaf workload-cert.pem, keep the
node and CA pins and any expected workload checks:
c8s verify --kind workload --from-file workload-cert.pem \
--image-policy-file node-pin.json --mesh-ca mesh-ca.pem \
--workload "<ALLOWLIST_ENTRY>" --allowlist allowlist.jsonFor bare evidence with no certificate or transcript binding, supply the producer's expected
REPORTDATA anchor using --expected-report-data <HEX> (one–64 bytes). c8s bindings use the
unpadded 48-byte SHA-384 value; do not pad it yourself. This flag is rejected for certificates
and saved attest-pq/attest-lb evidence. Saved attest-pq or attest-lb transcripts need
--route-digest; a saved attest-lb receipt additionally requires the observed serving
certificate and the request nonce. See the CLI reference
for these inputs.
Saved evidence does not establish a fresh response to this verification attempt.
Pin the launch measurement
For non-CDS targets, a complete --image-policy-file replaces --measurements,
--measurements-file, and --image-manifest. Generic verification can run without image pins;
measurement_pinned: false means it did not establish your expected image. CDS verification
requires its complete launch-file pin.
On AMD SEV-SNP, a non-CDS target can instead use --measurements <SHA384_LAUNCH_DIGEST> or
--measurements-file <DIGEST_FILE> (one digest per line). Replace these placeholders with the
approved launch digest or its file. Keep --mesh-ca mesh-ca.pem for CA-issued evidence.
On Intel TDX, pin MRTD, RTMR[1], and RTMR[2] together using a complete image policy or
--image-manifest <TDX_MANIFEST>. MRTD alone measures firmware; it leaves the kernel and rootfs
unmeasured by that policy. The verifier rejects an MRTD-only pin without a pinned mesh CA;
with --mesh-ca, it warns. RTMR[0] cannot be pinned. Manual RTMR[3] pins require
--image-manifest; policy-history replay instead needs the launch-key anchor described above.
Debug guests are rejected by default. Optional --min-tcb-* floors apply to AMD SEV-SNP
trusted computing base (TCB) components; setting one against TDX evidence is a policy error.
Use it in CI
c8s cds verify "https://<CDS_HOST>:8443" \
--image-policy-file cds-pin.json \
--served-policy-file peers.json \
--operator-keys operator.pub -o json > verdict.jsonGate on the command's exit status and retain the verdict. Do not discard a nonzero status when piping output to another command.
| Code | Meaning |
|---|---|
0 | Verified under the supplied policy; inspect warnings and freshness |
1 | Invalid command or policy inputs |
2 | Verification or policy check failed |
3 | Evidence or required network material unavailable |
4 | Partially verified; at least one presented property remains unproven |
Reading the verdict field by field
| Field | Check |
|---|---|
verified, partial, error, not_proven | Accept only the required complete verdict; read failures and gaps |
measurement_pinned, measurement, rtmrs_pinned, init_data_note | Confirm the expected image and launch bindings were checked |
fresh, binding | Certificate/discovery evidence is not bound to this run's nonce |
cert_body, chain_anchor, cert_sha256 | Identify how the certificate body and chain were authenticated |
workload, workload_allowlist_digest, sandbox_id | Inspect the CA-authenticated stamps and their accompanying notes |
measured_policies, leaf_stamped | Inspect replayed history and whether an issuing-policy stamp exists |
route_revision, route_digest | Retain the accepted route floor when checking a published manifest |
operator_keys, operator_keys_note, warnings | Inspect the CDS key comparison and any unperformed checks |
Independently recompute the measurement
Recompute reference values using the exact image build and launch procedure, including its
firmware, kernel, initrd, launch configuration, and platform-specific inputs. For an IGVM
image, use its build's IGVM measurement procedure and the deployed vCPU variant; a generic
sev-snp-measure --kernel example does not establish equivalence with that build.
Compare your independently produced values with the reviewed manifest, then prepare the complete target policy and run the applicable command above. Preserve the CDS launch-file binding when checking CDS. On TDX, compare the whole MRTD/RTMR[1]/RTMR[2] tuple. See Obtain launch measurements for artifact sources.
Caveats
- Freshness: serving-certificate and discovery checks report
fresh: false. Accepted history from those sources remains partial because later policy changes are not shown. - Vendor checks: the CLI delegates hardware signature and collateral verification to
attestation-go; this procedure does not independently establish its revocation coverage. - Reachability: use the actual CDS endpoint and the router's
public endpoint. Ordinary workload mesh endpoints require authenticated mesh clients;
kubectl port-forwardis not a way to obtain their private credentials.
See also
- CLI reference — all verification flags.
- The verification model — evidence scope and consumer checks.
- The trust root — CDS identity and mesh CA trust.
- The image allowlist — admission and policy history.