SPIFFE workload identity
Phase 4 §7.2 of RESTRUCTURING_PLAN.md. SPIFFE-bound identities replace the long-lived OAuth client-credentials grant currently used by
M2MTokenIssuerfor service-to-service authentication.
Why workload identity
The pre-Phase-4 M2MTokenIssuer mints short-lived JWTs via the
Auth0 / Entra client_credentials grant, but those tokens are
still bearer credentials — exfiltrate the JWT and you can replay it
from anywhere until it expires. SPIFFE-bound identities (SVIDs) are
workload-attested via the platform (UID, cgroup, label selectors) —
much harder to steal and automatically rotated by the SPIRE Server.
| Aspect | OAuth client_credentials | SPIFFE JWT-SVID |
|---|---|---|
| Issuer | Auth0 / Entra tenant | SPIRE Server (in-cluster) |
| Attestation | Shared client_secret (long-lived) | Node + workload attestor (live) |
| Bearer-token replay risk | High (until expiry) | Low (selectors validated by Workload API) |
| Rotation | Manual / scheduled | Automatic, per-SVID-lifetime |
| Cross-cell scope | Implicit (issuer trusts all audiences) | Explicit (spiffe://alpha-swarm.ai/cell/<id>/... trust-domain path) |
Trust domain layout
AlphaSwarm runs ONE trust domain — alpha-swarm.ai. Each cell carries a
namespace-scoped trust-domain prefix:
spiffe://alpha-swarm.ai/cell/<cell-id>/<service-account-name>
Example SPIFFE IDs:
| Cell | Service | SPIFFE ID |
|---|---|---|
cell-shared-std-local | alphaswarm-core | spiffe://alpha-swarm.ai/cell/cell-shared-std-local/alphaswarm-core |
cell-silo-reg-acme | alphaswarm-worker | spiffe://alpha-swarm.ai/cell/cell-silo-reg-acme/alphaswarm-worker |
cell-shared-std-us-east-1a | alphaswarm-tenant-router | spiffe://alpha-swarm.ai/cell/cell-shared-std-us-east-1a/alphaswarm-tenant-router |
Cross-cell calls validate the full SPIFFE ID, not just the trust domain — Cell-Bound-Authorization (Phase 5 §8.5) extends this with biscuit capability tokens that pin a request to a specific cell.
Deployment shape
Each cell runs ONE SPIRE control plane:
[ SPIRE Server StatefulSet ] (spire-system namespace)
▲
│ k8s_psat attest
│
[ SPIRE Agent DaemonSet ] (one per node)
▲
│ unix socket: /run/spire/sockets/agent.sock
│
[ AlphaSwarm workload pod ] (mounts the socket via hostPath volume)
│
└── spiffe.workloadapi.fetch_svid(audiences=[...])
The matching manifests live at:
alphaswarm_platform/deployments/kubernetes/mesh-identity/spire/server.yamlalphaswarm_platform/deployments/kubernetes/mesh-identity/spire/agent.yaml
Per-cell installs come from the Argo CD ApplicationSet at
alphaswarm_platform/deployments/argocd/applicationsets/cells-appset.yaml
(Phase 4.5 extends it with a mesh-identity component column).
AlphaSwarm integration
The application-side integration lives in
alphaswarm/auth/providers/spiffe.py
(SpiffeIdentityProvider). It implements the
:py:class:alphaswarm.auth.providers.protocol.IdentityProvider interface
but only the :py:meth:m2m_token method does real work — SPIFFE
is workload-only and does NOT participate in user OIDC flows. The
existing Auth0 / Entra providers stay wired for user-facing login.
Wiring
# Operator sets the workload API socket path (default is the
# conventional /run/spire/sockets/agent.sock from the SPIRE Agent
# DaemonSet's hostPath mount).
export ALPHASWARM_AUTH_SPIFFE_WORKLOAD_API_SOCKET="unix:///run/spire/sockets/agent.sock"
# Route the M2MTokenIssuer through SPIFFE instead of Auth0.
# (Phase 4.5 deliverable — the M2MTokenIssuer side is still TODO.)
export ALPHASWARM_AUTH_M2M_PROVIDER=spiffe
When the SPIFFE socket isn't reachable (development mode, smoke
tests, migrations), SpiffeIdentityProvider.m2m_token raises
IdentityProviderError. The fallback chain in
alphaswarm.credentials.resolver re-tries the legacy Auth0 path so
developers can iterate without a running SPIRE Agent.
Pod template requirements
For a pod to consume SVIDs from the SPIRE Workload API:
-
Mount the agent's host socket:
volumes:
- name: spire-agent-socket
hostPath:
path: /run/spire/sockets
type: Directory
containers:
- name: ...
volumeMounts:
- name: spire-agent-socket
mountPath: /run/spire/sockets
readOnly: true -
Set
SPIFFE_ENDPOINT_SOCKET=unix:///run/spire/sockets/agent.sockin the pod env (or rely on the AlphaSwarm default). -
Be in the
spire-systemClusterSPIFFEIDselector — the matching CRD is shipped per-cell in Phase 4.5; today thek8s_psatNode Attestor accepts every workload with a matching ServiceAccount.
Rotation + revocation
- SVID lifetime: 1h X.509-SVID, 5m JWT-SVID (configurable via the SPIRE Server config map).
- Trust anchor lifetime: 168h (7 days). Operators rotate the root via Vault PKI; the SPIRE Server propagates the new bundle to every Agent within ~1 minute.
- Revocation: deleting a workload's
RegistrationEntryfrom the SPIRE Server invalidates all future SVID issuance. Existing in-flight SVIDs expire at their natural TTL — for an immediate cut-off, also rotate the trust anchor.
Failure modes
| Failure | Behaviour |
|---|---|
| SPIRE Agent socket missing | SpiffeIdentityProvider.m2m_token raises IdentityProviderError |
| SPIRE Server unreachable | Agent serves cached SVID until it expires (~1h) |
| Workload not attested | fetch_svid raises; M2M chain falls through to Auth0 |
| Trust anchor rotation | SVIDs continue to validate during the 7-day overlap window |
Phase 4.5 follow-ups
- Per-cell
ClusterSPIFFEIDCRDs that bind workload selectors to SPIFFE IDs (today the spine relies on the default k8s_psat attestor). - M2MTokenIssuer dispatch — wire
ALPHASWARM_AUTH_M2M_PROVIDER=spiffeinto the issuer so it picks SPIFFE for M2M without affecting user OIDC flows. - Linkerd integration — Linkerd consumes SPIFFE identity for mTLS termination (Phase 4 §7.1). Phase 4.5 wires the SPIFFE trust anchor into Linkerd's Identity service.
- OIDC discovery provider — SPIRE Server can expose an OIDC discovery endpoint that lets non-SPIRE-aware services (Pomerium, Cloudflare Access) validate SVIDs as standard OIDC JWTs.
- Cross-cell federation — Phase 8 §11.2 multi-region cells will need SPIFFE trust-domain federation.