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:

FileRequired contents
cds-pin.jsonApproved CDS image policy, with every entry binding the exact launch configuration file
peers.jsonIndependently intended current set of node identities CDS admits, for the optional served-policy comparison
router-pin.jsonApproved identity policy for the node running the router
node-pin.jsonApproved identity policy for the node running the workload, when checking workload evidence
operator.pubIndependently 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

--kindDefault evidence modeTarget
cdsarmtls-certActual CDS Remote-Attestation TLS (RA-TLS) endpoint
lbdiscoveryRouter's /v1/discovery document
workloadarmtls-certExplicitly available workload RA-TLS evidence endpoint
autoautoDiscovery 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.json

Replace <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.json

This 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.pem

Record 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.pem

Exit 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.pem

The 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 json

The /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 | sha256sum

Verify the router

c8s verify "https://<ROUTER_HOST>:443" --kind lb \
  --image-policy-file router-pin.json \
  --mesh-ca mesh-ca.pem

Replace <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.json

Replace <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.json

Use 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 json

Replace 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 json

This 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.json

A 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.json

For 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.json

Gate on the command's exit status and retain the verdict. Do not discard a nonzero status when piping output to another command.

CodeMeaning
0Verified under the supplied policy; inspect warnings and freshness
1Invalid command or policy inputs
2Verification or policy check failed
3Evidence or required network material unavailable
4Partially verified; at least one presented property remains unproven

Reading the verdict field by field

FieldCheck
verified, partial, error, not_provenAccept only the required complete verdict; read failures and gaps
measurement_pinned, measurement, rtmrs_pinned, init_data_noteConfirm the expected image and launch bindings were checked
fresh, bindingCertificate/discovery evidence is not bound to this run's nonce
cert_body, chain_anchor, cert_sha256Identify how the certificate body and chain were authenticated
workload, workload_allowlist_digest, sandbox_idInspect the CA-authenticated stamps and their accompanying notes
measured_policies, leaf_stampedInspect replayed history and whether an issuing-policy stamp exists
route_revision, route_digestRetain the accepted route floor when checking a published manifest
operator_keys, operator_keys_note, warningsInspect 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-forward is not a way to obtain their private credentials.

See also