Most service-to-service authentication still relies on static secrets that someone has to create, distribute, store and rotate, and every one of those steps is a place where the secret leaks. SPIFFE (Secure Production Identity Framework for Everyone) replaces those secrets with short-lived, cryptographically verifiable identity documents that workloads obtain at runtime by proving what they are, and SPIRE is the reference implementation that issues them. This guide explains the SPIFFE ID, the two SVID formats, the Workload API, trust bundles and federation, then how SPIRE attests nodes and workloads, how to design registration entries, how to roll it out and what to monitor. It is written for platform engineers, security architects and identity teams who are evaluating or operating SPIFFE in Kubernetes, VM or multi-cloud estates. For where SPIFFE sits among other identity standards, see the Agent Identity Standards Tracker.
Key takeaways
- Identity is attested, not issued by hand. A workload receives its SPIFFE identity because the platform can prove facts about it (node, namespace, service account, image), so there is no bootstrap secret to leak.
- Prefer X.509-SVIDs for mTLS. The SPIFFE project prefers X.509-SVIDs because JWT-SVIDs are bearer tokens that can be replayed; use JWT-SVIDs only where an L7 proxy or load balancer breaks the TLS connection.
- Registration entries are your security policy. A loose selector, such as namespace only, hands one identity to every pod in that namespace. Selector design deserves the same review as firewall rules.
- Authentication is not authorisation. SPIFFE and SPIRE prove who a workload is. You still need policy in a proxy, mesh or application that decides what each SPIFFE ID may do.
- The SPIRE server is a tier-zero asset. It is the signing authority for the trust domain; its keys, datastore, admin IDs and upstream CA need privileged-access controls.
The SPIFFE building blocks
SPIFFE is a set of specifications, not a product. Five concepts carry the whole model.
- SPIFFE ID: a URI of the form
spiffe://trust-domain/path, for examplespiffe://prod.example.com/payments/api. The trust domain may use only lowercase letters, digits, dots, dashes and underscores, with no port or user info. The path may not contain empty,.or..segments, or a trailing slash, and query strings and fragments are forbidden. Implementations must accept IDs up to 2,048 bytes, and the trust domain is capped at 255 bytes. The scheme and trust domain are case-insensitive; the path is case-sensitive, which matters when you write policy that compares strings. - Trust domain: the trust root, backed by its own signing keys. The SPIFFE concepts page recommends separate trust domains for environments with different security levels, such as staging and production.
- SVID (SPIFFE Verifiable Identity Document): the document a workload presents. It carries exactly one SPIFFE ID and is valid only when signed by an authority of that ID's trust domain. The specification defines X.509 and JWT formats, and the newer WIT (workload identity token) format is listed alongside them.
- Workload API: a local API, normally a Unix domain socket, from which a workload obtains its SVID, private key and trust bundle. The caller needs no prior knowledge of its identity and no authentication token: the platform identifies it.
- Trust bundle: the public key material used to verify SVIDs from a trust domain, CA certificates for X.509-SVIDs and raw public keys for JWT-SVIDs. Bundles rotate frequently and are delivered through the same Workload API.
X.509-SVID and JWT-SVID compared
| Property | X.509-SVID | JWT-SVID |
|---|---|---|
| Where the SPIFFE ID lives | Exactly one URI SAN; validators must reject a leaf with more than one | sub claim |
| Proof of possession | Yes, private key used in the TLS handshake | No, bearer token that can be replayed |
| Key constraints | Leaf: cA=false, digitalSignature set, no keyCertSign or cRLSign; if EKU present, both serverAuth and clientAuth | Signed with RS256/384/512, ES256/384/512 or PS256/384/512 only; any other alg must be rejected |
| Audience | Not applicable; the peer checks the SPIFFE ID | aud required; a single, specific value is recommended |
| SPIRE default lifetime | 1 hour (default_x509_svid_ttl) | 5 minutes (default_jwt_svid_ttl) |
| Best fit | Service-to-service mTLS, sidecars, meshes | Calls through L7 proxies or load balancers that terminate TLS, and federation to cloud or OAuth systems that accept OIDC-style tokens |
The JWT-SVID specification warns against broad audiences such as production or the bare trust domain, because one compromised receiver could then replay the token to every other service. Scope aud to the target service, for example spiffe://prod.example.com/reports. It also allows only JWS compact serialisation, permits only the alg, kid and typ header parameters, and requires every network hop carrying the token to be confidential (a local Unix socket is the accepted exception). For the broader pattern of bearer tokens between services, see the Machine-to-Machine OAuth (Client Credentials) Guide.
How SPIRE issues identity
SPIRE has two components. The SPIRE server is the signing authority for one trust domain: it holds the signing keys, stores registration entries, attests agents and mints SVIDs. A SPIRE agent runs on every node that hosts workloads, exposes the Workload API locally (by default at /tmp/spire-agent/public/api.sock on Linux), attests the workloads that call it and caches their SVIDs.
- Node attestation: the agent proves the node's identity to the server using evidence the platform provides, such as a cloud instance identity document, a TPM-backed key, an existing X.509 certificate, a projected Kubernetes service account token or a one-time join token. The server verifies the evidence and issues the agent its own SPIFFE ID, for example
spiffe://prod.example.com/spire/agent/k8s_psat/cluster-a/<node UID>. - Authorised entries: the server sends the agent only the registration entries it is entitled to, found through the agent's parent ID and node selectors. A compromised node cannot ask for every identity in the trust domain.
- Workload attestation: when a process calls the Workload API, the agent identifies its PID through the kernel, asks its workload attestors (Unix, Kubernetes, Docker, systemd, Slurm) for selectors, and matches them against the cached entries.
- Issue and rotate: the matching SVID and bundle are returned and then streamed again before expiry. Workloads never handle a long-lived credential.
Node attestor choices
| Plugin | Evidence | Notes |
|---|---|---|
k8s_psat | Projected service account token of the agent pod, checked through the Kubernetes TokenReview API | Default audience spire-server; restrict with service_account_allow_list (for example spire:spire-agent). Needs get on pods and nodes and create on tokenreviews. |
aws_iid, azure_msi, azure_imds, gcp_iit | Cloud instance identity documents or tokens | Strongest option for VMs; pair with account or project allow lists |
tpm_devid | TPM-held DevID key | Hardware root of trust for bare metal and edge |
x509pop, sshpop | Proof of possession of an existing X.509 or SSH host certificate | Reuses an existing PKI; only as strong as that PKI's issuance |
join_token | Pre-shared token generated on the server | Bootstraps one agent; default token TTL 600 seconds. A static secret by another name, so keep it for labs and exceptional cases |
Designing registration entries and selectors
A registration entry maps a SPIFFE ID to a parent ID and a set of selectors, and every selector must match before the identity is issued. It is created with spire-server entry create -parentID ... -spiffeID ... -selector type:value, optionally with -x509SVIDTTL to override the lifetime. Entries are where most real weakness hides, so treat them as code: version them, review them and test them.
- Combine selectors:
k8s:ns:paymentsalone gives the identity to anything scheduled in that namespace. Addk8s:sa:payments-apiand, where image provenance is reliable,k8s:container-imageor the Sigstore-backedk8s:image-signature:verifiedselectors. For VMs, combineunix:uidwith a node selector from the attestor. - Use stable, meaningful paths: a convention such as
/ns/<namespace>/sa/<service-account>(the format Istio uses) or/<team>/<service>/<env>makes policy readable. Avoid pod names or other ephemeral values in the ID, because authorisation policy will be written against it. - Keep the kubelet check strict: the Kubernetes workload attestor queries the kubelet on its secure port (10250 by default). Do not set
skip_kubelet_verificationoruse_anonymous_authenticationin production. - Limit privileged flags:
-admingrants an ID access to the SPIRE server APIs and-downstreamlets an entry act as a downstream SPIRE server that can mint identities. Both should be rare and owned. - Automate entry creation: in Kubernetes, a controller that derives entries from labelled workloads removes manual drift; outside Kubernetes, generate entries from the same pipeline that deploys the service, and delete them when the service is retired. See the NHI Offboarding and Decommissioning Guide.
Federation and cross-domain trust
Federation lets workloads in one trust domain verify SVIDs from another without merging the two. Each trust domain publishes its bundle at a bundle endpoint, an HTTPS URL answered with a SPIFFE bundle, similar in role to an OIDC jwks_uri. Relationships are one-way, so mutual trust needs configuration on both sides.
- Profiles:
https_webauthenticates the endpoint with a publicly trusted Web PKI certificate;https_spiffeauthenticates it with an X.509-SVID and needs the endpoint's SPIFFE ID plus an initial bootstrap bundle when the endpoint serves its own domain. - Three parameters, configured not inferred: the client needs the trust domain name, the URL and the profile. The specification says none can be safely derived from the others and that falling back from one profile to another is unsafe. Tampering with any of them breaks the federation's security.
- Refresh and rotation: clients poll at the bundle's
spiffe_refresh_hint(five minutes if no hint is set). New keys should be published three to five times the refresh hint before they are used to sign. - Never merge bundles: each bundle verifies only its own trust domain's SVIDs, and long trust chains across many domains are discouraged.
- Federating to clouds and OAuth: SPIRE's OIDC Discovery Provider publishes
/.well-known/openid-configurationand/keysso that a cloud provider or authorisation server can validate JWT-SVIDs, giving a SPIFFE-backed route into cloud IAM or an OAuth token exchange without stored cloud keys. Restrict itsdomainslist and avoidallow_insecure_scheme. See the Workload Identity Federation Guide and the OAuth Token Exchange and Delegation Guide.
Using SVIDs: Envoy, meshes and authorisation
Applications can consume SVIDs directly through the go-spiffe or other SPIFFE libraries, or a proxy can do it for them.
- Envoy through SDS: the SPIRE agent acts as Envoy's Secret Discovery Service provider. It attests Envoy, then streams rotated certificates, keys and bundles to it without writing private keys to disk. The SDS secret name is the SPIFFE ID Envoy serves, and the validation context name is the trust domain.
- Peer pinning: restrict which peers a listener accepts with
match_typed_subject_alt_namesagainst the expected SPIFFE ID, not just "any certificate from the trust domain". - Authorisation policy: the SPIFFE documentation is explicit that SPIRE handles authentication only. Write allow rules keyed on SPIFFE IDs in Envoy filters, a service mesh policy engine or the application, and default to deny.
- Service meshes: several meshes issue SPIFFE-format identities from their own CA. You can run SPIRE as the issuer instead, to unify identity across mesh and non-mesh workloads. See the Service Mesh Security Guide and the mTLS Guide.
When to adopt SPIFFE, and how much
| Situation | Reasonable choice | Trade-off |
|---|---|---|
| Single cloud, workloads mostly call that cloud's services | Native workload identity (cloud managed identities, Kubernetes federation) | Simple and managed, but identity stops at the cloud boundary |
| One Kubernetes cluster with a mesh | Mesh-issued SPIFFE identities | Little extra to run; harder to extend to VMs and other clusters |
| Multi-cloud, hybrid, VMs plus Kubernetes | SPIRE with per-environment trust domains and federation | One identity model everywhere; you operate a tier-zero CA service |
| Regulated or high-assurance estate | SPIRE with an UpstreamAuthority chained to a hardware-protected enterprise CA and a key manager backed by a KMS or HSM | Higher assurance and audit; more integration and change control |
Small organisations rarely need SPIRE: native cloud workload identity removes most static secrets at lower cost. The case strengthens with the number of platforms, the need for mTLS between them and the volume of credentials currently held in vaults purely for service-to-service calls. For the cloud-native options, see the Cloud Workload Identity Guide.
Implementation roadmap
- Inventory and scope: list service-to-service credentials (API keys, shared passwords, long-lived certificates) and pick one or two call paths with a clear owner. Decide trust domain names now, one per environment, because renaming later re-issues every identity.
- Harden the server first: run it highly available with PostgreSQL or MySQL rather than the default SQLite datastore, keep signing keys in a KMS or HSM-backed key manager, set
tls_config(minimum TLS 1.2 is enforced), list the fewadmin_ids, setplugin_checksumfor external plugins and leaveallow_nonconforming_agent_idsfalse. - Choose node attestation: platform-backed attestors (
k8s_psatwith an allow list, cloud identity documents, TPM) over join tokens. Never setinsecure_bootstrapon agents outside a lab. - Pilot in parallel: issue SVIDs to the pilot services while the old secret still works. Turn on mTLS in permissive or logging mode in the proxy, confirm both sides see the expected SPIFFE IDs, then enforce.
- Add authorisation: write allow rules per SPIFFE ID, test denials, and only then revoke the static secret the call used to depend on.
- Tune lifetimes: keep X.509-SVIDs at an hour or less and JWT-SVIDs at minutes. Confirm rotation under load and during a server outage, since agents serve cached SVIDs only until they expire.
- Federate deliberately: add bundle endpoints per partner domain with documented owners, then the OIDC Discovery Provider for cloud access.
- Rollback: keep the old credential path disabled but restorable until the new path has run cleanly through at least one full CA rotation; rollback is re-enabling it and switching the proxy back to permissive mode.
Detection and measurement
- Server events: log and alert on registration entry creation, changes and deletions, especially those with
-adminor-downstream; agent attestation failures and bans; and changes to federation relationships. - Agent events: repeated workload attestation failures, which can mean a misconfigured selector or a process trying to obtain an identity it should not have. Keep
log_selectorslimited, since selector values can be sensitive. - Proxy and application logs: the peer SPIFFE ID on each mTLS connection, and denied requests by source ID. This is also your service dependency map.
- Federation fetches: the federation specification recommends logging bundle endpoint fetches for forensics.
Metrics that show progress:
- Percentage of service-to-service calls authenticated by SVID rather than a static secret.
- Number of static service credentials retired, tracked against the original inventory.
- Registration entries using a single broad selector (target: zero in production).
- Median SVID lifetime and rotation failures per week.
- Time to revoke a workload's identity (entry deletion to last valid SVID expiring).
Common failure patterns
- Namespace-only selectors: every pod in the namespace, including a compromised debugging pod, receives the production identity. Always pair namespace with service account and, where possible, image.
- Join tokens as a permanent pattern: agents bootstrapped with join tokens reintroduce exactly the distributable secret SPIFFE was meant to remove. The SPIRE server documentation limits each token to bootstrapping one agent; treat any reuse as a defect.
- JWT-SVIDs with broad audiences: a token scoped to the whole trust domain can be replayed by any receiving service to all others. Use one audience per target and the shortest workable lifetime.
- mTLS without authorisation: accepting any SVID from the trust domain turns strong authentication into flat network trust. Pin expected peer IDs and write deny-by-default policy.
- Treating the server as ordinary infrastructure: SQLite on a single node, signing keys on disk and many admin IDs make the identity system the weakest link. Apply privileged-access controls to its hosts and APIs. See the Machine Identity, PKI and Certificate Lifecycle Guide.
- Delegated Identity API without guard rails: the SPIRE agent documentation warns that an authorised delegate can obtain identities for any workload the agent serves, and that recycled PIDs must be handled. Limit delegates to trusted node components.
Framework mapping
| Framework | Reference | How SPIFFE and SPIRE contribute |
|---|---|---|
| NIST CSF 2.0 | PR.AA-01, PR.AA-03, PR.AA-05 | Managed service identities and credentials, authenticated services, least-privilege policy keyed on SPIFFE IDs |
| NIST SP 800-53 Rev. 5 | IA-9, IA-5, SC-8, SC-12, SC-17, AC-6 | Service identification and authentication, automated authenticator management, mTLS transmission protection, key management and PKI certificates, least privilege |
| NIST SP 800-207A | Identity-tier policies | Names SPIFFE as an application identity infrastructure for identity-based segmentation in cloud-native, multi-cloud systems |
| ISO/IEC 27001:2022 Annex A | A.5.17, A.8.5, A.8.24 | Authentication information, secure authentication, use of cryptography |
| MITRE ATT&CK v20 | T1528, T1557 | Short-lived, proof-of-possession credentials reduce the value of stolen application tokens; pinned mTLS resists adversary-in-the-middle |
| MITRE D3FEND | D3-CBAN | Certificate-based authentication between workloads |
For how workload identity fits the wider NHI authentication picture, see the NHI Authentication Guide.
Practitioner checklist
- Define one trust domain per environment and document naming and path conventions before the first entry.
- Run the SPIRE server highly available, with an external datastore, KMS or HSM-backed keys and a short list of admin IDs.
- Use platform-backed node attestation with allow lists; disallow
insecure_bootstrapand routine join tokens. - Require at least two selectors per production entry, including service account or UID, and review entries as code.
- Default to X.509-SVIDs with mTLS; restrict JWT-SVIDs to a single audience and minutes of lifetime.
- Pin expected peer SPIFFE IDs and enforce deny-by-default authorisation in the proxy or application.
- Configure federation parameters explicitly per partner and log every bundle fetch.
- Alert on entry changes, admin or downstream grants and attestation failures.
- Retire each static secret once its call path is authenticated by SVID, and track the count.
Standards and references
- SPIFFE: SPIFFE Identity and Verifiable Identity Document
- SPIFFE: The X.509 SPIFFE Verifiable Identity Document
- SPIFFE: The JWT SPIFFE Verifiable Identity Document
- SPIFFE: SPIFFE Federation
- SPIFFE: SPIRE Concepts
- SPIFFE: Configuring SPIRE Server
- SPIFFE: Using Envoy with SPIRE
- NIST: SP 800-207A, A Zero Trust Architecture Model for Access Control in Cloud-Native Applications in Multi-Cloud Environments
Related NHI Mgmt Group resources: Workload Identity Federation Guide · Cloud Workload Identity Guide · Kubernetes NHI Security Guide · Kubernetes Service Account Token Guide · Secrets Management Guide