Chio/Docs

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

Chio consumes SPIFFE identities; it does not issue them. This guide assumes your runtime already emits a SPIFFE ID, through an Envoy ext_authz principal, a SPIRE-managed SVID, an attestation JWT, or an explicit operator claim. The chio side is policy plus issuance plus receipt verification.

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.

policy.yaml
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: attested

A few notes on fields that surprise people:

  • Omitting credential_kinds accepts any of uri, x509_svid, and jwt_svid. Explicit lists are the secure default.
  • path_prefixes is a list of prefix strings, not globs. A workload at /payments/worker/shard-3 matches /payments/worker.
  • Pairing require_workload_identity with require_runtime_assurance_tier gives you both "which workload" and "how strongly attested". The two requirements evaluate independently.

Soft match for gradual rollout

Use 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

bash
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.

bash
curl -X POST https://trust.example.com/v1/capabilities/issue \
  -H "Authorization: Bearer $CHIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d @issuance-request.json

Fail-closed on conflict

If the request carries both an explicit 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.

bash
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.

policy.yaml (extensions)
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.

ConditionWhat to do
Explicit workloadIdentity conflicts with raw runtimeIdentityFix 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_identityEither 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_secondsRefresh the upstream attestation and resend. Do not extend the age ceiling to bypass freshness.
Verifier bridge projects a non-SPIFFE identifierFix the projection rule on the bridge. Chio will not invent a typed identity from an opaque string.

Summary

  1. Write the rule. Add require_workload_identity with an explicit trust domain, path prefix set, and credential kind set.
  2. Issue with attestation. Pass runtimeAttestation on issuance so the capability is stamped with the minimum runtime-assurance tier.
  3. Verify on the receipt. The accepted workloadIdentity appears under the kernel signature; audit pipelines query on it directly.
  4. Rebind to verified when needed. Use trusted_verifiers to promote raw attestation into the verified tier under explicit operator policy.

Next Steps

Bind Workload Identity · Chio Docs