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.conf2. 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.conf4. 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 Type | Example | Description |
|---|---|---|
unix:uid | unix:uid:1000 | Process UID |
unix:gid | unix:gid:100 | Process GID |
unix:user | unix:user:agent-harbor | Username |
unix:group | unix:group:wheel | Group name |
unix:path | unix:path:/usr/bin/ah | Executable path |
docker:label | docker:label:app:myapp | Docker label |
k8s:pod-label | k8s:pod-label:app:myapp | Kubernetes label |
Certificate Rotation
SPIFFE SVIDs are automatically rotated:
- SPIRE agent requests new SVIDs before expiry
- Agent Harbor watches the SVID stream
- New connections use fresh certificates
- 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 serverSolutions:
- 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 issuedSolutions:
- 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/serveSolutions:
- Verify access point’s registration entry
- Check
--expected-server-idflag matches
Next Steps
- Files Provider Guide - Manual PKI alternative
- Vault Integration Guide - Enterprise PKI
- Troubleshooting Guide - Common issues