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.