Skip to main content

Integrate SPIRE

This guide covers deploying and configuring SPIRE (SPIFFE Runtime Environment) with AuthSec for automatic workload identity management and certificate rotation.


Overview

SPIRE is the production-grade reference implementation of the SPIFFE (Secure Production Identity Framework For Everyone) standard. It provides:

  • Automatic identity issuance — Workloads receive cryptographic identities upon startup without manual provisioning
  • Zero-trust attestation — Workload authenticity is verified against platform-level attributes before credentials are issued
  • Continuous credential rotation — X.509 certificates and JWT-SVIDs are renewed automatically before expiration
  • Platform-agnostic deployment — Operates across Kubernetes, virtual machines, bare-metal servers, and multi-cloud environments

Architecture

┌─────────────────┐
│ SPIRE Server │ ← Central identity authority
└────────┬────────┘

│ Issues SVIDs (certificates)

┌────┴────┐
│ │
┌───▼───┐ ┌──▼────┐
│ Agent │ │ Agent │ ← Runs on each node
└───┬───┘ └──┬────┘
│ │
│ │ Distributes identities
│ │
┌───▼───┐ ┌──▼────┐
│Worker │ │Worker │ ← Your workloads
│load 1 │ │load 2 │
└───────┘ └───────┘
ComponentRole
SPIRE ServerCentral authority responsible for validating agent attestation and issuing SVIDs
SPIRE AgentNode-level process that attests workloads and delivers credentials via the Workload API
WorkloadsServices and autonomous agents that receive and use SPIFFE identities for authenticated communication

Prerequisites

  • Kubernetes cluster (v1.19+) or VM-based infrastructure
  • kubectl access with cluster-admin permissions
  • An active AuthSec workspace with admin access

Installation

Deploy the SPIRE Server

Create the SPIRE namespace and deploy the server:

kubectl create namespace spire
kubectl apply -f https://raw.githubusercontent.com/spiffe/spire/main/support/k8s/server/spire-server.yaml

Verify the server pod is running:

kubectl get pods -n spire

Expected output:

NAME                            READY   STATUS    RESTARTS   AGE
spire-server-0 1/1 Running 0 1m

Deploy the SPIRE Agent

Deploy the agent as a DaemonSet so that every node in the cluster runs an agent instance:

kubectl apply -f https://raw.githubusercontent.com/spiffe/spire/main/support/k8s/agent/spire-agent.yaml

Verify agent deployment:

kubectl get daemonset spire-agent -n spire

Expected output:

NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE
spire-agent 3 3 3 3 3

Option 2: Docker Compose

For development and testing environments, SPIRE can be deployed via Docker Compose:

# docker-compose.yml
version: "3.8"

services:
spire-server:
image: ghcr.io/spiffe/spire-server:latest
hostname: spire-server
volumes:
- ./server:/opt/spire/conf/server
- spire-server-data:/opt/spire/data/server
command: ["-config", "/opt/spire/conf/server/server.conf"]
ports:
- "8081:8081"
networks:
- spire

spire-agent:
image: ghcr.io/spiffe/spire-agent:latest
depends_on:
- spire-server
hostname: spire-agent
volumes:
- ./agent:/opt/spire/conf/agent
- /var/run/docker.sock:/var/run/docker.sock
command: ["-config", "/opt/spire/conf/agent/agent.conf"]
networks:
- spire

volumes:
spire-server-data:

networks:
spire:
docker-compose up -d

Configuration

Step 1: Define the Trust Domain

The trust domain establishes the root namespace for all SPIFFE identities in your deployment. All workload identities are scoped under this domain:

spiffe://authsec.example.com/production/api-gateway
spiffe://authsec.example.com/production/ml-agent

Configure the trust domain in the SPIRE server configuration file:

# server.conf
server {
bind_address = "0.0.0.0"
bind_port = "8081"
trust_domain = "authsec.example.com"
data_dir = "/opt/spire/data/server"
}

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

KeyManager "disk" {
plugin_data {
keys_path = "/opt/spire/data/server/keys.json"
}
}

NodeAttestor "k8s_psat" {
plugin_data {
clusters = {
"production" = {
service_account_allow_list = ["spire:spire-agent"]
}
}
}
}
}

Step 2: Register Workloads

Each workload that requires a SPIFFE identity must be registered with the SPIRE server. The registration entry defines the SPIFFE ID to assign and the selectors used to identify the workload at runtime.

kubectl exec -n spire spire-server-0 -- \
/opt/spire/bin/spire-server entry create \
-spiffeID spiffe://authsec.example.com/production/ml-agent \
-parentID spiffe://authsec.example.com/spire/agent/k8s_psat/production/default \
-selector k8s:ns:default \
-selector k8s:sa:ml-agent-service-account \
-selector k8s:pod-label:app:ml-agent
ParameterPurpose
-spiffeIDThe SPIFFE identity to assign to this workload
-parentIDThe identity of the SPIRE agent that will attest this workload
-selectorOne or more criteria used to match the workload at runtime (Kubernetes namespace, service account, pod labels)

Step 3: Connect to AuthSec

To enable AuthSec to verify SPIRE-issued JWT-SVIDs, configure OIDC discovery on the SPIRE server:

oidc_discovery {
domain = "auth.authsec.ai"
}

This exposes a JWKS endpoint that AuthSec uses to validate JWT signatures from SPIRE-issued tokens.


Using SPIRE in Your Application

Fetch an SVID

Workloads obtain their SPIFFE identity by connecting to the local SPIRE agent through the Workload API.

Python:

from spiffe import WorkloadApiClient

client = WorkloadApiClient()
svid = client.fetch_x509_svid()

print(f"SPIFFE ID: {svid.spiffe_id}")
print(f"Certificate: {svid.cert}")
print(f"Private Key: {svid.private_key}")

Go:

package main

import (
"context"
"fmt"
"github.com/spiffe/go-spiffe/v2/workloadapi"
)

func main() {
ctx := context.Background()

source, err := workloadapi.NewX509Source(ctx)
if err != nil {
panic(err)
}
defer source.Close()

svid, err := source.GetX509SVID()
if err != nil {
panic(err)
}

fmt.Printf("SPIFFE ID: %s\n", svid.ID)
}

Establish mTLS Communication

Use SPIRE-issued certificates to create mutually authenticated connections between services.

Go Server:

package main

import (
"context"
"log"
"net/http"

"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
"github.com/spiffe/go-spiffe/v2/workloadapi"
)

func main() {
ctx := context.Background()

source, err := workloadapi.NewX509Source(ctx)
if err != nil {
log.Fatal(err)
}
defer source.Close()

tlsConfig := tlsconfig.MTLSServerConfig(source, source, tlsconfig.AuthorizeAny())

server := &http.Server{
Addr: ":8443",
TLSConfig: tlsConfig,
}

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Authenticated via mTLS"))
})

log.Println("Listening on :8443")
log.Fatal(server.ListenAndServeTLS("", ""))
}

Go Client:

package main

import (
"context"
"fmt"
"io"
"net/http"

"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
"github.com/spiffe/go-spiffe/v2/workloadapi"
)

func main() {
ctx := context.Background()

source, err := workloadapi.NewX509Source(ctx)
if err != nil {
panic(err)
}
defer source.Close()

tlsConfig := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeAny())
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
}

resp, err := client.Get("https://api-service:8443/")
if err != nil {
panic(err)
}
defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}

Verification

Confirm SPIRE Server Status

List all registered workload entries:

kubectl exec -n spire spire-server-0 -- \
/opt/spire/bin/spire-server entry show

Confirm Agent Health

kubectl exec -n spire spire-agent-XXXXX -- \
/opt/spire/bin/spire-agent healthcheck

Test SVID Retrieval

kubectl exec -n spire spire-agent-XXXXX -- \
/opt/spire/bin/spire-agent api fetch x509

A successful response returns the X.509 certificate chain and SPIFFE ID assigned to the agent.


Production Considerations

ConsiderationRecommendation
Key storageUse a Hardware Security Module (HSM) or cloud KMS to protect SPIRE server signing keys
High availabilityDeploy multiple SPIRE server replicas backed by a shared database (PostgreSQL or MySQL)
Certificate monitoringConfigure alerts for SVID rotation failures and certificate expiration events
Network isolationApply Kubernetes network policies to restrict agent-to-server and workload-to-agent communication
Entry auditingPeriodically review registered workload entries to remove stale or unauthorized identities

Troubleshooting

Workload Cannot Fetch SVID

  1. Confirm the SPIRE agent pod is running on the same node as the workload:

    kubectl get pods -n spire -l app=spire-agent
  2. Verify the workload is registered with matching selectors:

    kubectl exec -n spire spire-server-0 -- \
    /opt/spire/bin/spire-server entry show
  3. Check that the workload pod labels and service account match the registered selectors:

    kubectl get pod YOUR_POD -o yaml | grep labels -A 5

Certificate Expiration

SPIRE agents renew SVIDs automatically. If certificates are expiring, inspect the agent logs for rotation errors:

kubectl logs -n spire spire-agent-XXXXX

mTLS Connection Failure

Verify that both the client and server workloads hold valid SVIDs and share the same trust domain:

kubectl exec YOUR_POD -- curl -v https://api-service:8443/

Next Steps