Hoe weet Service A dat Service B echt Service B is?
In traditionele netwerken vertrouwden we netwerk locatie. Als traffic van het juiste IP kwam, was het legitiem. Zero trust doodde die aanname. Nu moet elke service zijn identiteit bewijzen, elke keer, ongeacht netwerkpositie.
SPIFFE (Secure Production Identity Framework for Everyone) is een standaard voor service identity. SPIRE is de productie-klare implementatie. Samen geven ze elke workload een cryptografische identiteit — automatisch, zonder statische secrets.
Het Probleem: Service Identity is Moeilijk
Traditionele benaderingen voor service authenticatie:
Shared secrets
# Elke pod kent hetzelfde wachtwoord
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: shared-secret
key: api-key
Problemen:
- Eén gecompromitteerde service legt alle services bloot
- Rotatie vereist gecoördineerde updates
- Geen identiteitsonderscheid tussen services
Service accounts met tokens
# Kubernetes ServiceAccount tokens
automountServiceAccountToken: true
Beter, maar:
- Tokens zijn lang geldig
- Werken alleen binnen Kubernetes
- Beperkte attestatie mogelijkheden
mTLS met statische certificaten
# Handmatig certificaat beheer
volumeMounts:
- name: certs
mountPath: /certs
Veilig, maar:
- Certificaat management is operationele overhead
- Rotatie vereist restarts
- Cross-cluster identity is complex
SPIFFE: De Standaard
SPIFFE definieert drie dingen:
1. SPIFFE ID
Een URI die een workload uniek identificeert:
spiffe://trust-domain/path/to/workload
Voorbeelden:
spiffe://example.com/ns/production/sa/api-server
spiffe://example.com/k8s/cluster-a/ns/default/pod/frontend-abc123
spiffe://example.com/aws/account-123/region/eu-west-1/instance/i-abc123
Het formaat is consistent over alle omgevingen — Kubernetes, VMs, cloud instances, bare metal.
2. SVID (SPIFFE Verifiable Identity Document)
Een kortlevende credential die de identiteit van de workload bewijst. Kan zijn:
X.509 SVID — Een certificaat met de SPIFFE ID in de SAN
Subject Alternative Name:
URI: spiffe://example.com/ns/production/sa/api-server
JWT SVID — Een gesigneerde JWT token
{
"sub": "spiffe://example.com/ns/production/sa/api-server",
"aud": ["spiffe://example.com/ns/production/sa/database"],
"exp": 1690000000
}
3. Workload API
Een lokale API (meestal een Unix socket) waar workloads hun SVIDs ophalen:
/run/spire/sockets/agent.sock
Workloads handelen nooit direct private keys. Ze vragen SVIDs aan bij de Workload API, die key generatie en rotatie afhandelt.
SPIRE: De Implementatie
SPIRE heeft twee componenten:
SPIRE Server
- Centrale autoriteit die SVIDs uitgeeft
- Slaat registratie entries op (welke workloads welke identiteiten krijgen)
- Beheert trust bundles
SPIRE Agent
- Draait op elke node
- Attesteert workloads (bewijst dat ze zijn wie ze claimen)
- Stelt de Workload API beschikbaar
- Cachet en roteert SVIDs
flowchart TD
subgraph server["SPIRE Server"]
REG["Registration Entries"]
TRUST["Trust Bundles"]
end
server --> A1["Agent<br/>(Node1)"]
server --> A2["Agent<br/>(Node2)"]
server --> A3["Agent<br/>(Node3)"]
A1 --> W1["Workload API"]
A2 --> W2["Workload API"]
A3 --> W3["Workload API"]
SPIRE Installeren op Kubernetes
Met Helm:
helm repo add spiffe https://spiffe.github.io/helm-charts-hardened
helm repo update
# Installeer SPIRE CRDs
helm install spire-crds spiffe/spire-crds \
--namespace spire-system \
--create-namespace
# Installeer SPIRE
helm install spire spiffe/spire \
--namespace spire-system \
--set global.spire.trustDomain=example.com
Voor GitOps met ArgoCD:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: spire-crds
namespace: argocd
spec:
project: default
source:
repoURL: https://spiffe.github.io/helm-charts-hardened
chart: spire-crds
targetRevision: 0.4.0
destination:
server: https://kubernetes.default.svc
namespace: spire-system
syncPolicy:
automated:
prune: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: spire
namespace: argocd
spec:
project: default
source:
repoURL: https://spiffe.github.io/helm-charts-hardened
chart: spire
targetRevision: 0.21.0
helm:
values: |
global:
spire:
trustDomain: example.com
clusterName: production
spire-server:
replicaCount: 3
dataStore:
sql:
databaseType: postgres
spire-agent:
socketPath: /run/spire/sockets/agent.sock
destination:
server: https://kubernetes.default.svc
namespace: spire-system
syncPolicy:
automated:
prune: true
Workload Registratie
Workloads moeten geregistreerd worden om identiteiten te ontvangen. Met SPIRE’s Kubernetes workload registrar gebeurt dit automatisch:
apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterSPIFFEID
metadata:
name: api-server
spec:
spiffeIDTemplate: "spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}"
podSelector:
matchLabels:
app: api-server
namespaceSelector:
matchLabels:
spire-enabled: "true"
Elke pod die matcht met de selector in een gelabelde namespace krijgt het gespecificeerde SPIFFE ID.
SVIDs Gebruiken in Applicaties
Optie 1: SPIFFE Helper Sidecar
De SPIFFE helper sidecar haalt SVIDs op en schrijft ze naar disk:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
template:
spec:
containers:
- name: app
image: my-app:v1.0.0
volumeMounts:
- name: spiffe
mountPath: /spiffe
readOnly: true
env:
- name: TLS_CERT
value: /spiffe/svid.pem
- name: TLS_KEY
value: /spiffe/svid_key.pem
- name: CA_BUNDLE
value: /spiffe/bundle.pem
- name: spiffe-helper
image: ghcr.io/spiffe/spiffe-helper:latest
volumeMounts:
- name: spiffe
mountPath: /spiffe
- name: spire-agent-socket
mountPath: /run/spire/sockets
volumes:
- name: spiffe
emptyDir: {}
- name: spire-agent-socket
hostPath:
path: /run/spire/sockets
type: Directory
Optie 2: Native SPIFFE Library
Voor applicaties die direct integreren:
import (
"github.com/spiffe/go-spiffe/v2/workloadapi"
)
func main() {
ctx := context.Background()
// Connect naar Workload API
source, err := workloadapi.NewX509Source(ctx)
if err != nil {
log.Fatal(err)
}
defer source.Close()
// Haal SVID op
svid, err := source.GetX509SVID()
if err != nil {
log.Fatal(err)
}
fmt.Printf("SPIFFE ID: %s\n", svid.ID)
// Gebruik voor mTLS
tlsConfig := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeID(
spiffeid.RequireID(spiffeid.MustParseSpiffeID("spiffe://example.com/ns/production/sa/database")),
))
}
Optie 3: Envoy Sidecar met SDS
Envoy kan SVIDs ophalen via de Secret Discovery Service:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
image: my-app:v1.0.0
ports:
- containerPort: 8080
- name: envoy
image: envoyproxy/envoy:v1.28-latest
volumeMounts:
- name: envoy-config
mountPath: /etc/envoy
- name: spire-agent-socket
mountPath: /run/spire/sockets
Envoy config voor SDS:
static_resources:
clusters:
- name: spire_agent
connect_timeout: 1s
type: STATIC
http2_protocol_options: {}
load_assignment:
cluster_name: spire_agent
endpoints:
- lb_endpoints:
- endpoint:
address:
pipe:
path: /run/spire/sockets/agent.sock
- name: backend
connect_timeout: 1s
type: STRICT_DNS
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
tls_certificate_sds_secret_configs:
- name: "spiffe://example.com/ns/default/sa/api-server"
sds_config:
api_config_source:
api_type: GRPC
grpc_services:
- envoy_grpc:
cluster_name: spire_agent
validation_context_sds_secret_config:
name: "spiffe://example.com"
sds_config:
api_config_source:
api_type: GRPC
grpc_services:
- envoy_grpc:
cluster_name: spire_agent
Service Mesh Integratie
SPIRE integreert met service meshes:
Istio
Istio kan SPIRE gebruiken als identity provider:
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
trustDomain: example.com
values:
global:
caAddress: spiffe-csi-driver:443
Linkerd
Linkerd’s identity systeem is SPIFFE-compatibel. Integratie is eenvoudig met de identity controller.
Cross-Cluster Federatie
SPIRE maakt identity over cluster grenzen mogelijk door federatie:
Cluster A (production)
global:
spire:
trustDomain: production.example.com
federation:
enabled: true
bundleEndpoint:
address: spire-bundle.production.example.com
port: 8443
trustDomainBundles:
- endpointURL: https://spire-bundle.staging.example.com:8443
trustDomain: staging.example.com
Cluster B (staging)
global:
spire:
trustDomain: staging.example.com
federation:
enabled: true
bundleEndpoint:
address: spire-bundle.staging.example.com
port: 8443
trustDomainBundles:
- endpointURL: https://spire-bundle.production.example.com:8443
trustDomain: production.example.com
Nu kunnen services in staging identiteiten van production verifiëren en vice versa.
Attestatie Methoden
SPIRE bewijst workload identity door attestatie. Kubernetes gebruikt:
Node Attestatie
# Hoe nodes hun identiteit bewijzen aan de server
nodeAttestor:
k8sPsat:
enabled: true
cluster: production
De node bewijst dat het een legitieme Kubernetes node is met projected service account tokens.
Workload Attestatie
# Hoe workloads hun identiteit bewijzen aan de agent
workloadAttestors:
k8s:
enabled: true
De agent verifieert de workload’s container via de Kubernetes API.
Autorisatie met OPA
SPIRE levert identity; je hebt nog steeds autorisatie nodig. Combineer met Kyverno of OPA:
# OPA policy met SPIFFE IDs
package authz
default allow = false
allow {
# Sta api-server toe database te benaderen
input.source == "spiffe://example.com/ns/production/sa/api-server"
input.destination == "spiffe://example.com/ns/production/sa/database"
}
allow {
# Sta frontend toe api-server te benaderen
input.source == "spiffe://example.com/ns/production/sa/frontend"
input.destination == "spiffe://example.com/ns/production/sa/api-server"
}
Mijn Productie Setup
Hier is mijn SPIRE configuratie:
global:
spire:
trustDomain: infrastructure.internal
clusterName: production
spire-server:
replicaCount: 3
dataStore:
sql:
databaseType: postgres
connectionString: "postgres://spire:password@postgres:5432/spire?sslmode=require"
nodeAttestor:
k8sPsat:
enabled: true
serviceAccountAllowList:
- spire-system:spire-agent
ca:
keyType: ec-p256
ttl: 24h
spire-agent:
socketPath: /run/spire/sockets/agent.sock
workloadAttestors:
k8s:
enabled: true
sds:
enabled: true
defaultBundleName: "null"
defaultAllBundlesName: ROOTCA
spiffe-csi-driver:
enabled: true
Belangrijke beslissingen:
- PostgreSQL backend — Persistente server state voor HA
- EC-P256 keys — Balans van security en performance
- 24h TTL — SVIDs roteren frequent
- CSI driver — Schone volume-gebaseerde SVID levering
Troubleshooting
Check agent health
kubectl exec -n spire-system -it spire-agent-xxx -- \
/opt/spire/bin/spire-agent healthcheck
Lijst geregistreerde workloads
kubectl exec -n spire-system -it spire-server-0 -- \
/opt/spire/bin/spire-server entry show
Verifieer workload identity
kubectl exec -it my-pod -- \
cat /run/spire/sockets/agent.sock # Check socket bestaat
kubectl exec -it my-pod -- \
openssl x509 -in /spiffe/svid.pem -text -noout | grep URI
Waarom Dit Ertoe Doet
Statische secrets zijn een risico. Ze kunnen lekken, zijn moeilijk te roteren, en beantwoorden niet “wie maakt dit request?”
SPIFFE/SPIRE levert:
- Automatische identity — Workloads krijgen identity zonder handmatige secrets
- Kortlevende credentials — SVIDs roteren frequent, beperken blootstelling
- Cryptografisch bewijs — Kan niet worden gefalsificeerd of herhaald
- Cross-environment — Hetzelfde identity model overal
Dit is zero trust correct geïmplementeerd. Elke service bewijst zijn identiteit met cryptografisch bewijs, elke keer, ongeacht netwerkpositie.
Begrijpen hoe workload identity werkt is niet academisch — het is essentieel voor het bouwen van systemen waar het compromitteren van één service niet alles compromitteert.
Identity zou automatisch en cryptografisch verifieerbaar moeten zijn. SPIFFE en SPIRE geven elke workload een identiteit die het niet hoeft te beheren — en die aanvallers niet kunnen stelen.
