Attestor backends — overview¶
An Attestor is the seam that decides who is allowed to register
as an agent. Every call to AdmissionService.Register is routed
through the configured Attestor before any passport is stored — so
the control plane can verify that the calling workload is who it
claims to be against the identity system your organization already
runs, rather than accepting any well-formed passport that arrives.
The default Attestor accepts every well-formed passport (matching the pre-Phase-D behaviour — no extra infrastructure required). External backends bind admission to a SPIRE workload-identity deployment or to an OpenID Connect IdP your security team already operates, so an agent can't join the swarm unless an external identity system has already attested its workload.
This page explains what the Attestor seam does in plain language, when you'd switch to an external backend, and how to pick between the two shipped today. The per-backend pages cover provisioning specifics.
What an Attestor does¶
When an agent tries to join the swarm via AdmissionService.Register,
it presents a self-signed passport plus an optional
external_credential (a SPIFFE JWT-SVID, an OIDC ID-token, or empty
for the native flow). The configured Attestor:
- Verifies the credential against external trust roots — the SPIRE trust bundle for SPIFFE, the IdP's JWKS for OIDC — or accepts the empty credential for the native default.
- Extracts an attested identity —
spiffe://example.org/yutha/...,oidc:https://issuer/:subject, oryutha:native:<passport_pubkey_hex>— that the audit log binds to the agent'sagent_id. - Projects allowlisted claims into receipt evidence so auditors
can chain a Yutha
agent_idall the way back to the workload selectors (SPIRE) or token claims (OIDC) the identity system already records.
On rejection the call fails with PERMISSION_DENIED and an
agent.register.deny receipt lands in the audit log — no passport
is stored, no agent joins the swarm. The deny receipt's evidence
names which Attestor rejected and why (audience mismatch, expired
token, signature invalid, etc.) without leaking the credential bytes
themselves.
The Attestor never replaces the constitution layer — what an agent is allowed to do once admitted stays with capabilities and the Cedar+ constitution. The Attestor only decides who gets in the door.
When to use which¶
native (the default) — accepts any well-formed passport with a
future expires_at; records yutha:native:<pubkey-hex> as the
attested external identity. Zero infrastructure. The right choice
for local development, integration tests, demos, and any deployment
where the topology layer (open / closed / hybrid admission) gives
enough control over who joins.
External Attestor backends — when one or more of these applies:
- No shadow IT identity. Your organization already runs SPIRE, Okta, Auth0, Azure AD, or another workload-identity system. Agents shouldn't get a Yutha-only identity that bypasses what your security team already operates.
- Compliance. Audit requires every admission to be traceable to an externally-attested workload identity, not just a self-signed passport.
- Sybil resistance. In open-admission swarms, you want to prove that every joining agent corresponds to a real workload your identity system already vouched for — not an arbitrary passport-minter.
- Cross-organization workflows. SPIFFE trust-domain federation lets workloads from a partner organization join your swarm under a federated identity, without provisioning per-agent credentials on your side.
For local development, demos, and the examples shipped with the
repo, the default native Attestor is correct. None of them needs
SPIRE or an OIDC IdP.
Backends shipped today¶
| Backend | Selector flag | Credential format | Operator runbook |
|---|---|---|---|
| Native (default) | --attestor native |
none (accepts empty external_credential) |
n/a — zero-config |
| SPIFFE / SPIRE | --attestor spiffe |
JWT-SVID minted by the SPIRE workload API | SPIFFE/SPIRE Attestor |
| OpenID Connect | --attestor oidc |
OIDC ID-token minted by any OIDC IdP | OIDC Attestor |
Both external backends:
- Verify credentials offline against a cached trust bundle (SPIRE trust bundle, OIDC JWKS) — no per-admission round-trip to the identity system once steady state is reached.
- Hot-rotate the trust bundle (SPIFFE via the Workload API stream, OIDC via JWKS TTL refresh + kid-miss async refetch) so signing-key rotations at the identity system don't require a control-plane restart.
- Fail closed on any verification failure — the agent doesn't join and the audit log records the rejection with operator-actionable evidence.
- Never leak credential bytes into error messages or receipts (per RFC 0016 §3.1).
Which to pick:
- SPIFFE / SPIRE when you already run SPIRE or you want CNCF- standard workload identity. Strongest fit for Kubernetes deployments with SPIRE agents on every node, and for cross-organization federation via SPIFFE trust domains.
- OIDC when you don't run SPIRE but you do run an OIDC IdP (Okta, Auth0, Azure AD, Google Workspace, Keycloak). Broad- compatibility on-ramp — most enterprises can do this even if they can't do SPIFFE today.
Some operators run both at different layers (SPIRE for in-cluster agents, OIDC for human-facing tool agents); v1 supports one Attestor per control plane, so multi-Attestor deployments today run one control plane per identity domain.
How operators select a backend¶
Pick the backend at startup with one flag, then pass the per-backend configuration with that backend's flag set:
# SPIFFE/SPIRE — Workload API socket (hot bundle rotation)
./yutha \
--attestor spiffe \
--attestor-spiffe-socket /run/spire/sockets/agent.sock \
--attestor-spiffe-audience yutha-prod-payroll \
[other startup flags]
# OIDC — Discovery against the IdP's issuer URL
./yutha \
--attestor oidc \
--attestor-oidc-issuer https://login.example.okta.com \
--attestor-oidc-audience yutha-prod-payroll \
[other startup flags]
Every --attestor-* flag has a matching YUTHA_ATTESTOR_* env var
if you prefer env-driven config. The audience value MUST be
swarm-specific — a generic value invites cross-swarm credential
replay; see each per-backend runbook §6.
Agents on the other side of the call need to fetch a matching
credential before they call register(). The developer-side
documentation lives at
developer quickstart → joining a swarm with workload attestation;
the gist is one extra keyword argument: await
client.admission.register(passport, external_credential=svid_bytes).
Where to go from here¶
- SPIFFE/SPIRE Attestor — SPIRE registration entries + selectors, trust-bundle rotation, static- bundle (air-gapped) mode.
- OIDC Attestor — JWKS source choice
(Discovery /
jwks_urioverride / static file), per-IdP recipes for Auth0 / Okta / Azure AD / Keycloak / Google Workspace. - Enterprise identity end-to-end — the integrated walkthrough showing an Attestor paired with a Signer in one production deployment.
- Signer backends — overview — the other enterprise-identity seam (custody of the control plane's signing key, vs the Attestor's external-identity verification).