
Security
Zero-Trust Workload Identity on Kubernetes: SPIFFE/SPIRE, SVIDs, and the End of Long-Lived Secrets
SPIFFE/SPIRE issues cryptographic, attested workload identity — X.509 and JWT SVIDs — so Kubernetes services get mutual TLS and zero long-lived static secrets.
Ask any platform team how one service authenticates to another and the honest answer is usually some variation of the same thing: a shared secret. An API key in a Secret, a bearer token baked into an environment variable, a mutual-TLS certificate that was issued by hand two years ago and has been copied between namespaces ever since. The service on the other end trusts the caller because the caller *holds the right string*. That is authentication by possession, and it is the single weakest assumption in most production clusters. Anything that can read the string becomes the workload.
Zero trust, applied honestly to workloads rather than to network perimeters, demands the opposite assumption: a workload should be trusted because it can *prove what it is*, not because it carries a credential someone could have stolen. The proof should be cryptographic, short-lived, issued automatically, and tied to attributes of the running process that an attacker cannot forge. That is precisely what SPIFFE defines and what SPIRE implements — and in 2026 both are CNCF graduated projects, sitting in the same production-readiness tier as Kubernetes, Prometheus, and Envoy. This is the *machine* plane of identity; the complementary *human* plane — interactive single sign-on for people — is a separate decision we take up in self-hosted SSO after Okta.
This article builds the case and the architecture for cryptographic workload identity on Kubernetes: how SPIFFE IDs and SVIDs replace shared secrets, how SPIRE's two-layer attestation model issues identity that no attacker can mint, how mutual TLS and trust-domain federation follow from it, and where the operational weight actually lands. The goal is the same one that runs through our secrets management work: a cluster where a leaked credential is worth nothing within the hour, and where every identity assertion is an auditable event rather than an act of faith.
The Problem SPIFFE Solves: Identity by Attestation, Not by Possession
Every authentication system answers one question: *who is this?* In a cluster, the principals asking are not users — they are workloads, scaling up and down, rescheduled across nodes, with IP addresses that mean nothing five minutes from now. The legacy answers all fail in this environment. Network identity (IP allowlists, security groups) is meaningless when pods are ephemeral and a compromised pod inherits its neighbor's address space. Shared secrets are forgeable by anyone who can read them. Hostname-pinned certificates require a manual issuance ceremony that does not survive autoscaling.
SPIFFE — the Secure Production Identity Framework For Everyone — defines a different model. A workload is named by a SPIFFE ID, a URI of the form spiffe://trust-domain/path, for example spiffe://prod.example/ns/payments/sa/payments-api. That identity is carried in a SVID (SPIFFE Verifiable Identity Document), which comes in two forms: an X.509-SVID (a short-lived certificate, used for mutual TLS) and a JWT-SVID (a signed token, used where TLS is impractical, such as authenticating to an external API or an OIDC-aware cloud service). The SVID is the cryptographic proof of the SPIFFE ID. Crucially, the workload never chooses its own identity and never holds a long-lived credential it could leak — the identity is *issued to it* after the platform verifies what it is.
That verification step is the heart of the model and the thing that makes it qualitatively different from a secrets manager. A secrets manager hands a credential to whoever can authenticate to the secrets manager — which usually means whoever holds the bootstrap token. SPIFFE inverts the trust: identity is *attested*. The platform inspects properties of the running workload — its node, its namespace, its ServiceAccount, its container image — and issues an SVID only if those properties match a registered policy. There is no shared join secret to steal, because identity is derived from the workload's own attributes rather than from a key it carries.
SPIRE Architecture: Server, Agents, and Two Layers of Attestation
SPIRE is the production reference implementation of the SPIFFE APIs. A deployment has two components. The SPIRE Server is the signing authority and the source of truth: it holds the CA (self-signed, chained to an upstream CA such as Vault's PKI engine, or backed by a disk/AWS/KMS key), it stores the registration entries that define who gets which identity, and it can expose an OIDC discovery endpoint so JWT-SVIDs are verifiable by any standard OIDC consumer. The SPIRE Agent runs as a DaemonSet — one per node — and is the only component that talks to workloads, via a Unix domain socket called the Workload API. A workload fetches its SVID over that socket; no token, no network round-trip to the server, nothing the pod has to be pre-provisioned with.
Trust is established in two distinct attestation layers, and keeping them separate is what makes the system hard to subvert. Node attestation is how an agent proves to the server that it is a legitimate agent on a legitimate node — using node-level evidence that cannot be forged from inside a pod: a Kubernetes projected ServiceAccount token validated against the API server, a cloud instance identity document (AWS, GCP, Azure), or a TPM-backed quote on bare metal. Workload attestation is how the agent proves what a *specific process* is before handing it an SVID: the agent's workload attestor plugins query the kernel and the kubelet for the calling process's pod UID, namespace, ServiceAccount, and labels, and match them against the selectors in the registration entries.
The two layers compose into a chain of custody for identity. The server trusts the agent because of node attestation. The agent trusts the workload because of workload attestation. The workload trusts the SVID because it chains to the trust bundle. At no point does a human copy a secret, and at no point does a forgeable attribute (an IP, a label a pod can set on itself, a token it could read from disk) suffice on its own. This is the property that lets SPIFFE/SPIRE claim *attested* identity rather than asserted identity — the audit-grade rigor distinction between a control you can demonstrate and a convention you hope holds.
server {
bind_address = "0.0.0.0"
bind_port = "8081"
trust_domain = "prod.example"
data_dir = "/run/spire/server/data"
# Short SVID lifetimes bound the blast radius of any leak.
default_x509_svid_ttl = "1h"
default_jwt_svid_ttl = "5m"
ca_ttl = "24h"
}
plugins {
DataStore "sql" {
plugin_data {
database_type = "postgres"
connection_string = "dbname=spire host=spire-db user=spire sslmode=require"
}
}
# Node attestation: trust agents that present a projected SA token
# the API server validates via TokenReview.
NodeAttestor "k8s_psat" {
plugin_data {
clusters = {
"prod" = {
service_account_allow_list = ["spire:spire-agent"]
}
}
}
}
KeyManager "disk" {
plugin_data { keys_path = "/run/spire/server/data/keys.json" }
}
}The k8s_psat (projected ServiceAccount token) node attestor is the right default on Kubernetes: the agent presents a projected token, and the server validates it against the cluster's TokenReview API, confirming the agent runs under the expected ServiceAccount before issuing it an agent SVID. The short default_x509_svid_ttl of one hour is deliberate — SPIRE rotates each X.509-SVID automatically at roughly 80% of its lifetime (around 48 minutes), so a leaked certificate is useless within the hour and the rotation needs no human, no restart, and no ticket.
Registration Entries: The Policy That Decides Who Becomes Whom
Registration entries are where the access model lives. An entry is a mapping that tells the SPIRE Server: *any process matching these selectors is entitled to this SPIFFE ID.* The selectors are the attestation evidence — k8s:ns:payments, k8s:sa:payments-api, k8s:pod-label:app:payments, k8s:container-image:.... If a process does not match an entry, it gets no SVID. There is no default identity, no fallback, no wildcard issuance. This fail-closed default is what makes the registration table a security boundary rather than a convenience.
# 1. Register the per-node agent identity (parent for workloads on that node)
spire-server entry create \
-spiffeID spiffe://prod.example/agent/k8s-node \
-selector k8s_psat:cluster:prod \
-node
# 2. Register the payments-api workload.
# parentID ties the entry to the node-agent identity above;
# selectors are the workload-attestation evidence the agent must observe.
spire-server entry create \
-parentID spiffe://prod.example/agent/k8s-node \
-spiffeID spiffe://prod.example/ns/payments/sa/payments-api \
-selector k8s:ns:payments \
-selector k8s:sa:payments-api \
-selector k8s:pod-label:app:payments-api \
-dnsName payments-api.payments.svc \
-x509SVIDTTL 3600
# Inspect what an entry would issue, and to which selectors:
spire-server entry show -spiffeID spiffe://prod.example/ns/payments/sa/payments-apiHand-maintaining entries does not scale, and nobody runs production that way. The SPIFFE CSI Driver and the SPIRE Controller Manager automate registration: the controller manager watches a ClusterSPIFFEID custom resource and reconciles entries from declarative rules ("every pod with label spiffe.io/spiffe-id: true in these namespaces gets spiffe://prod.example/ns/{namespace}/sa/{serviceAccount}"), while the CSI driver mounts the Workload API socket into pods without a privileged hostPath. This is the same policy-as-code discipline we apply to admission control, applied to identity issuance: the registration table is generated from version-controlled intent, reviewed in pull requests, and auditable as a Git history rather than a pile of imperative CLI calls.
Mutual TLS Everywhere: How Two Workloads Authenticate With SVIDs
With both ends holding X.509-SVIDs, mutual TLS stops being a certificate-management project and becomes a property of the runtime. Each workload fetches its own SVID and the trust bundle from the local Workload API. When the client opens a TLS connection, it presents its X.509-SVID; the server presents its own; each side validates the other's certificate against the trust bundle and then — and this is the part conventional mTLS misses — authorizes on the *SPIFFE ID in the certificate's URI SAN*, not on a hostname or an IP. The server's policy is allow spiffe://prod.example/ns/payments/sa/payments-api, expressed in terms of identity that survives rescheduling, scaling, and IP churn.
The integration surface is the pleasant part. Envoy consumes SVIDs natively via SDS (Secret Discovery Service) pointed at the SPIRE Agent, so any Envoy-based proxy or mesh gets SPIFFE mTLS without application changes. Istio ships SPIFFE-compliant identity by default — Istiod's CA issues X.509-SVIDs — and for organizations that want a single certificate authority across both mesh and non-mesh workloads, Istiod can be configured to delegate issuance to an external SPIRE deployment. For applications outside a mesh, the go-spiffe and equivalent libraries fetch and rotate SVIDs in a few lines; the application asks for an mTLS-ready TLS config and the library keeps it current.
Across organizational or cluster boundaries, SPIFFE Federation extends the same handshake without a shared CA. Each trust domain runs its own SPIRE Server with its own root of trust. A bundle endpoint serves that domain's public trust bundle; a federated peer fetches it and can then verify SVIDs issued by the other domain. A payments service in spiffe://prod.example can establish mutual TLS with a partner's ledger service in spiffe://partner.example because each side has fetched the other's bundle — no private key ever crosses the boundary, no joint CA hierarchy has to be negotiated, and revoking the federation is a matter of dropping a bundle. This is the optionality argument made concrete: trust relationships are composable and reversible rather than welded together.
Killing Long-Lived Secrets: JWT-SVIDs, OIDC Federation, and Keyless Cloud Auth
X.509-SVIDs handle service-to-service mTLS inside the cluster. The harder secrets to kill are the ones pointing *outward*: the static AWS access key a pod uses to read S3, the long-lived database password, the API token for a SaaS provider. These are the credentials that end up in a Secret, persist in etcd past the workload's lifetime, and turn up in every audit. JWT-SVIDs plus SPIRE's OIDC discovery provider eliminate a large class of them.
SPIRE can expose an OIDC-compliant JWKS endpoint that publishes the public keys used to sign JWT-SVIDs. That turns SPIRE into an OIDC identity provider that a cloud platform's IAM can be configured to trust. The workload fetches a JWT-SVID over the Workload API and exchanges it — via the cloud's standard OIDC web-identity federation — for a short-lived cloud credential. No static cloud access key is ever provisioned, stored, or rotated. The same pattern authenticates to a secrets engine: Vault and OpenBao both ship a SPIFFE/JWT auth method, so a workload proves its SPIFFE ID to obtain a Vault token and then a dynamic, short-lived backend credential.
This is the same conclusion our secrets management architecture reaches from the other direction: dynamic, short-lived credentials beat static ones because the leaked-credential blast radius collapses from *indefinite* to *minutes*. SPIFFE/SPIRE generalizes that result from secrets to identity itself. Where the secrets-management stack issues short-lived *credentials*, SPIRE issues short-lived *identity*, and identity is the thing every credential exchange is ultimately trying to establish. The two are complementary layers of the same posture, not competing tools.
Audit Evidence: Why Attested Identity Is the Strongest Compliance Story You Can Tell
Every serious security audit — SOC 2 Type II CC6.1–CC6.3, NIS2 Article 21 measures, an internal access review — asks four questions about machine-to-machine access: what identities exist, what each is allowed to do, how access is authenticated, and whether you would detect misuse. A shared-secret architecture answers these badly. The identity inventory is a grep through manifests; authentication is "the caller had the key"; misuse detection is non-existent because every holder of the key is indistinguishable. SPIFFE/SPIRE answers all four structurally.
- Inventory — the registration entries are a machine-readable, queryable catalogue of every workload identity, the selectors that earn it, and the trust domain it belongs to.
spire-server entry showis the inventory auditors ask for; it is always current because it *is* the issuance policy. - Authentication — access is gated on a cryptographically attested SPIFFE ID, not a bearer string. "How do you know the caller is the payments service?" has a real answer: node attestation plus workload attestation plus a certificate chaining to your trust bundle.
- Authorization — policy is expressed in stable identity terms (
allow spiffe://prod.example/ns/payments/sa/payments-api) that auditors can read and that do not drift as pods reschedule. - Detection — every SVID issuance is a logged event with a timestamp, the attested selectors, and the SPIFFE ID granted. Shipped to an immutable SIEM, that stream is a continuous record of which workload obtained which identity, when — the misuse-detection substrate a static secret can never provide.
The audit question that exposes shared secrets is simple: 'show me, for last Tuesday, every workload that authenticated as the payments service and prove none of them were impersonators.' With shared secrets there is no answer — possession is indistinguishable from identity. With SPIFFE, the answer is the issuance log plus the attestation policy, and it is the same answer every time.
There is a sovereignty dimension here that compounds the audit story. SPIFFE is an open specification and SPIRE is Apache-2.0, CNCF-graduated software. The trust domain, the CA, the registration policy, and the issuance log all live in infrastructure you operate and can inspect — there is no vendor identity plane to which you have delegated the question *who is allowed to be the payments service*. For regulated environments running self-hosted infrastructure, owning the root of workload identity outright — rather than renting it from a cloud IAM whose internals you cannot audit — is the difference between a compliance narrative you assert and one you can demonstrate from primary evidence.
Adoption Path: Where SPIRE Earns Its Operational Weight (and Where It Doesn't Yet)
SPIFFE/SPIRE is not free to operate, and pretending otherwise sets teams up to fail. You are standing up a certificate authority, a datastore, a per-node DaemonSet, and a registration-automation layer, then integrating SVID consumption into workloads. The right sequencing makes each step deliver value independently so the investment is never all-or-nothing.
Start where a mesh already does the heavy lifting. If you run Istio, you are already issuing SPIFFE identities — formalize the trust domain, consider delegating Istiod's CA to a dedicated SPIRE deployment for unified governance, and you have mesh-wide mTLS on attested identity with little incremental work. From there, extend SPIRE to the non-mesh workloads that need to authenticate to *external* systems — the pods holding static cloud keys and database passwords — and use JWT-SVIDs with OIDC federation to delete those static secrets first, because that is where the audit risk concentrates. Cross-cluster and cross-organization mutual TLS via federation is the last and most advanced step, justified when you genuinely have trust boundaries to span.
The honest current-state caveats, as of 2026: registration automation is solid but still a moving target — the SPIRE Controller Manager and ClusterSPIFFEID CRDs are the right path and should be adopted from day one rather than hand-rolling entry scripts. The SPIRE Server datastore and CA are your most security-critical assets and deserve the same disaster-recovery rigor as etcd itself. And SVID consumption in legacy applications that cannot speak the Workload API needs a sidecar or the SPIFFE CSI Driver, which is mature but is an integration step, not a flip of a switch. None of these are blockers. They are the reason this is an architecture decision with a sequencing plan, not an afternoon's install.
The destination is worth the climb. A cluster on attested workload identity is one where the answer to *who is this workload?* is cryptographic and continuously re-proven; where a stolen credential is worthless within the hour; where mutual TLS is a runtime property rather than a certificate-rotation chore; and where the identity inventory, the authentication mechanism, and the issuance log are all primary audit evidence you own outright. That is what Stribog builds for teams that have to demonstrate, not just assert, that they control who is allowed to be each part of their system. Start by naming your trust domain. Everything else follows from there.
§FAQ/Common questions
Frequently asked
What is the difference between a SPIFFE ID and an SVID?
A SPIFFE ID is the name of a workload — a URI like spiffe://prod.example/ns/payments/sa/payments-api. An SVID (SPIFFE Verifiable Identity Document) is the cryptographic proof of that name. SVIDs come in two forms: an X.509-SVID is a short-lived certificate used for mutual TLS, and a JWT-SVID is a signed token used where TLS is impractical, such as authenticating to an external OIDC-aware API. The ID is the identity; the SVID is the credential that proves it, and it is reissued continuously with a short TTL rather than held long-term.
How does SPIRE prevent a malicious pod from getting another workload's identity?
Through two layers of attestation. Node attestation proves the SPIRE Agent runs on a legitimate node using non-forgeable evidence (a projected ServiceAccount token validated against the API server, a cloud instance document, or a TPM quote). Workload attestation then proves what a specific process is: the agent inspects the calling pod's UID, namespace, ServiceAccount, and labels from the kernel and kubelet, and issues an SVID only if those attributes match a registration entry's selectors. A pod cannot forge another workload's namespace, ServiceAccount binding, or node placement, so it cannot obtain an identity it is not entitled to. If no entry matches, no SVID is issued.
Do I need a service mesh to use SPIFFE/SPIRE?
No. A service mesh is a convenient consumer — Istio ships SPIFFE identity natively, and Envoy can consume SVIDs via SDS — but SPIRE issues identity independently of any mesh. Non-mesh workloads fetch SVIDs directly from the Workload API socket using the go-spiffe libraries or the SPIFFE CSI Driver, and use them for application-level mutual TLS or, via JWT-SVIDs, for keyless authentication to cloud IAM and secrets engines. SPIFFE/SPIRE is often most valuable precisely for the workloads outside a mesh that need to authenticate to external systems without static keys.
How does SPIFFE/SPIRE eliminate long-lived secrets?
In two ways. For service-to-service communication, X.509-SVIDs replace shared API keys and hand-issued certificates with short-lived (typically one-hour) certificates that SPIRE rotates automatically at about 80% of their lifetime, so nothing static persists. For authentication to external systems, SPIRE exposes an OIDC discovery endpoint; a workload exchanges a JWT-SVID for a short-lived cloud credential or a Vault/OpenBao token via standard OIDC federation, so no static cloud access key or database password is ever provisioned or stored. The leaked-credential blast radius collapses from indefinite to roughly the SVID's TTL.
What is the main operational risk of adopting SPIRE?
The SPIRE Server is the certificate authority for your entire trust domain — compromising it lets an attacker mint any identity, which is a sharper failure mode than leaking one individual secret. The model trades many small static-secret risks for one large, concentrated root of trust, and that root must be defended explicitly: an upstream CA you control (Vault PKI or an HSM/KMS-backed key), a hardened and audited datastore, restrictive node-attestation allow-lists, and a tested CA disaster-recovery plan. Done properly this is a better overall risk posture, but the concentration of risk is real and must be planned for, not discovered.
Further reading
- Your own ACME: a sovereign internal PKI with step-ca and cert-manager
- Digital sovereignty: from policy slogan to testable architecture
- Gaia-X in practice: federation, labels, and the sovereignty gap
- Confidential computing: protecting data-in-use with hardware TEEs
- Infrastructure and security capabilities
- Secrets management: ESO + short-lived credentials
- Zero-trust networking with eBPF and Cilium
- Policy as code with Kyverno at scale
- NIS2, DORA, and the AI Act for self-hosted clusters
- Self-hosted SSO: Keycloak vs Authentik vs Zitadel after Okta
- Runtime threat detection with Falco and Tetragon
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.