Tag: SPIFFE

SPIRE Setup Documentation

Overview

This setup deploys SPIRE as follows:

  • SPIRE Server on <SPIRE_SERVER_HOST>
  • SPIRE Agent on <SPIRE_AGENT_HOST>
  • Trust domain: <TRUST_DOMAIN>
  • Server/agent communication port: <SERVER_PORT>/tcp

How it works

SPIRE provides machine and workload identity.

The SPIRE Server on <SPIRE_SERVER_HOST> is the trust authority for the trust domain <TRUST_DOMAIN>.

The SPIRE Agent on <SPIRE_AGENT_HOST> attests to the server using x509pop with an X.509 certificate issued by an enterprise/internal CA.

Applications on <SPIRE_AGENT_HOST> do not talk directly to the SPIRE Server. They talk to the local SPIRE Agent over the local workload API socket <AGENT_SOCKET_PATH>.

The agent returns an X.509-SVID representing the workload identity.

Installation Instructions

  1. Install SPIRE binaries

Run on both hosts:

mkdir -p /opt/spire
cd /tmp
wget https://github.com/spiffe/spire/releases/download/v1.15.2/spire-1.15.2-linux-amd64-musl.tar.gz
tar zxf spire-1.15.2-linux-amd64-musl.tar.gz
cp -r spire-1.15.2/. /opt/spire/

On the SPIRE Server host:

ln -sf /opt/spire/bin/spire-server /usr/bin/spire-server

On the SPIRE Agent host:

ln -sf /opt/spire/bin/spire-agent /usr/bin/spire-agent

  1. Configure SPIRE Server

Create directories:

mkdir -p /opt/spire/conf
mkdir -p /opt/spire/data/server
mkdir -p /opt/spire/conf/x509pop

Place the CA bundle at:

/opt/spire/conf/x509pop/bundle.pem

Contents:

—–BEGIN CERTIFICATE—–
<REDACTED CA CERTIFICATE>
—–END CERTIFICATE—–
—–BEGIN CERTIFICATE—–
<REDACTED CA CERTIFICATE>
—–END CERTIFICATE—–
—–BEGIN CERTIFICATE—–
<REDACTED CA CERTIFICATE>
—–END CERTIFICATE—–

Create /opt/spire/conf/server.conf:

server {
bind_address = “0.0.0.0”
bind_port = “<SERVER_PORT>”
trust_domain = “<TRUST_DOMAIN>”
data_dir = “/opt/spire/data/server”
log_level = “INFO”
}

plugins {
DataStore “sql” {
plugin_data {
database_type = “sqlite3”
connection_string = “/opt/spire/data/server/datastore.sqlite3”
}
}

NodeAttestor “x509pop” {
plugin_data {
ca_bundle_path = “/opt/spire/conf/x509pop/bundle.pem”
}
}

KeyManager “memory” {
plugin_data {}
}
}

health_checks {
listener_enabled = true
bind_address = “127.0.0.1”
bind_port = “<HEALTH_PORT>”
}

Create /etc/systemd/system/spire-server.service:

[Unit]
Description=SPIRE Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/spire/bin/spire-server run -config /opt/spire/conf/server.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Start server:

systemctl daemon-reload
systemctl enable –now spire-server
systemctl status spire-server –no-pager

Validate server:

spire-server healthcheck

  1. Configure SPIRE Agent

Create directories:

mkdir -p /opt/spire/conf
mkdir -p /opt/spire/data/agent
mkdir -p /opt/spire/sockets
mkdir -p /opt/spire/conf/x509pop

Issue a certificate through your enterprise PKI platform. Download as OpenSSL format and split CRT/KEY files. Copy the node certificate to:

/opt/spire/conf/x509pop/agent.crt

Copy the node private key to:

/opt/spire/conf/x509pop/agent.key

The private key must be unencrypted PEM.

Set permissions:

chmod 700 /opt/spire/conf/x509pop
chmod 600 /opt/spire/conf/x509pop/agent.key
chmod 644 /opt/spire/conf/x509pop/agent.crt

Create /opt/spire/conf/agent.conf:

agent {
data_dir = “/opt/spire/data/agent”
log_level = “INFO”
trust_domain = “<TRUST_DOMAIN>”
server_address = “<SPIRE_SERVER_HOST>”
server_port = “<SERVER_PORT>”
socket_path = “<AGENT_SOCKET_PATH>”
insecure_bootstrap = true
}

plugins {
KeyManager “disk” {
plugin_data {
directory = “/opt/spire/data/agent”
}
}

WorkloadAttestor “unix” {
plugin_data {}
}

NodeAttestor “x509pop” {
plugin_data {
private_key_path = “/opt/spire/conf/x509pop/agent.key”
certificate_path = “/opt/spire/conf/x509pop/agent.crt”
}
}
}

Create /etc/systemd/system/spire-agent.service:

[Unit]
Description=SPIRE Agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/spire/bin/spire-agent run -config /opt/spire/conf/agent.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Start agent:

systemctl daemon-reload
systemctl enable –now spire-agent
systemctl status spire-agent –no-pager

  1. Validate x509pop agent attestation

On the SPIRE Server host:

spire-server agent list

Expected result:

  • Agent attestation type is x509pop
  • Can re-attest is true
  • Parent ID format resembles:

spiffe://<TRUST_DOMAIN>/spire/agent/x509pop/<AGENT_HASH>

  1. Create workload registration entry

Use the current x509pop agent SPIFFE ID from spire-server agent list.

On the SPIRE Server host:

spire-server entry create \
-spiffeID spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME> \
-parentID spiffe://<TRUST_DOMAIN>/spire/agent/x509pop/<AGENT_HASH> \
-selector unix:uid:0

This authorizes a root-owned process on the SPIRE Agent host.

  1. Fetch workload identity on the SPIRE Agent host

/opt/spire/bin/spire-agent api fetch x509 -socketPath <AGENT_SOCKET_PATH>

Expected SPIFFE ID:

spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>

  1. Write certs to disk for testing

Create destination directory:

mkdir -p /etc/spire/svid/test
chmod 700 /etc/spire/svid/test

Write files:

/opt/spire/bin/spire-agent api fetch x509 \
-socketPath <AGENT_SOCKET_PATH> \
-write /etc/spire/svid/test

Inspect output:

ls -l /etc/spire/svid/test

openssl x509 -in /etc/spire/svid/test/svid.0.pem -text -noout

  1. Reboot persistence validation

Reboot the SPIRE Agent host.

After reboot, validate:

systemctl status spire-agent –no-pager

ls -l <AGENT_SOCKET_PATH>

/opt/spire/bin/spire-agent api fetch x509 -socketPath <AGENT_SOCKET_PATH>

Expected behavior:

  • spire-agent starts automatically
  • Workload API socket exists
  • X.509-SVID fetch succeeds
  1. Operational notes
  • Current architecture: <SPIRE_SERVER_HOST> = SPIRE Server; <SPIRE_AGENT_HOST> = SPIRE Agent
  • Current trust domain: <TRUST_DOMAIN>
  • Current server/agent path: <SPIRE_AGENT_HOST> to <SPIRE_SERVER_HOST> on TCP <SERVER_PORT>
  • Current Workload API socket: <AGENT_SOCKET_PATH>
  • Current example workload selector: unix:uid:0
  • Current example workload identity: spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>

JWT

Create JWT registration on the SPIRE Server host:

spire-server entry create \
-spiffeID spiffe://<TRUST_DOMAIN>/workload/<JWT_WORKLOAD_NAME> \
-parentID spiffe://<TRUST_DOMAIN>/spire/agent/x509pop/<AGENT_HASH> \
-selector unix:uid:0

Fetch JWT-SVID from the SPIRE Agent host:

/opt/spire/bin/spire-agent api fetch jwt \
-socketPath <AGENT_SOCKET_PATH> \
-audience <JWT_AUDIENCE> \
-spiffeID spiffe://<TRUST_DOMAIN>/workload/<JWT_WORKLOAD_NAME>

Example output:

token(spiffe://<TRUST_DOMAIN>/workload/<JWT_WORKLOAD_NAME>):
<REDACTED JWT-SVID>

bundle(spiffe://<TRUST_DOMAIN>):
<REDACTED JWKS BUNDLE>

SPIRE OIDC Discovery Provider

On the SPIRE Server host:

mkdir -p /opt/spire-extras
cd /tmp
wget https://github.com/spiffe/spire/releases/download/v1.15.2/spire-extras-1.15.2-linux-amd64-musl.tar.gz
tar zxf spire-extras-1.15.2-linux-amd64-musl.tar.gz
cp -r spire-extras-1.15.2/ /opt/spire-extras/

ln -sf /opt/spire-extras/bin/oidc-discovery-provider /usr/bin/oidc-discovery-provider
mkdir -p /opt/spire-extras/conf/oidc-discovery-provider

Create certificate and key files:

/opt/spire-extras/conf/oidc-discovery-provider/tls.crt
/opt/spire-extras/conf/oidc-discovery-provider/tls.key

Set permissions:

chmod 644 /opt/spire-extras/conf/oidc-discovery-provider/tls.crt
chmod 600 /opt/spire-extras/conf/oidc-discovery-provider/tls.key

Create /opt/spire-extras/conf/oidc-discovery-provider/oidc-discovery-provider.conf:

log_level = “INFO”

domains = [“<OIDC_DISCOVERY_DOMAIN>”]

server_api {
address = “unix://<SPIRE_SERVER_API_SOCKET>”
}

serving_cert_file {
cert_file_path = “/opt/spire-extras/conf/oidc-discovery-provider/tls.crt”
key_file_path = “/opt/spire-extras/conf/oidc-discovery-provider/tls.key”
}

Create /etc/systemd/system/spire-oidc-discovery-provider.service:

[Unit]
Description=SPIRE OIDC Discovery Provider
After=network-online.target spire-server.service
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/spire-extras/bin/oidc-discovery-provider -config /opt/spire-extras/conf/oidc-discovery-provider/oidc-discovery-provider.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Ping Integration

PingFederate Integration Note for SPIRE JWT Validation

Purpose

Configure PingFederate to trust and validate JWTs issued from the SPIRE environment.

SPIRE issuer details

Use these values:

  • Issuer: https://<OIDC_DISCOVERY_DOMAIN>
  • OIDC discovery URL: https://<OIDC_DISCOVERY_DOMAIN>/.well-known/openid-configuration
  • JWKS URL: https://<OIDC_DISCOVERY_DOMAIN>/keys

Trust model

PingFederate should validate JWT signatures using the JWKS published by the SPIRE OIDC Discovery Provider.

Ping does not need to call the SPIRE server directly for every token validation. It should use the discovery/JWKS metadata from the OIDC Discovery Provider.

Expected JWT characteristics

Issuer

Ping should require:

iss = https://<OIDC_DISCOVERY_DOMAIN>

Audience

Recommended audience value:

<PING_AUDIENCE>

Clients requesting JWT-SVIDs from SPIRE should request them with this audience.

Subject

The workload identity will be in:

sub

Example:

spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>

This is the primary identity claim Ping should use to identify the calling workload.

Recommended validation rules in Ping

Validate:

  • JWT signature against SPIRE JWKS
  • iss matches https://<OIDC_DISCOVERY_DOMAIN>
  • aud contains <PING_AUDIENCE>
  • token is within validity window (exp, iat)
  • sub is an allowed SPIFFE ID or matches allowed policy rules

Example workload identity currently in use

Current example SPIFFE ID:

spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>

Client-side JWT retrieval model

A workload on the SPIRE Agent host should obtain its JWT from the local SPIRE agent, not from the SPIRE server directly.

Local agent socket:

<AGENT_SOCKET_PATH>

Example operational flow

  1. Workload on the SPIRE Agent host requests a JWT-SVID from the local SPIRE agent.
  2. JWT-SVID is issued with:
  • issuer = https://<OIDC_DISCOVERY_DOMAIN>
  • audience = <PING_AUDIENCE>
  • subject = workload SPIFFE ID
  1. Workload presents JWT to PingFederate.
  2. PingFederate validates the JWT using SPIRE OIDC discovery/JWKS.
  3. PingFederate maps the SPIFFE workload identity to access policy, token issuance, or downstream application authorization.

Suggested placeholder legend

| Placeholder | Meaning |
|—|—|

| <SPIRE_SERVER_HOST> | SPIRE server hostname |

| <SPIRE_AGENT_HOST> | SPIRE agent hostname |

| <TRUST_DOMAIN> | SPIRE trust domain |

| <SERVER_PORT> | SPIRE server listener port |

| <HEALTH_PORT> | Health check port |

| <AGENT_SOCKET_PATH> | Local SPIRE Agent workload API socket |

| <AGENT_HASH> | x509pop parent/agent hash |

| <WORKLOAD_NAME> | Example X.509 workload name |

| <JWT_WORKLOAD_NAME> | Example JWT workload name |

| <JWT_AUDIENCE> | JWT audience used by client |

| <PING_AUDIENCE> | Audience PingFederate validates |

| <OIDC_DISCOVERY_DOMAIN> | Public/abstracted OIDC issuer hostname |

| <SPIRE_SERVER_API_SOCKET> | SPIRE server private API socket |

 

Who Is Acting? SPIFFE Identity and Delegated Authority for Agents

An agent can perform an operation under its own authority or perform the same operation on behalf of a person. Those are different security events.

“Agent XYZ modified this resource” identifies a machine principal. “Agent XYZ modified this resource using Lisa’s authority” identifies both an authorization subject and an acting party.

SPIFFE supplies the agent’s verifiable workload identity. OAuth token exchange can connect that identity to delegated user authority while preserving the distinction between whose permissions apply and which software exercised them. These are complementary layers, not capabilities SPIFFE automatically combines.

Start with the agent’s workload identity

For this example, Agent XYZ runs as a workload registered with SPIRE:

spiffe://agents.example.org/agent/xyz

SPIRE attests the execution environment and local workload, matches its evidence against registration policy, and delivers the appropriate SVID through the Workload API. The identity belongs to the attested workload—not to an agent name supplied in a prompt or HTTP header.

That distinction suggests an important design rule: identify the execution boundary you actually control.

If a single orchestrator process runs ten logically different agents with access to the same credentials, labeling them with ten names does not create ten independently attested security principals. Where strong separation matters, give the agents distinct workload boundaries and issuance policies; otherwise, treat their logical names and run IDs as application metadata rather than independent cryptographic identities. This follows from the Workload API’s caller-identification model.

Model one: the agent acts as itself

For autonomous work, the application authenticates Agent XYZ and authorizes the agent principal directly.

There are two common integration patterns:

  • Direct SPIFFE authentication: the API accepts an X.509-SVID over mTLS, or a JWT-SVID intended for that API.
  • Identity-to-token exchange: a token service validates the agent’s SPIFFE credential and issues an application access token representing the agent.

A proposed audit record for this model could look like:

{
     "event": "resource.update",
     "identity_mode": "standalone_agent",
     "effective_subject": "spiffe://agents.example.org/agent/xyz",
     "current_actor": "spiffe://agents.example.org/agent/xyz",
     "authorization_principal": "agent-xyz",
     "resource": "case/4821",
     "outcome": "success",
     "request_id": "req-7ec15",
     "agent_run_id": "run-93a2"
}

 

Here, no user’s authority is involved. The application should evaluate its agent-specific policy, not silently substitute the permissions of the person who originally scheduled the job.

Model two: Lisa’s authority, Agent XYZ’s actions

Now suppose Lisa asks Agent XYZ to update a resource.

The desired application semantics are:

Authorization subject: Lisa
Current actor:         Agent XYZ
Permission source:     Lisa’s rights for this application
Audit attribution:     Lisa, acting through Agent XYZ

 

OAuth 2.0 Token Exchange, defined in RFC 8693, provides a protocol for requesting tokens with delegation semantics. Its act claim distinguishes the acting party from the token’s subject.

“Actor token” and “actor claim” are different things

The actor_token is an input to token exchange: evidence identifying the proposed actor.

The act claim is information in the resulting token: the issuer’s assertion identifying the current actor.

In this design:

  • The subject token represents Lisa.
  • The actor token represents Agent XYZ.
  • The issued access token retains Lisa as sub and identifies Agent XYZ in act.sub.

A JWT-SVID can serve as actor evidence if the token service explicitly supports and trusts that profile. Its subject is the agent’s SPIFFE ID, and its audience should identify the token service accepting it. A JWT-SVID is not, merely because it is a JWT, a user-delegated OAuth access token.

The exchange

The architecture is:

Lisa’s authorized delegation
+
Agent XYZ’s verified SPIFFE identity
|
v
Token service / STS
|
v
Access token: subject=Lisa, actor=XYZ
|
v
Resource application

 

An illustrative token-exchange request contains these form fields; client authentication is handled separately under the token service’s configured policy:

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=<Lisa's eligible subject token>
subject_token_type=urn:ietf:params:oauth:token-type:access_token
actor_token=<Agent XYZ's JWT-SVID>
actor_token_type=urn:ietf:params:oauth:token-type:jwt
requested_token_type=urn:ietf:params:oauth:token-type:access_token
audience=case-management-api
scope=cases.read cases.update

 

The request asks for delegation; it does not grant it. RFC 8693 also defines may_act, which can express eligibility to become an actor, but the issuer must still apply its delegation policy.

For this implementation, require the STS to check:

  • Lisa’s token is valid and eligible for this exchange.
  • The agent credential is valid for the STS and maps to an approved actor.
  • Agent XYZ may act for Lisa in this application.
  • The requested audience and permissions are permitted.
  • The resulting actor identity comes from verified evidence, never a caller-supplied display name.

These are proposed deployment requirements, not automatic behavior implied by accepting the exchange grant. They implement the issuer-side policy boundary described by RFC 8693.

The resulting access token

An illustrative decoded payload—not a complete access-token profile—is:

{
     "iss": "https://identity.example.org",
     "sub": "user:lisa",
     "aud": "case-management-api",
     "client_id": "agent-xyz-client",
     "scope": "cases.read cases.update",
     "iat": 1793325600,
     "exp": 1793325900,
     "jti": "delegated-token-4b781",
     "act": {
          "sub": "spiffe://agents.example.org/agent/xyz"
     }
}

 

The outer subject remains Lisa. The actor identifies Agent XYZ. client_id identifies the OAuth client and should not be assumed interchangeable with the actor identity.

Can it have all of Lisa’s rights?

Yes—as an explicit application policy, not as an inherent property of act.

For the requested model, the application can evaluate its normal user authorization rules against Lisa while retaining Agent XYZ for attribution. The token must still permit the relevant resource and operation; adding an actor claim does not manufacture permissions or bypass audience restrictions.

A useful implementation policy is:

Effective permissions =
Lisa’s current application permissions
∩ token-authorized operations
∩ delegation policy

 

To preserve all of Lisa’s application rights, the token and delegation policy must allow that set. For narrower automation, restrict them to the task. OAuth security guidance favors audience-restricted, least-privilege tokens rather than broadly reusable authority.

Full-rights delegation has a deliberate consequence: compromising the authorized agent can expose the same application operations Lisa could perform. Actor attribution improves investigation; it is not a privilege boundary.

Programming applications to retain the actor

Issuing the claim is only half the implementation. The application must preserve it through authentication, authorization, execution, and audit.

The following is a recommended application architecture.

  1. Build a verified request identity

At the authentication boundary, validate the access token using the configured issuer, signature policy, intended audience, and temporal checks. Where the API adopts RFC 9068, apply that profile’s validation requirements rather than merely decoding a JWT.

Construct an immutable, request-scoped identity object with fields such as:

effective_subject
current_actor
prior_actors
oauth_client_id
token_issuer
token_id
authorized_scopes
authenticated_transport_peer
delegation_id

 

Recommended rules:

  • Extract the actor only from validated token claims or trusted introspection results.
  • Keep issuer-qualified user identity; a bare sub is not necessarily globally unique.
  • Preserve the SPIFFE trust domain in actor identifiers.
  • Reject malformed actor structures.
  • Do not infer “human request” merely because act is absent.
  • On delegated-only endpoints, require the expected actor information.

These are application controls designed to make the RFC’s subject/actor distinction operational.

  1. Separate permission evaluation from attribution

Business logic should receive both identities, even when authorization intentionally uses only Lisa’s permissions.

For example:

Permission lookup:
subject = user:lisa

Activity attribution:
subject = user:lisa
actor   = spiffe://agents.example.org/agent/xyz

 

Avoid collapsing this into one username field. In the proposed implementation, authorization receives the verified subject and relevant token constraints; audit receives the complete verified identity context.

This separation preserves the purpose of the act claim: distinguishing delegated execution from acting directly as the subject.

  1. Emit structured business events

A proposed event schema:

{
     "event": "case.update",
     "identity_mode": "user_delegation",
     "effective_subject": {
          "issuer": "https://identity.example.org",
          "id": "user:lisa"
     },
     "current_actor": {
          "id": "spiffe://agents.example.org/agent/xyz"
     },
     "oauth_client_id": "agent-xyz-client",
     "token_id": "delegated-token-4b781",
     "delegation_id": "delegation-81a6",
     "request_id": "req-a740",
     "agent_run_id": "run-93a2",
     "action": "case.update",
     "resource": "case/4821",
     "authorization": {
          "decision": "allow",
          "permission_source": "subject",
          "policy_version": "case-policy-17"
     },
     "outcome": "success"
}

 

The field names are a suggested schema, not SPIFFE or OAuth standards.

For this schema, I would require:

  • Both successful operations and denied attempts carry the identity context.
  • The event records the affected business object and committed outcome.
  • Tokens and private keys are excluded from logs.
  • Agent-supplied run IDs are marked as correlation metadata, not authentication evidence.
  • Database audit columns retain both modified_by_subject and modified_by_actor.

A human-readable rendering can then say:

Lisa updated case 4821 through Agent XYZ.

 

The structured event retains the identifiers needed for investigation.

  1. Carry the identity into background execution

Do not let request identity disappear when work moves to a queue or worker.

In the proposed architecture, enqueue a protected job context referencing the verified subject, actor, delegation, and original request. At execution time, obtain or validate authority appropriate to the worker and operation; do not treat an arbitrary queue field such as “user”: “lisa” as permission to act.

Also avoid retaining Lisa’s identity in a mutable global variable: concurrent jobs must not share authorization or audit context.

  1. Preserve delegation across service boundaries

For a chain such as:

Lisa → Agent XYZ → Tool service → Resource API

 

RFC 8693 permits nested actor claims. The outermost actor is the current actor; nested actors represent earlier actors. Previous actors are informational and must not become an alternative source of permissions.

An illustrative claim at the resource API might be:

{
     "sub": "user:lisa",
     "act": {
          "sub": "spiffe://services.example.org/tool/case-editor",
          "act": {
               "sub": "spiffe://agents.example.org/agent/xyz"
          }
     }
}

 

For this design, log the current actor and retained history, and correlate each hop. Never authorize by taking the union of every actor’s permissions.

Attribution is not proof that the actor presented the token

A signed act claim says the issuer associated Agent XYZ with the delegated token. A bearer token can nevertheless be presented by whoever possesses it. Actor attribution alone does not prevent replay.

For stronger attribution, use sender-constrained access tokens where supported. RFC 8705 binds a token to an mTLS client certificate through cnf.x5t#S256; the resource server verifies that the presenting certificate matches the binding.

With rotating SPIFFE certificates, that binding is to the specific certificate, not simply its SPIFFE ID. The implementation must coordinate access-token renewal and certificate rotation. TLS termination must also preserve a trustworthy validation boundary; an untrusted forwarded certificate header is not equivalent to an authenticated peer. These follow from RFC 8705’s certificate-binding mechanism.

The takeaway

For agent identity, keep three questions separate:

  1. Which workload is this? SPIFFE provides the verifiable identity.
  2. Whose authority is being exercised? Delegated tokens preserve the user as subject.
  3. Which software exercised that authority? The actor claim preserves attribution.

Then make the application record all three at the business-event boundary.

The goal is not merely an access token containing Agent XYZ’s name. It is an authorization and audit model that can reliably distinguish Lisa acting directly, Agent XYZ acting independently, and Agent XYZ exercising Lisa’s delegated authority.

SPIFFE and SPIRE: Giving Software an Identity It Can Prove

A service connecting to another service needs to answer a deceptively difficult question: what is the caller, and why should I believe it?

SPIFFE and SPIRE address that question with cryptographically verifiable workload identities, issued at runtime rather than provisioned as long-lived application secrets. SPIFFE defines the identity standards and interfaces; SPIRE implements the attestation, issuance, and rotation machinery. Together, they provide an identity layer that can operate across heterogeneous infrastructure.

Why this exists: the bootstrap problem

Consider an application retrieving a database password from a secrets manager. Moving the password out of a configuration file is useful, but the application still needs to authenticate to the secrets manager. Where does that initial credential come from?

This is the secret-zero problem: protecting a credential often requires another credential. SPIRE approaches it through secure introduction—using evidence about the workload and its execution environment to determine which identity it may receive. It is not a secrets manager; its identities can instead authenticate workloads to a secrets manager.

The same identity machinery supports service-to-service authentication. SPIRE delivers short-lived, automatically rotated credentials suitable for mutual TLS, either directly to applications or to proxies acting on their behalf. The objective is not to invent a replacement for TLS, but to automate the trustworthy introduction and credential lifecycle that TLS requires.

SPIFFE is the contract; SPIRE is the machinery

SPIFFE, the Secure Production Identity Framework for Everyone, defines standards for workload identifiers, verifiable identity documents, trust material, and APIs through which workloads obtain and use their identities. SPIRE, the SPIFFE Runtime Environment, is a reference implementation that adds node and workload attestation, identity registration, signing, and credential renewal. Other implementations can participate in the SPIFFE ecosystem; SPIFFE and SPIRE are not interchangeable names.

The central concepts are straightforward, but their security implications matter.

SPIFFE ID: the name, not the credential

A SPIFFE ID is a URI:

spiffe://prod.example.org/payments/settlement-api

Here, prod.example.org is the trust domain, and /payments/settlement-api identifies the workload within it. The identifier is not secret and does not prove identity by itself. Proof comes from a verifiable document carrying that identifier.

A trust domain establishes an identity authority boundary. Its name resembles a DNS name but need not resolve through DNS. An organization might use separate trust domains for independently administered environments rather than treating every cluster as part of one authority.

SVID: the verifiable credential

A SPIFFE Verifiable Identity Document, or SVID, carries the SPIFFE ID in a cryptographically verifiable form. Two foundational formats are X.509-SVIDs and JWT-SVIDs.

An X.509-SVID is a constrained X.509 certificate. Its SPIFFE ID appears in the Subject Alternative Name extension as a URI SAN; the specification requires exactly one URI SAN. In an mTLS deployment, the certificate identifies the workload, while possession of the corresponding private key supplies the cryptographic proof during authentication.

A JWT-SVID supports token-based authentication, particularly across application-layer boundaries. Unlike certificate-based TLS authentication, a JWT-SVID is a bearer credential: a recipient can potentially replay it to another service accepting its audience. Audience restrictions limit that exposure but do not turn it into proof-of-possession authentication.

Trust bundle: the verification material

A workload also needs the public trust material required to verify its peers. SPIFFE distributes this through trust bundles, containing the relevant certificates or public keys for an issuing trust domain. A bundle is verification material—not a collection of workload private keys.

Workload API: the local delivery interface

The SPIFFE Workload API is a protobuf-defined gRPC interface through which workloads retrieve credentials and trust material. Crucially, the workload does not need to present a pre-provisioned application secret to use it. The implementation must identify the caller; SPIRE does so using local workload attestation, including inspection of operating-system metadata.

That distinction is fundamental: reaching the API is not equivalent to being entitled to an arbitrary identity.

How SPIRE turns evidence into identity

A conventional SPIRE deployment has two principal components:

  • SPIRE Server: maintains identity registration policy and signs SVIDs.
  • SPIRE Agent: runs on workload nodes, attests local callers, and exposes the Workload API.

The server is the identity authority; the agent is a trusted intermediary between that authority and local workloads. SPIRE can operate its own certificate authority or integrate with an upstream authority.

The resulting flow separates two different questions.

  1. Node attestation: may this agent participate?

First, the agent proves its node identity to the server using a configured node-attestation mechanism. The server must establish trust in the agent before relying on its statements about local workloads. Node-attestor plugins allow this introduction to use evidence appropriate to the hosting environment.

This does not eliminate bootstrap trust. It relocates it into the attestation mechanism and the infrastructure supplying its evidence.

  1. Registration: which evidence permits which identity?

The server stores registration entries mapping:

  • A SPIFFE ID.
  • One or more selectors describing eligible workloads.
  • A parent ID establishing the relevant identity relationship.

The server distributes applicable entries to agents, which cache and update them. During workload attestation, an agent compares discovered selectors against those entries to determine which identities the caller may receive.

An illustrative policy might express:

Identity:

spiffe://prod.example.org/payments/settlement-api

Required evidence:

Kubernetes namespace = payments

Kubernetes service account = settlement-api

Placement:

Eligible attested nodes

This is an issuance policy, not an assertion the application makes about itself. Available selectors depend on the configured workload-attestor plugins, and multiple attestation strategies can contribute evidence for one workload.

  1. Workload attestation: what is this local caller?

When a workload contacts the agent, the agent establishes facts about the calling process and its environment. It matches that evidence to registration policy and returns the identities the workload is entitled to use—not merely the identity it requests.

The architectural sequence is:

Node evidence → trusted agent

Local caller evidence → matching registration policy

Matching policy → workload SVID and trust material

The agent obtains and caches SVIDs from the server, exposing the appropriate material locally to eligible workloads. Credential issuance therefore does not require every application to implement its own enrollment protocol against a central CA.

  1. Authentication: prove identity to a peer

For mTLS, workloads use their X.509-SVIDs and private keys to authenticate a connection and use trust bundles to validate the peer. Applications can integrate directly, or Envoy can obtain credentials through SPIRE’s Secret Discovery Service integration. SPIRE streams rotated material to Envoy, which applies it to new connections.

But a trusted certificate is only the beginning of an access decision.

A settlement API may accept credentials issued by the production trust domain while permitting requests only from a particular caller identity. Authentication establishes who is calling; authorization decides what that identity may do. SPIRE supplies the former, while application logic or a policy engine such as OPA supplies the latter.

Federation: recognize another authority without merging it

SPIFFE federation allows workloads to validate identities issued in another trust domain by securely obtaining that domain’s trust bundle. Each domain retains its identity authority; federation does not require sharing private signing keys or collapsing the domains into one namespace.

A useful security interpretation is:

Federation makes another domain’s identities verifiable. Authorization determines which of those identities are acceptable.

Consequently, bundle authenticity and the binding between a trust domain and its bundle are security-critical. Trusting the wrong bundle means trusting the wrong issuer.

What security teams should scrutinize

The following are architectural implications of the attestation and credential model—not properties that deploying SPIRE automatically guarantees:

  • Selector governance is credential-issuance governance. If an attacker can arrange for a workload to satisfy privileged registration selectors, the identity system may issue that privileged identity correctly according to its policy. Review who controls the underlying attributes, not just the selector syntax.
  • Attestation does not guarantee integrity. Matching execution-environment evidence establishes eligibility under policy; it does not demonstrate that the application is uncompromised.
  • Workload isolation remains essential. SPIFFE explicitly assumes sufficient isolation to prevent one workload from stealing another’s issued credentials. The standard does not supply that isolation.
  • Rotation is not containment. Short-lived credentials reduce the useful lifetime of stolen material, but an attacker retaining control of an eligible workload can potentially obtain replacements. Credential lifecycle controls must accompany workload remediation.
  • The identity plane is privileged infrastructure. Servers sign identities, agents determine local eligibility, and bundles establish verification trust. Their protection belongs in the authentication threat model.

The takeaway

SPIFFE standardizes how software names and proves its identity. SPIRE operationalizes that model by connecting infrastructure evidence to issuance policy and continuously delivering usable credentials. Its value is not simply “automatic certificates”; it is a consistent, attestable identity foundation for service authentication and secure introduction.