SPIFFE/SPIRE Deployment Guide
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