Skip to content

Workload Identity with SPIFFE/SPIRE ​

Last Updated: September 13, 2026 Validation baseline: SPIRE 1.15.3, hardened chart 0.30.2 / CRD chart 0.6.1, Controller Manager 0.7.0, chart CSI image 0.2.7 (also checked against current CSI 0.2.13 documentation), go-spiffe 2.8.1. Inspect chart-rendered image versions separately.

SPIFFE defines workload identity, credential, delivery, and trust formats; SPIRE implements them. Issuing an identity does not automatically encrypt traffic or authorize service access. This guide validates local configuration, schemas, libraries, charts, and diagrams. No live cluster, AWS CA, SPIRE attestation, or mesh installation was performed.

Overview ​

SPIFFE and SPIRE are CNCF Graduated projects. Their CNCF project pages record August 23 and August 22, 2022 respectively. Project maturity is separate from validation of an individual deployment.

Stable identities are useful across changing IPs/Pods, but applications still need a Workload API client, SDK, proxy, or explicit file adapter. “Zero application changes for every workload” is not a universal guarantee. This chapter focuses on SPIRE X.509/JWT paths; the separately published Incubating WIT-SVID specification is not assumed to be supported in every deployment.

Core Concepts ​

SPIFFE ID ​

text
spiffe://example.org/ns/payments/sa/payment-processor

An ID contains a scheme, trust domain, and optional path. Query, fragment, port, dot-segment, and percent-encoded path components are disallowed. DNS-like stable trust-domain names are useful but need not be DNS-resolvable. IPv4-shaped or numeric names are not categorically invalid; distinguish syntax from naming guidance.

SVIDs and Validation ​

ConcernX.509-SVIDJWT-SVID
IdentitySPIFFE URI SAN in the leafsub
ValidationChain, lifetime, SVID rules, trust domainSignature, subject, audience, expiry
UseTLS client/server authenticationAPIs accepting bearer tokens
KeyWorkload/agent private-key pathIssuer retains signing private key
LifetimePolicy and actual issuancePolicy and actual token exp

CN is not the SPIFFE identity. Local go-spiffe tests rejected CN-only, multiple SPIFFE URIs, expiry, and wrong trust domains. Audience validation limits recipients but is not replay detection: the same valid bearer token passed verification again. Apply appropriate TLS/token-use policy and replay defenses where required.

Trust bundles include X.509 authorities, JWT verification keys, and metadata. PEM, SPIFFE bundle JSON, and arbitrary YAML are not interchangeable. Public bundles must not contain workload or CA private keys.

SPIRE Architecture ​

SPIRE Server, Agent, signing keys and registration responsibilities

SPIRE Server, Agent, signing keys and registration responsibilitiesOpen full screen ↗

The server manages agent attestation, registration, and X.509/JWT signing. DataStore and KeyManager have different persistence responsibilities. An UpstreamAuthority such as AWS Private CA signs SPIRE intermediate CAs; it does not replace every workload leaf-signing operation.

The agent attests the process calling its API and uses synchronized entries/SVID cache. A valid cache does not require new server issuance on every API request. Consumers must adopt updated credentials through streams, SDKs, or proxies.

Local X.509-SVID cache and conditional renewal path

Local X.509-SVID cache and conditional renewal pathOpen full screen ↗

Installation ​

Download the example directory and work from examples/security/spiffe. Verify hostPath/CSI/kernel/kubelet requirements. The same DaemonSets cannot be assumed to run on hosts such as Fargate where required access is unavailable.

bash
helm repo add spiffe https://spiffe.github.io/helm-charts-hardened
helm repo update spiffe
helm upgrade --install spire-crds spiffe/spire-crds \
  --version 0.6.1 --namespace spire-system --create-namespace
helm upgrade --install spire spiffe/spire \
  --version 0.30.2 --namespace spire-system --values lab-values.yaml

Single-Server Lab ​

yaml
global:
  spire:
    trustDomain: example.org
    clusterName: documentation
    caSubject:
      organization: Documentation Lab
      country: KR
    namespaces:
      server:
        name: spire-system
      system:
        name: spire-system
  installAndUpgradeHooks:
    enabled: false
  deleteHooks:
    enabled: false
spire-server:
  replicaCount: 1
  controllerManager:
    enabled: true
    identities:
      clusterSPIFFEIDs:
        default:
          enabled: false
        oidc-discovery-provider:
          enabled: false
        test-keys:
          enabled: false
  externalControllerManagers:
    enabled: false
  persistence:
    enabled: true
    size: 1Gi
spire-agent:
  workloadAttestors:
    k8s:
      verification:
        type: apiServerCA
    unix:
      enabled: true
spiffe-oidc-discovery-provider:
  enabled: false
spiffe-csi-driver:
  enabled: true

This is a single-server SQLite lab. Broad default, test, and unused OIDC identities are disabled; a separate ClusterSPIFFEID selects workloads. The chart defaults kubelet verification to skip, so apiServerCA is set explicitly. This assumes that the actual kubelet serving certificate validates under that CA; use the appropriate CA/host-certificate method for other PKI rather than disabling verification.

Install/delete hooks are disabled in this profile; perform any required migration/cleanup separately. The render includes a Server StatefulSet with Controller Manager sidecar plus Agent and CSI DaemonSets. Confirm release-specific resource names, labels, and socket paths.

High Availability ​

ha-values.yaml uses three replicas, shared PostgreSQL, an existing password Secret, verify-full TLS with a mounted CA, and anti-affinity matching actual Pod labels. Three independent SQLite replicas are not a shared HA datastore.

Prepare PostgreSQL/DNS, the CA ConfigMap, spire-database Secret key password, StorageClass, and connectivity first. The example supplies the raw password through PGPASSWORD using extraEnv.valueFrom.secretKeyRef. It disables the chart's dataStore.sql.externalSecret interpolation and leaves password empty, so the generated connection string contains no password. SPIRE's PostgreSQL driver reads PGPASSWORD separately: quotes, backslashes, whitespace and dollar signs do not enter the JSON/DSN parser. Do not pre-escape or URI-encode the actual password.

Native SPIRE 1.15.3 configuration and lib/pq 1.12.3 parsing checks covered six synthetic password cases while retaining sslmode=verify-full and the CA path. These checks did not connect to PostgreSQL or test failover. Secret changes delivered as environment variables require restarting the server Pods; coordinate database password rotation with the restart and verify availability. HA also requires key persistence, backups, bundle rollover, and recovery testing.

Node Attestation ​

Separate agent and workload attestation

Separate agent and workload attestationOpen full screen ↗

k8s_psat validates the agent's projected ServiceAccount token through Kubernetes TokenReview, then checks namespace/SA/Pod/node data. This is not the IAM OIDC-provider flow used by IRSA. Match logical cluster names, token audience, SA allowlist, and TokenReview permissions.

Default agent IDs follow spiffe://TRUST_DOMAIN/spire/agent/k8s_psat/CLUSTER/NODE_UID; the current version also offers a Pod UID mode. Discover registered agent/alias IDs instead of inventing parentIDs. token generate, entry create, and bundle set mutate real state.

aws_iid is an alternative using EC2 instance identity. It is not universally stronger than PSAT or restricted to non-EKS environments. Review skip_block_device, local-validation assumptions, allowed accounts, and additional selectors. Do not place static AWS credentials in ConfigMaps.

Workload Attestation ​

The agent uses caller PID/cgroups and kubelet information. Do not default to insecure kubelet port 10255 or skip_kubelet_verification=true. Secure authentication, the correct serving CA, and network access are prerequisites.

Common selectors include k8s:ns, k8s:sa, k8s:pod-label, k8s:pod-uid, and k8s:container-name/image. container-image reflects Kubernetes-reported tags/digests; nginx:* is not a glob selector. A tag string is not supply-chain verification. Use appropriate digest/signature attestation separately where needed.

Principals able to change namespace/Pod labels or create Pods under a ServiceAccount can affect identity eligibility. Control namespace/SA/Pod creation and ownership of identity policy together. Unix UID/GID/path/hash selectors also depend on plugin configuration and the threat model.

Kubernetes Integration ​

CSI Mounts the API Socket ​

The chart’s SPIFFE CSI 0.2.7 and current 0.2.13 implementations mount a directory containing the Workload API Unix socket. It does not automatically create svid.pem, svid.key, or bundle.pem files. File-based applications need a separate adapter plus renewal/reload handling.

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    spiffe-enabled: 'true'
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: payment-processor
  namespace: payments
---
apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterSPIFFEID
metadata:
  name: payments-workload
spec:
  spiffeIDTemplate: spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{
    .PodSpec.ServiceAccountName }}
  namespaceSelector:
    matchLabels:
      spiffe-enabled: 'true'
  podSelector:
    matchLabels:
      spiffe-managed: 'true'
  workloadSelectorTemplates:
  - k8s:ns:{{ .PodMeta.Namespace }}
  - k8s:sa:{{ .PodSpec.ServiceAccountName }}
  - k8s:container-name:app
  ttl: 1h
  jwtTtl: 5m
---
apiVersion: v1
kind: Pod
metadata:
  name: payment-processor
  namespace: payments
  labels:
    spiffe-managed: 'true'
spec:
  serviceAccountName: payment-processor
  containers:
  - name: app
    image: registry.example.com/team/payment-app:REPLACE_WITH_APPROVED_VERSION
    env:
    - name: SPIFFE_ENDPOINT_SOCKET
      value: unix:///spiffe-workload-api/spire-agent.sock
    volumeMounts:
    - name: spiffe-workload-api
      mountPath: /spiffe-workload-api
      readOnly: true
  volumes:
  - name: spiffe-workload-api
    csi:
      driver: csi.spiffe.io
      readOnly: true

Replace the application image with an actual Workload API consumer. Distinguish chart identity jwtTTL from CRD jwtTtl. The explicit selector targets the app container; a separate Envoy container needs matching proxy registration policy.

Envoy SDS ​

The complete bootstrap example includes HTTP filters, the SDS cluster, require_client_certificate:true, and an exact allowed peer-URI matcher. The Envoy process must be attestable and both named server/client identities must be registered.

SDS shares the public agent socket with the Workload API. Certificate resource names use a workload SPIFFE ID or default; validation contexts use a trust-domain ID or ROOTCA/ALL. Check SPIFFE certificate-validator support, especially for ALL. Protocol schemas and URI-matcher implementation were verified; no real Envoy/SDS/mTLS handshake was run.

Service Mesh Integration ​

Istio ​

Do not point Istio's CA address at SPIRE Server port 8081 or use invented ENABLE_SPIFFE_IDENTITY/PILOT_ENABLE_SPIRE_INTEGRATION variables. The current official integration configures CSI socket mounts, SPIRE registration, and sidecar/gateway templates.

With native sidecars, istio-proxy is an initContainer and must be patched there. Explicitly disabled native-sidecar mode uses containers instead. Validate installed versions, templates, sockets, and readiness; do not replace a complete injector ConfigMap with a partial snippet.

Cilium ​

Cilium 1.20.1 mutual authentication is beta and out-of-band from ordinary connections. Traffic encryption requires separate WireGuard/IPsec configuration. An arbitrary label containing a SPIFFE ID is not an authenticated identity policy.

The official setup uses authentication.mutual.spire.enabled and, for its bundled installation, authentication.mutual.spire.install.enabled. Keep bundled and external SPIRE configurations distinct and consult the pinned installation source. Endpoint selectors/authentication modes, identity issuance, and encryption have separate responsibilities.

Linkerd ​

Do not pass SPIRE bundle JSON where Linkerd expects PEM roots or copy SPIRE CA private keys as issuer keys. Linkerd needs an appropriate issuer certificate/key and trusted roots, with renewal and root rollover. See the reviewed cert-manager/Linkerd path. Sharing root trust alone does not integrate the SPIFFE Workload API or SDS.

Federation ​

Federation with explicit bundle trust and workload authorization

Federation with explicit bundle trust and workload authorizationOpen full screen ↗

Configure each trust direction explicitly; federation does not automatically establish mutual trust or authorization. Operate bundle endpoint connectivity, TLS verification, refresh failures, expiry, and rollover.

yaml
apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterFederatedTrustDomain
metadata:
  name: partner-domain
spec:
  trustDomain: partner.example.org
  bundleEndpointURL: https://bundle.partner.example.org
  bundleEndpointProfile:
    type: https_web

This https_web example assumes a real endpoint with a valid Web PKI certificate. https_spiffe additionally needs endpointSPIFFEID and an initial trusted bundle acquired through an authenticated bootstrap path; setting a URL alone is insufficient. Use bare trust-domain names such as partner.example.org in applicable workload federatesWith lists. Fetching a trust bundle and authorizing a peer workload are distinct.

EKS Integration ​

IRSA/Pod Identity provide AWS API credential paths; SPIFFE/SPIRE provide workload identity paths. Neither replaces the other, and not every environment needs both. IRSA supports cross-account designs and refreshes credentials through compatible SDK/projected-token behavior; Pod restart is not inherently required. Do not assume a fixed twelve-hour lifetime.

Configure IRSA on the ServiceAccount and validate aud/sub trust and AWS permissions. A Pod annotation or AWS_ROLE_ARN environment variable alone is insufficient. For workload mTLS, applications/proxies must consume SVIDs and authorize peer identities separately.

AWS Private CA ​

Use the pinned plugin fields inside a complete server plugins section. This is a plugin fragment, not an independently runnable server configuration.

hcl
# Merge this plugin into an otherwise complete server configuration.
UpstreamAuthority "aws_pca" {
  plugin_data {
    region = "ap-northeast-2"
    certificate_authority_arn = "arn:aws:acm-pca:ap-northeast-2:111122223333:certificate-authority/REPLACE_CA_ID"
    ca_signing_template_arn = "arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen0/V1"
  }
}

SPIRE owns the intermediate CA and signs leaves. Scope DescribeCertificateAuthority/IssueCertificate/GetCertificate to the intended CA ARN using the policy example. Select signing algorithms/templates for the real CA. supplemental_bundle_path contains additional PEM authorities, not a backup region. Distinguish aws_kms KeyManager from UpstreamAuthority plugins.

Best Practices and Troubleshooting ​

Tune lifetimes against renewal failures, clock skew, issuance load, and offline periods. Short TTLs do not solve every revocation or JWT replay concern. bundle set changes trusted bundles; it does not rotate a CA private key.

Network policy must account for DNS, Kubernetes TokenReview/API, datastore, upstream CA/KMS, federation, and telemetry as well as Server↔Agent traffic. A Pod selector in one namespace does not select agents in another. Validate real connectivity before claiming the policy permits all required flows.

Running api fetch inside the agent Pod attests that calling process, not the application's context. Diagnose from the intended workload context under an approved procedure. Limit sensitive selectors, tokens, and keys in logs.

Summary and References ​

Local validation covered server/agent configuration, nine ID cases, five X.509 cases, six JWT cases, lab/HA Helm, CRD/Pod schemas, Envoy protobuf schemas, and twenty-four browser cases for eight diagrams. No real attestation, cluster installation, DB connection, AWS issuance, federation exchange, or mTLS traffic was executed.