Skip to Content
GuidesWorkflowsEnrollmentSPIFFE/SPIRE Deployment Guide

SPIFFE/SPIRE Deployment Guide

Experimental Feature — Executor enrollment and fleet identity management are under active development. Configuration formats and identity provider interfaces may change between releases.

SPIFFE (Secure Production Identity Framework For Everyone) with SPIRE provides automatic, cryptographically verifiable workload identities. This is the recommended identity provider for production deployments.

Overview

SPIFFE provides:

  • Automatic SVIDs: X.509 certificates issued automatically to workloads
  • Short-lived credentials: Certificates with hour-long TTLs, automatically rotated
  • Workload attestation: Identity based on process attributes, not secrets
  • Zero-trust networking: Cryptographic proof of workload identity

Architecture

┌──────────────────────────────────────────────────────────────┐ │ Trust Domain │ │ (spiffe://example.org) │ │ │ │ ┌─────────────┐ ┌─────────────────────────────────┐ │ │ │SPIRE Server │◄────────│ Registration DB │ │ │ │ (CA + API) │ │ (workload → SPIFFE ID mapping) │ │ │ └──────┬──────┘ └─────────────────────────────────┘ │ │ │ │ │ │ Node Attestation │ │ │ (join token, x509pop) │ │ ▼ │ │ ┌─────────────┐ │ │ │ SPIRE Agent │ ◄── Workload API ──► Access Point │ │ │ (per node) │ (ah agent access-point)│ │ └──────┬──────┘ │ │ │ │ │ │ Workload Attestation │ │ │ (unix:uid, docker:label) │ │ ▼ │ │ ┌─────────────┐ │ │ │ Executor │ ◄── Workload API ──► SVID │ │ │ (ah agent) │ (X.509 certificate) │ │ └─────────────┘ │ └──────────────────────────────────────────────────────────────┘

NixOS Deployment

Agent Harbor provides NixOS modules for SPIRE deployment:

SPIRE Server

# /etc/nixos/configuration.nix { imports = [ /path/to/agent-harbor/nix/nixos-modules ]; services.spire.server = { enable = true; trustDomain = "example.org"; bindAddress = "0.0.0.0"; bindPort = 8081; dataStore = { type = "sqlite3"; connectionString = "/var/lib/spire/server/datastore.sqlite3"; }; nodeAttestors = { joinToken.enable = true; }; # Declarative registration entries registrationEntries = [ { parentId = "spiffe://example.org/spire/agent/join_token/access-point-1"; spiffeId = "spiffe://example.org/ah/serve"; selectors = [ "unix:user:agent-harbor" ]; } { parentId = "spiffe://example.org/spire/agent/join_token/executor-1"; spiffeId = "spiffe://example.org/ah/agent/executor-1"; selectors = [ "unix:user:executor" ]; } ]; }; # Open firewall for SPIRE server networking.firewall.allowedTCPPorts = [ 8081 ]; }

SPIRE Agent

# On executor nodes { services.spire.agent = { enable = true; trustDomain = "example.org"; serverAddress = "spire-server.example.org"; serverPort = 8081; # For production, provide a trust bundle trustBundlePath = /path/to/trust-bundle.pem; # Or for development: # insecureBootstrap = true; joinToken = "your-join-token-here"; # Or use secrets management: # joinTokenFile = config.sops.secrets."spire/join-token".path; workloadAttestors = { unix.enable = true; }; }; }

Manual Deployment

1. Deploy SPIRE Server

# Download SPIRE curl -sL https://github.com/spiffe/spire/releases/download/v1.14.1/spire-1.14.1-linux-amd64-musl.tar.gz | tar xz # Create server configuration cat > /etc/spire/server.conf << EOF server { bind_address = "0.0.0.0" bind_port = "8081" socket_path = "/run/spire/server.sock" trust_domain = "example.org" data_dir = "/var/lib/spire/server" log_level = "INFO" } plugins { DataStore "sql" { plugin_data { database_type = "sqlite3" connection_string = "/var/lib/spire/server/datastore.sqlite3" } } NodeAttestor "join_token" { plugin_data {} } KeyManager "disk" { plugin_data { keys_path = "/var/lib/spire/server/keys.json" } } } EOF # Start SPIRE server spire-server run -config /etc/spire/server.conf

2. Generate Join Token

spire-server token generate \ -socketPath /run/spire/server.sock \ -ttl 3600 # Output: Token: abc123def456...

3. Deploy SPIRE Agent

# Create agent configuration cat > /etc/spire/agent.conf << EOF agent { data_dir = "/var/lib/spire/agent" log_level = "INFO" server_address = "spire-server.example.org" server_port = "8081" socket_path = "/run/spire/agent.sock" trust_domain = "example.org" join_token = "abc123def456..." insecure_bootstrap = true # Remove in production } plugins { NodeAttestor "join_token" { plugin_data {} } KeyManager "disk" { plugin_data { directory = "/var/lib/spire/agent" } } WorkloadAttestor "unix" { plugin_data { discover_workload_path = true } } } EOF # Start SPIRE agent spire-agent run -config /etc/spire/agent.conf

4. Create Registration Entries

# Get agent SPIFFE ID AGENT_ID=$(spire-server agent list -socketPath /run/spire/server.sock | grep -oP 'spiffe://[^\s]+' | head -1) # Register access point workload spire-server entry create \ -socketPath /run/spire/server.sock \ -parentID "$AGENT_ID" \ -spiffeID "spiffe://example.org/ah/serve" \ -selector "unix:user:agent-harbor" # Register executor workload spire-server entry create \ -socketPath /run/spire/server.sock \ -parentID "$AGENT_ID" \ -spiffeID "spiffe://example.org/ah/agent/executor-1" \ -selector "unix:user:executor"

5. Configure Agent Harbor

# Access point ah agent access-point \ --fleet-listen 0.0.0.0:4433 \ --server-identity spiffe \ --spiffe-socket /run/spire/agent.sock \ --executor-san-uri-prefix "spiffe://example.org/ah/agent/" # Executor ah agent enroll \ --remote-server https://access-point.example.org:4433 \ --identity spiffe \ --spiffe-socket /run/spire/agent.sock \ --expected-server-id "spiffe://example.org/ah/serve"

Workload Selectors

SPIRE identifies workloads using selectors. Common selectors:

Selector TypeExampleDescription
unix:uidunix:uid:1000Process UID
unix:gidunix:gid:100Process GID
unix:userunix:user:agent-harborUsername
unix:groupunix:group:wheelGroup name
unix:pathunix:path:/usr/bin/ahExecutable path
docker:labeldocker:label:app:myappDocker label
k8s:pod-labelk8s:pod-label:app:myappKubernetes label

Certificate Rotation

SPIFFE SVIDs are automatically rotated:

  1. SPIRE agent requests new SVIDs before expiry
  2. Agent Harbor watches the SVID stream
  3. New connections use fresh certificates
  4. Existing connections continue until closed

Default TTL is 1 hour; configure on server:

server { default_x509_svid_ttl = "4h" # Longer TTL }

High Availability

For production HA:

Federated SPIRE

Multiple SPIRE servers can federate across regions:

server { federation { bundle_endpoint { address = "0.0.0.0" port = 8443 } } }

PostgreSQL Backend

Use PostgreSQL for multi-server deployments:

services.spire.server = { dataStore = { type = "postgres"; connectionString = "postgres://spire:password@db:5432/spire?sslmode=require"; }; };

Troubleshooting

Agent Can’t Connect to Server

error: failed to connect to spire server

Solutions:

  • Check firewall allows port 8081
  • Verify server address and port
  • Check join token is valid and not expired

No SVID for Workload

error: no identity issued

Solutions:

  • Check registration entry exists: spire-server entry show
  • Verify selectors match the workload
  • Check agent is attested: spire-server agent list

SPIFFE ID Mismatch

error: server SPIFFE ID mismatch: expected spiffe://example.org/ah/serve

Solutions:

  • Verify access point’s registration entry
  • Check --expected-server-id flag matches

Next Steps