Istio + SPIRE Integration Practical Guide: From Automatic SVID Issuance to Multi-Cluster Federation
This post is Part 1 of a 3-part series.
- Part 1 (current): Istio + SPIRE Integration and Multi-Cluster Federation
- Part 2 (upcoming): Eliminating Service Account Keys with JWT-SVID and AWS IAM OIDC Integration
- Part 3 (upcoming): Zero Trust Without Sidecars Using Istio Ambient Mode + SPIRE
SPIFFE/SPIRE answers cryptographically the question: "Is this workload actually running in the environment I expect?" It proves through multiple factors which node, which service account, and which image the workload is running on, then issues an SVID (cryptographic identity document), which the Envoy sidecar automatically receives to establish mTLS.
Kubernetes's default service account tokens and namespace-based mTLS alone leave this proof incomplete. If a service account token is stolen, the identity itself can be forged, and in multi-cluster environments, connecting trust between different PKI schemes becomes even more complex. In environments that require compliance with NIST SP 800-207 (the Zero Trust principle requiring explicit identity verification for all workloads) — such as finance and healthcare — and in multi-cloud environments using AWS, GCP, and Azure simultaneously, SPIFFE/SPIRE is rapidly establishing itself as the vendor-neutral standard.
This post walks through the complete process of integrating Istio and SPIRE in practice, and how to configure federation between different trust domains in a multi-cluster environment, step by step.
Core Concepts
SPIFFE, SPIRE, SVID — Three Key Terms and Two Identity Forms
SPIFFE (Secure Production Identity Framework For Everyone): An open specification that assigns standardized cryptographic identities to workloads in cloud-native environments. SPIRE is a CNCF graduated project that implements this specification at runtime, and SVID (SPIFFE Verifiable Identity Document) is its output.
SVIDs are issued in two forms.
| Type | Form | Primary Use Case |
|---|---|---|
| X.509 SVID | X.509 certificate | Envoy mTLS — encrypted communication between services |
| JWT-SVID | JWT token | OIDC integration with AWS IAM, GCP Workload Identity, etc. |
X.509 SVIDs include the SPIFFE URI in the certificate's SAN (Subject Alternative Name) field. To inspect an issued SVID directly, you can use the following command.
# SVID 인증서의 SAN 필드를 직접 확인
openssl x509 -in svid.pem -text -noout | grep -A2 "Subject Alternative Name"
# 출력 예시:
# X509v3 Subject Alternative Name:
# URI:spiffe://cluster-a.example.com/ns/prod/sa/payment-serviceA SPIFFE URI follows the format spiffe://<trust-domain>/<path>. For example, spiffe://cluster-a.example.com/ns/prod/sa/payment-service means "the payment-service service account in the prod namespace within the cluster-a.example.com trust domain," and this identity is verified during the mTLS handshake.
Istio + SPIRE Integration Architecture
This approach replaces Istio's default SDS (Secret Discovery Service) implementation with the SPIRE Agent. The Envoy sidecar accesses the SPIRE Agent's Workload API via a UNIX Domain Socket to receive SVIDs, and automatically renews them at 50% of the TTL.
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Pod │
│ ┌──────────────┐ SDS/UNIX Socket ┌──────────────────┐ │
│ │ Envoy Proxy │◄──────────────── │ SPIRE Agent │ │
│ │ (Sidecar) │ SVID X.509 cert │ (DaemonSet) │ │
│ └──────────────┘ └──────┬───────────┘ │
└─────────────────────────────────────────────│───────────────┘
│ gRPC (Node Attestation
│ + SVID 서명 요청)
┌──────────▼──────────┐
│ SPIRE Server │
│ (StatefulSet) │
│ ClusterSPIFFEID │
│ (Controller Mgr) │
└─────────────────────┘The SPIRE Agent runs as a DaemonSet with one instance per node, directly verifying (attesting) the workload's runtime environment (node, service account, namespace). The SPIRE Agent and SPIRE Server communicate via gRPC to handle node authentication and SVID signing. The key distinction is proving not merely "is this Pod's service account token valid?" but "is this Pod actually running on this node under these conditions?"
Multi-Cluster Trust Domain Federation
Each cluster operates as an independent trust domain, while the SPIRE Servers exchange their root CAs with each other through Bundle Endpoints.
cluster-a cluster-b
SPIRE Server ──── Bundle Exchange ──── SPIRE Server
(port 8443) (루트 CA 자동 교환) (port 8443)
│ │
▼ ▼
Workload A Workload B
spiffe://cluster-a.../sa/frontend spiffe://cluster-b.../sa/payment
└──────── mTLS 수립 가능 ──────────┘Practical Implementation
Single Cluster Setup: Basic Istio + SPIRE Integration
Step 1: Install the full SPIRE stack with Helm.
helm install spire spire/spire \
--namespace spire-system \
--set global.trustDomain="cluster.local" \
--set spire-controller-manager.enabled=true \
--set spiffe-csi-driver.enabled=true| Option | Role |
|---|---|
global.trustDomain |
Must match Istio's trust domain exactly |
spire-controller-manager.enabled |
Enables the ClusterSPIFFEID CRD controller |
spiffe-csi-driver.enabled |
Enables secure socket mount method (recommended over hostMount) |
Verify the installation:
kubectl get pods -n spire-system
# 기대 출력: spire-server, spire-agent(DaemonSet), spiffe-csi-driver 모두 RunningStep 2: Specify the SPIRE SDS socket when installing Istio.
# istio-operator.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
defaultConfig:
proxyMetadata:
# Envoy가 SPIRE Agent 소켓을 SDS 소스로 인식하는 핵심 설정
SPIFFE_ENDPOINT_SOCKET: "unix:///run/secrets/workload-spiffe-credentials/socket"
PROXY_CONFIG_XDS_AGENT: "true"
values:
global:
pilotCertProvider: "spire" # Istiod가 SPIRE를 인증서 제공자로 인식
sds:
token:
aud: istio-ca
components:
pilot:
k8s:
env:
- name: ENABLE_CA_SERVER
value: "false"ENABLE_CA_SERVER: "false": Disables the Istiod built-in CA server, fully delegating SVID issuance to SPIRE.
SPIFFE_ENDPOINT_SOCKETis the critical setting that tells Envoy where to find the SPIRE Agent socket — without this value, Envoy cannot receive SVIDs.
Step 3: Add the SPIFFE CSI volume mount to the sidecar injection template.
# ConfigMap: istio-sidecar-injector
spec:
containers:
- name: istio-proxy
env:
- name: SPIFFE_ENDPOINT_SOCKET
value: "unix:///run/secrets/workload-spiffe-credentials/socket"
volumeMounts:
- name: workload-socket
mountPath: /run/secrets/workload-spiffe-credentials
volumes:
- name: workload-socket
csi:
driver: "csi.spiffe.io"
readOnly: trueWithout the
SPIFFE_ENDPOINT_SOCKETenvironment variable, Envoy cannot find the socket path and will fail to receive SVIDs. The mount path and the directory portion of the environment variable must match.
Step 4: Automatically register workloads with ClusterSPIFFEID.
Since 2023, the old k8s-workload-registrar has been deprecated, and the SPIRE Controller Manager with the ClusterSPIFFEID CRD has become the recommended standard. By declaring label selectors and the federatesWith field, workload registration and federation relationships are handled automatically.
apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterSPIFFEID
metadata:
name: istio-workloads
spec:
spiffeIDTemplate: "spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}"
podSelector:
matchLabels:
security.istio.io/tlsMode: "istio"
workloadSelectorTemplates:
- "k8s:ns:{{ .PodMeta.Namespace }}"
- "k8s:sa:{{ .PodSpec.ServiceAccountName }}"| Field | Description |
|---|---|
spiffeIDTemplate |
Dynamically generates the SPIFFE URI using Go template syntax |
podSelector |
Targets only Pods with Istio sidecar injection enabled |
workloadSelectorTemplates |
Validates the Pod's namespace and service account during attestation |
Verify automatic workload registration:
# SPIRE Server Pod 이름 조회 후 등록된 entry 확인
SPIRE_POD=$(kubectl get pod -n spire-system -l app=spire-server -o name | head -1)
kubectl exec -n spire-system $SPIRE_POD -- \
/opt/spire/bin/spire-server entry show
# 출력: spiffe://cluster.local/ns/prod/sa/payment-service 등 자동 등록 확인Multi-Cluster Setup: Trust Domain Federation
SPIRE Server configuration for Cluster A (HCL format)
SPIRE Server uses native configuration files in HCL (HashiCorp Configuration Language) format. If you're familiar with Kubernetes YAML, think of it as a curly-brace block-based structure.
# spire-server.conf (cluster-a)
# 단순화된 예시 — 운영 환경에서는 datastore, key_manager, notifier 블록 추가 필요
server {
trust_domain = "cluster-a.example.com"
federation {
bundle_endpoint {
address = "0.0.0.0"
port = 8443
}
federates_with "cluster-b.example.com" {
bundle_endpoint_url = "https://spire-server.cluster-b.svc:8443"
bundle_endpoint_profile "https_spiffe" {
endpoint_spiffe_id = "spiffe://cluster-b.example.com/spire/server"
}
}
}
}Verify federation status:
SPIRE_POD=$(kubectl get pod -n spire-system -l app=spire-server -o name | head -1)
kubectl exec -n spire-system $SPIRE_POD -- \
/opt/spire/bin/spire-server bundle list
# 출력: cluster-b.example.com 번들이 정상 교환되었는지 확인Declare federation on Cluster A using the ClusterFederatedTrustDomain resource.
apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterFederatedTrustDomain
metadata:
name: cluster-b-federation
spec:
trustDomain: "cluster-b.example.com"
bundleEndpointURL: "https://spire-server.cluster-b.svc:8443"
bundleEndpointProfile:
type: HTTPSSPIFFEAuthentication
endpointSPIFFEID: "spiffe://cluster-b.example.com/spire/server"Workload registration including federation targets
apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterSPIFFEID
metadata:
name: cross-cluster-workload
spec:
spiffeIDTemplate: "spiffe://cluster-a.example.com/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}"
federatesWith:
- "cluster-b.example.com"Authorize workloads from the remote cluster using Istio AuthorizationPolicy.
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-cluster-b-workloads
spec:
rules:
- from:
- source:
principals:
- "spiffe://cluster-b.example.com/ns/prod/sa/payment-service"The core principle of federation: The SPIRE Servers on both clusters periodically fetch each other's root CAs from the Bundle Endpoints. When a root CA is rotated on one cluster, it is automatically reflected on the other, eliminating the need to manually distribute certificates.
Large-Scale Operations: Indeed's Nested SPIRE Topology
In large-scale multi-cluster deployments, registering all workloads to a single SPIRE Server creates availability and scalability limitations. Indeed adopted a Nested SPIRE topology to address this problem.
Root SPIRE Server (Global)
│ X.509 발급 (Intermediate CA)
├── Intermediate SPIRE Server (cluster-a)
│ └── Workload SVIDs (X.509 + JWT)
│
└── Intermediate SPIRE Server (cluster-b)
└── Workload SVIDs (X.509 + JWT)Intermediate SPIRE Servers designate the Root SPIRE Server as their UpstreamAuthority.
# intermediate-spire-server.conf (cluster-a)
# 단순화된 예시 — 운영 환경에서는 datastore, key_manager 블록 추가 필요
UpstreamAuthority "spire" {
plugin_data {
server_address = "root-spire-server.spire-root.svc"
server_port = 8081
workload_api_socket_path = "/tmp/spire-agent/public/api.sock"
}
}| Layer | Role |
|---|---|
| Root SPIRE Server | Issues X.509 certificates to each cluster's intermediate server; serves as the enterprise-wide trust anchor |
| Intermediate SPIRE Server | Performs workload attestation and SVID signing within the cluster |
| Workload SVID | Simultaneously issues X.509 SVIDs for Istio mTLS and JWT-SVIDs for AWS IAM and Confluent integration |
JWT-SVID → OIDC token conversion pattern: By converting JWT-SVIDs issued by SPIRE into OIDC tokens for AWS IAM or GCP Workload Identity Federation, you can access cloud resources without service account keys. This pattern, which Indeed uses in production, originates from the separation of roles between X.509 SVIDs and JWT-SVIDs — X.509 handles mTLS, and JWT handles external service authentication.
Pros and Cons Analysis
Advantages
| Item | Details |
|---|---|
| Fine-grained Attestation | Combining multiple factors such as node, image signature, and process list enables far more granular identity proof than basic Istio |
| Automatic SVID Renewal | Envoy renews automatically with zero downtime at 50% of the TTL, requiring no operator intervention |
| Regulatory Compliance | Short-lived certificates — typically under one hour — minimize the impact of leaks, making NIST SP 800-207 compliance easier |
| Multi-Cloud Standard | Unifies AWS, GCP, Azure, and on-premises under a single vendor-neutral standard |
| Hardened Workload Identity | Even if a service account token is stolen, no identity is issued unless the entire runtime environment can be proven |
Disadvantages and Caveats
| Item | Details | Mitigation |
|---|---|---|
| Pre-registration Required | Workloads not registered with SPIRE will not reach READY state, requiring deployment order management | Configure automatic registration with the ClusterSPIFFEID CRD |
| SPIRE Agent Single Point of Failure | If the DaemonSet SPIRE Agent is unhealthy, all workloads on that node cannot receive identities | Configure PodDisruptionBudget and liveness probes for the Agent |
| Trust Domain Mismatch | If Istio and SPIRE have different trust domains, authentication and authorization errors will occur | Ensure global.trustDomain matches the Istio configuration at install time |
| Increased Operational Complexity | Multi-cluster federation adds operational overhead such as bundle endpoint management and maintaining TLS connections | Declarative management is possible with the ClusterFederatedTrustDomain CRD |
| HostMount Security Risk | Sharing the SPIRE Agent socket via hostMount risks exposing full node-level permissions | Use the SPIFFE CSI Driver instead |
| Initial Learning Curve | Configuring the full stack — CSI Driver, Controller Manager, Server/Agent — is required | The entire stack can be installed with a single Helm chart spire/spire; it is recommended to follow the SPIRE official documentation and the Istio integration guide step by step to build proficiency |
SDS (Secret Discovery Service): The API that Envoy uses to dynamically fetch certificates and keys. Istio uses its own SDS implementation by default, but when integrating with SPIRE, the SPIRE Agent takes over this role. As long as Envoy knows the socket path specified by
SPIFFE_ENDPOINT_SOCKET, it operates identically regardless of the issuing party.
Most Common Mistakes in Practice
-
Setting different trust domains for Istio and SPIRE — The
global.trustDomainat Helm install time must match Istio'smeshConfig.trustDomain. If they differ, SVIDs will be issued but Istio will not trust them, causing mTLS connections to fail. -
Mounting the volume without the
SPIFFE_ENDPOINT_SOCKETenvironment variable — Even if the socket path is mounted, Envoy requires theSPIFFE_ENDPOINT_SOCKETenvironment variable to recognize that path as its SDS source. With only the volume mount and no environment variable, Envoy will continue using the default Istio CA. -
Not opening the bundle endpoint firewall in multi-cluster federation — Port 8443 on the SPIRE Server must be reachable from the remote cluster. Failing to verify NetworkPolicy and ingress rules in advance will cause bundle exchange to silently fail.
Closing Thoughts
The Istio + SPIRE integration puts into practice the principle that "a stolen service account token alone cannot forge an identity." This architecture — which cryptographically proves the entire runtime environment of a workload — forms the essential foundation of a Zero Trust architecture.
Three steps you can take right now:
-
Set up a single-cluster practice environment — Create a local cluster with
kindorminikube, then try installing the full SPIRE stack with the command below.bashhelm install spire spire/spire \ --set global.trustDomain="cluster.local" \ --set spire-controller-manager.enabled=true \ --set spiffe-csi-driver.enabled=true \ --namespace spire-system --create-namespace -
Verify ClusterSPIFFEID CRD registration — Deploy a sample workload, then use the command below to confirm the workload was automatically registered.
bashSPIRE_POD=$(kubectl get pod -n spire-system -l app=spire-server -o name | head -1) kubectl exec -n spire-system $SPIRE_POD -- \ /opt/spire/bin/spire-server entry show -
Practice multi-cluster federation — The AWS Samples
istio-on-eksrepository provides an EKS-based SPIRE federation reference architecture, allowing you to follow a real multi-cluster federation setup step by step.
Next post: A Workload Identity pattern for accessing S3 and DynamoDB without service account keys by integrating SPIRE's JWT-SVIDs with AWS IAM OIDC — "Goodbye Service API Keys."
References
Official Documentation
- Istio Official Docs — SPIRE Integration
- SPIFFE Official Site — SPIRE Concepts
- SPIFFE Official Docs — Scaling SPIRE
- SPIRE Controller Manager — ClusterSPIFFEID CRD Documentation
Practical Configuration Guides
- AWS Samples: Istio on EKS with SPIRE Federation
- imesh.ai — How to Integrate Istio and SPIRE for Secure Workload Identity
- Jimmy Song — Managing Certificates in Istio with cert-manager and SPIRE
- HPE Developer — Service Mesh Security Hardening with SPIRE and Istio
In-Depth Learning and Case Studies