Bind Workload Identity
This guide walks through pinning a capability to a SPIFFE workload so only that specific process can use it. You will write a WorkloadIdentityMatch policy, issue a capability that carries a runtimeAttestation, and verify the binding on a signed receipt. See the Workload Identity concept page first if you want the model before the commands.
You do not install SPIRE through chio
Prerequisites
- A running chio kernel with a capability authority. See Installation.
- A workload whose SPIFFE ID you know, for example
spiffe://prod.chio/payments/worker. - One of the three delivery paths wired up (Envoy ext_authz, explicit attestation, or a verifier bridge). The policy and verification steps below are identical for all three.
Step 1: Write a WorkloadIdentityMatch Rule
Start from a rule that already enumerates the tools you want to gate, then add require_workload_identity. The match is additive: trust domain, path prefix, and credential kind all have to pass for admission.
rules:
payments_write:
enabled: true
default: block
allow:
- transfer_funds
- reconcile_ledger
require_workload_identity:
scheme: spiffe
trust_domain: prod.chio
path_prefixes:
- /payments/worker
- /payments/reconciler
credential_kinds:
- x509_svid
- jwt_svid
require_runtime_assurance_tier: attestedA few notes on fields that surprise people:
- Omitting
credential_kindsaccepts any ofuri,x509_svid, andjwt_svid. Explicit lists are the secure default. path_prefixesis a list of prefix strings, not globs. A workload at/payments/worker/shard-3matches/payments/worker.- Pairing
require_workload_identitywithrequire_runtime_assurance_tiergives you both "which workload" and "how strongly attested". The two requirements evaluate independently.
Soft match for gradual rollout
prefer_workload_identity instead of require_ during rollout. The kernel records a soft match on the receipt without denying the call, which lets you observe the identity population in your audit log before flipping to a hard requirement.Step 2: Issue an Identity-Bound Capability
Issuance can carry a runtimeAttestation block. When present, the authority resolves the highest satisfied runtime-assurance tier and stamps the capability with a minimum runtime-assurance constraint. Governed execution later re-checks that the presented evidence still clears the stamped tier.
From the CLI
chio capability issue \
--authority-seed authority.seed \
--subject did:chio:7b0f... \
--tool transfer_funds \
--ttl 300s \
--runtime-attestation-file attestation.json
# attestation.json
# {
# "schema": "chio.runtime-attestation.v1",
# "verifier": "spire-agent",
# "issuedAt": 1744537800,
# "expiresAt": 1744538100,
# "evidenceSha256": "…",
# "workloadIdentity": {
# "scheme": "spiffe",
# "credentialKind": "x509_svid",
# "uri": "spiffe://prod.chio/payments/worker",
# "trustDomain": "prod.chio",
# "path": "/payments/worker"
# }
# }From the trust-control HTTP API
The same contract is exposed at POST /v1/capabilities/issue for deployments that centralize issuance on a trust-control cluster. The runtimeAttestation body is identical.
curl -X POST https://trust.example.com/v1/capabilities/issue \
-H "Authorization: Bearer $CHIO_TOKEN" \
-H "Content-Type: application/json" \
-d @issuance-request.jsonFail-closed on conflict
workloadIdentity and a raw runtimeIdentity that do not agree, or if the SPIFFE URI is malformed, issuance fails. Do not wrap this in a retry. Fix the upstream and reissue.Step 3: Verify the Binding on the Receipt
Every governed admission that clears a workload-identity check records the accepted workloadIdentity object on the signed receipt. This is what makes the binding auditable offline: a reviewer does not have to trust the runtime to tell them which workload called, the receipt already carries it under the kernel's signature.
chio --json receipt show --receipt-id rct-01hxab...
# {
# "receiptId": "rct-01hxab…",
# "verdict": "allow",
# "capabilityId": "cap-7b0f…",
# "runtimeAttestation": {
# "tier": "attested",
# "workloadIdentity": {
# "scheme": "spiffe",
# "credentialKind": "x509_svid",
# "uri": "spiffe://prod.chio/payments/worker",
# "trustDomain": "prod.chio",
# "path": "/payments/worker"
# }
# },
# "signature": "ed25519:…"
# }If you run an auditor pipeline, filter receipts by runtimeAttestation.workloadIdentity.uri to get the exact set of admissions a particular workload made. The receipt query API exposes this as a first-class filter.
Rebinding to verified via Trusted Verifiers
Raw attestation evidence lands at the attested tier by default. To let a rule require verified, add an explicit trusted_verifiers entry that binds a {schema, verifier} pair to an effective tier. This is how operators keep verifier trust out of the rule surface and in one policy extension.
extensions:
runtime_assurance:
tiers:
verified:
minimum_attestation_tier: verified
max_scope:
operations: ["invoke"]
ttl_seconds: 300
trusted_verifiers:
spire_prod:
schema: chio.runtime-attestation.spiffe.x509-svid.v1
verifier: https://spire.prod.internal
verifier_family: spiffe
effective_tier: verified
max_evidence_age_seconds: 120
allowed_attestation_types: [x509_svid]With this in place, a rule that sets require_runtime_assurance_tier: verified admits only calls whose attestation matches a trusted-verifier rule and satisfies its freshness and claim constraints.
Fail-Closed Conditions
The kernel denies admission, not warns, when any of the following hold. Matching operator recovery guidance lives in WORKLOAD_IDENTITY_RUNBOOK in the reference tree.
| Condition | What to do |
|---|---|
| Explicit workloadIdentity conflicts with raw runtimeIdentity | Fix the upstream so both agree; do not mask the conflict at the kernel. |
| SPIFFE URI is malformed (missing trust domain, empty path) | Regenerate the SVID from SPIRE or your attestation source; inspect the exact string. |
Presented identity fails require_workload_identity | Either add the workload to the allowed prefixes, or accept the deny. Do not relax the match to get past one call. |
Evidence older than max_evidence_age_seconds | Refresh the upstream attestation and resend. Do not extend the age ceiling to bypass freshness. |
| Verifier bridge projects a non-SPIFFE identifier | Fix the projection rule on the bridge. Chio will not invent a typed identity from an opaque string. |
Summary
- Write the rule. Add
require_workload_identitywith an explicit trust domain, path prefix set, and credential kind set. - Issue with attestation. Pass
runtimeAttestationon issuance so the capability is stamped with the minimum runtime-assurance tier. - Verify on the receipt. The accepted
workloadIdentityappears under the kernel signature; audit pipelines query on it directly. - Rebind to verified when needed. Use
trusted_verifiersto promote raw attestation into theverifiedtier under explicit operator policy.
Next Steps
- Workload Identity (concept) · the normalized shape and why SPIFFE is canonical
- Envoy ext_authz · the mesh delivery path for SPIFFE principals
- Policy Schema · the full
WorkloadIdentityMatchcontract - Rotate Keys & Revoke · what to do when a workload's identity material changes