trstctl /docs GitHub ↗

Workload identity — give software a verifiable identity, no secrets to steal

What it is

A workload is a running piece of software — a service, a container, a CI job, an AI agent. Workload identity is how that software proves what it is to other services, without anyone planting a long-lived password or API key inside it. trstctl does this by combining two ideas: attestation (cryptographic proof of what and where a workload is) and short-lived credentials issued only to workloads that pass attestation.

The mental model: instead of giving every employee a permanent badge they might lose, you install a fingerprint scanner at each door. The workload doesn't carry a secret — it proves what it is at the moment it needs access, and gets a pass that expires in minutes. This page covers the SPIFFE standard for workload identity, trstctl's attestation chain, ephemeral issuance, lifecycle management for non-human identities, and a purpose-built broker for AI agents.

Why it exists

The classic way to give a service access — bake an API key or certificate into it — is also the classic way to get breached: those secrets get copied into logs, images, git history, and laptops, and they rarely expire. Attestation-based, short-lived identity removes the thing attackers steal. There's nothing long-lived in the workload to leak, and even a captured credential is useless within minutes. This is the foundation of "zero-trust" service-to-service security, and it matters even more for AI agents, which spin up fast, act with real privileges, and need tight, revocable scopes.

How it works

The attestation chain (F30) — proof before trust

Everything here rests on attestation: before issuing anything, trstctl demands proof of the workload's identity and verifies it. The framework is pluggable — an Attestor knows how to verify one kind of proof — and trstctl ships six:

  • TPM 2.0 quote — verifies a hardware TPM's endorsement chain back to the manufacturer root, plus a signed quote bound to a fresh nonce.
  • AWS IMDSv2 — verifies the PKCS#7-signed EC2 instance identity document against the AWS root.
  • GCP / Azure metadata — verifies the signed identity document the cloud's metadata service hands a VM.
  • Kubernetes projected SAT — verifies a pod's projected service-account token against the cluster's JWKS.
  • GitHub OIDC + Fulcio — verifies a GitHub Actions OIDC token and can produce a Sigstore/Fulcio binding for keyless code signing.

The verifier dispatches by method, computes a stable attestation ID inside the crypto boundary (AN-3), adds an attestation node to the credential graph, and emits attestation.verified — or attestation.rejected and nothing else on failure (fail-closed, AN-2). Every attester must pass a conformance harness that proves it accepts the genuine proof and rejects a forgery. All signature/JWT/CMS verification runs through internal/crypto.

Code: internal/attest (Attestor, Verifier, Conform) and internal/attest/{tpmquote,awsiid,gcpmeta,azureimds,k8ssat,githuboidc}.

The SPIFFE Workload API (F24) — the standard interface

SPIFFE is the open standard for workload identity; its document is the SVID, delivered as an X.509 certificate or a JWT. trstctl implements a SPIRE-compatible Workload API server: a workload presents selectors (e.g. k8s:ns:default, k8s:sa:web), the server matches them against registration entries using set-subset semantics (you must present every selector an entry requires), and issues the SVID. Signing goes through the crypto boundary (AN-3) to keys held in the isolated signer (AN-4); a NeedsRotation helper flags an SVID for renewal once it's half-expired (SPIRE's policy); issuance runs on a bulkhead (AN-7) and is audited (AN-2).

Code: internal/protocols/spiffe (Server, FetchX509SVIDs, FetchJWTSVIDs, NeedsRotation, WorkloadAPIServer, ServeWorkloadAPI). Status: served as a gRPC service on a Unix domain socket (EXC-WIRE-02, protocols.spiffe.enabled, default off): a spiffe-helper/go-spiffe/Envoy-SDS workload dials the socket and FetchX509SVID returns an SVID + trust bundle signed through the signer (AN-4). The Workload-API gRPC/protobuf contract is vendored verbatim from go-spiffe so the wire format is byte-identical.

Ephemeral issuance (F25) — attestation in, short-lived cert out

The ephemeral issuer ties it together: it takes an attestation, verifies it (refusing to sign if verification fails), mints a short-lived certificate (default TTL 15 minutes, clamped to a per-method maximum), and binds the attestation to the credential in the graph and audit trail. Every request needs an idempotency key (AN-5), so a retried request returns the same credential rather than minting a second.

Code: internal/ephemeral (Issuer, TTLPolicy). Status: library-complete, tested; not yet exposed as a served endpoint.

Non-human identity lifecycle (F59)

Beyond a single credential, the identity itself has a lifecycle: created, scoped, rotated, disabled, retired (a terminal state). trstctl models this as a guarded state machine — every transition goes through one path that enforces the legal moves, updates the identity's node in the credential graph, and emits a lifecycle event (nhi.created, nhi.rotated, nhi.disabled, nhi.expired, AN-2).

Code: internal/nhi (Manager, Create, Scope, Rotate, Disable, Expire). Status: the served REST routes POST /api/v1/identities and POST /api/v1/identities/{id}/transitions (both idempotent, AN-5) are wired through the orchestrator; the standalone manager is otherwise library-level.

The AI-agent identity broker (F61)

AI agents are a sharp case: they appear quickly, act with real privileges, and chain tools together, so an over-scoped or un-revocable agent credential is dangerous. The broker is a dedicated issuance surface that (1) evaluates a policy decision before issuing — a deny records agent.identity.refused and signs nothing; (2) issues an attested, short-lived credential via the ephemeral issuer; (3) records the agent and its credential in the graph so you can ask blast radius ("everything this agent can reach") before trusting it; and (4) supports one-call revocation of every credential an agent owns.

Code: internal/broker (Broker, Issue, Revoke, BlastRadius). Status: library-complete and tested; not yet wired into a served endpoint.

Use it

The non-human-identity lifecycle is served today:

# create a managed non-human identity (idempotent)
trstctl-cli identities create -f service-account.json

# transition its state (e.g. disable on decommission)
trstctl-cli identities transition <id> -f '{"to":"disabled","reason":"decommission"}'

Those map to POST /api/v1/identities and POST /api/v1/identities/{id}/transitions (both require an Idempotency-Key). The SPIFFE Workload API is now served over a UDS (protocols.spiffe.enabled; a workload FetchX509SVIDs an SVID signed through the signer). The ephemeral, attestation, and broker flows are exercised today through their Go APIs and tests — for example, verifying an AWS instance and issuing a 15-minute SVID — pending their own served surfaces noted below.

Pitfalls & limits

Capability Status today
NHI lifecycle routes (F59) Served/api/v1/identities, /transitions
SPIFFE Workload API (F24) Served — gRPC over a UDS (protocols.spiffe.enabled, EXC-WIRE-02); FetchX509SVID signs through the signer
Ephemeral issuance (F25) Library-complete, tested; no served endpoint yet
Attestation chain (F30) Library-complete, tested (6 attesters, conformance)
AI-agent broker (F61) Library-complete, tested; no served endpoint yet

The SPIFFE Workload API is served (gRPC/UDS); the attestation, ephemeral, and broker components are built and tested behind their interfaces, with their own served surfaces tracked in Current limitations. Operationally: each attestation method needs its trust source configured (cloud roots, cluster JWKS, TPM manufacturer roots), and short TTLs mean workloads must renew — which is the point, but plan for it.

Reference

  • Served routes: POST /api/v1/identities, POST /api/v1/identities/{id}/transitions.
  • Attestation methods: tpm, aws_iid, gcp_iit, azure_imds, k8s_sat, github_oidc.
  • SPIFFE: FetchX509SVIDs, FetchJWTSVIDs; selector match is set-subset.
  • Events: attestation.verified/rejected/bound, ephemeral.issued, spiffe.svid.issued, nhi.*, agent.identity.{issued,refused,revoked}.

See also

SSH (attestation-gated SSH certs use the same chain) · Issuance & certificate authorities · Graph, query & AI (blast radius) · Policy & governance (the broker's policy gate) · glossary: workload, attestation, SPIFFE/SVID

Covers: F24, F25, F30, F59, F61

Rendered live from github.com/ctlplne/trstctl — found a mistake? edit this page.