Identity — SPIFFE/SPIRE¶
Every workload in the cluster gets a cryptographic identity. No static credentials. No service account strings. Every connection carries proof of what it is.
Architecture¶
graph LR
K8S["Kubernetes API\n(pod UID, SA, namespace)"]
SA["SPIRE Server\nStatefulSet\nCA + registration store"]
AG["SPIRE Agent\nDaemonSet (one per node)\nworkload attestor"]
WA["SPIFFE Workload API\nunix socket\n/run/spire/sockets/agent.sock"]
ENV["Envoy (ZT Gateway)\nSDS consumer"]
IST["Istio sidecars\nSDS consumer"]
APP["Applications\nJWT SVID requester"]
K8S -->|"pod attestation"| AG
AG -->|"PSAT attestation"| SA
SA -->|"issue SVID"| AG
AG -->|"deliver via Workload API"| WA
WA -->|"SDS stream"| ENV
WA -->|"SDS stream"| IST
WA -->|"gRPC Workload API"| APP
SPIRE integrates with Istio as the CA: Istio's cacerts secret is populated with the SPIRE-issued intermediate certificate. All Envoy sidecar certificates are SPIFFE SVIDs — Istio's mesh certificates are SVIDs, not a parallel certificate system.
SVID Types¶
X.509 SVID — Used for mTLS. The SPIFFE ID appears in the Subject Alternative Name of the TLS certificate. Renewed every hour without a pod restart; the SPIRE agent pushes the new cert to Envoy via SDS.
Subject: CN=orders-svc
SAN: URI:spiffe://chakra.internal/ns/orders/sa/orders-svc
Not After: <now + 1h>
JWT SVID — Used for OBO delegation. Short-lived (5 minutes), audience-scoped JWT signed by SPIRE. A service requests a JWT SVID for the downstream it intends to call:
{
"sub": "spiffe://chakra.internal/ns/orders/sa/orders-svc",
"aud": ["spiffe://chakra.internal/ns/inventory/sa/inventory-svc"],
"exp": 1714512900,
"iat": 1714512600
}
The SPIRE agent caches JWT SVIDs — calls within the same 5-minute window reuse the cached token.
SPIFFE ID Registry¶
identity/spiffe-ids.yaml — human-authored. Defines which workloads receive SVIDs and their OBO call chain constraints.
- name: inventory-svc
spiffe_id: spiffe://chakra.internal/ns/inventory/sa/inventory-svc
namespace: inventory
service_account: inventory-svc
max_obo_chain_depth: 3
allowed_callers:
- spiffe://chakra.internal/ns/orders/sa/orders-svc
- spiffe://chakra.internal/ns/api-gateway/sa/api-gateway
max_obo_chain_depth: prevents privilege escalation through deep delegation. A chain of A→B→C→D has depth 3; a chain of A→B→C→D→E would be depth 4 and is rejected by validate-obo-chain.rego if the limit is 3.
allowed_callers: OPA's validate-obo-chain.rego checks the JWT SVID subject against this list at every hop. An undeclared service-to-service call path is rejected, even if the mTLS connection is established.
OBO Token Construction¶
# Service making a downstream call on behalf of a user
import spiffe
workload_api = spiffe.WorkloadApiClient()
# Request a JWT SVID scoped to the downstream service
jwt_svid = workload_api.fetch_jwt_svid(
audiences=["spiffe://chakra.internal/ns/inventory/sa/inventory-svc"]
)
headers = {
"X-SPIFFE-OBO-Token": jwt_svid.token,
"X-SPIFFE-Caller-Chain": f"{own_spiffe_id},{existing_chain}",
"X-SPIFFE-User-Principal": user_principal, # from the inbound request
}
The SPIRE agent signs the JWT with the cluster's CA key. The downstream service's Envoy sends it to OPA via ext_authz. OPA verifies the signature against the SPIRE trust bundle (loaded as an OPA bundle from the spire-bundle ConfigMap).
Reference Implementation¶
| File | Purpose |
|---|---|
identity/spire-server.yaml |
SPIRE Server StatefulSet: Kubernetes PSAT attestor, k8sbundle notifier, Prometheus metrics |
identity/spire-agent.yaml |
SPIRE Agent DaemonSet: workload attestor, SDS endpoint for Envoy |
identity/spiffe-ids.yaml |
SPIFFE ID registry: workloads, allowed callers, OBO chain depth limits |
identity/obo-token-policy.rego |
OPA bundle policy: OBO chain validation logic (loaded by both Gatekeeper and standalone OPA) |