ACE Journal

Network microsegmentation with SPIFFE and SPIRE in multi-cloud environments

Who this is for: platform engineers and security architects running workloads across two or more cloud environments who have outgrown IP-based segmentation and want identity-aware microsegmentation that survives infrastructure churn. You should be comfortable with Kubernetes, have passing familiarity with X.509 certificates, and understand why mTLS is different from one-way TLS. You do not need a service mesh to start, though mesh integration is covered later.

The firewall rule that doesn’t survive a reschedule

Imagine you have a payments service running on Kubernetes in AWS and an accounts service running on GCP Cloud Run. You want to ensure that only the payments service can call the accounts API. The classic approach is an IP allowlist: collect the NAT gateway IPs for the AWS cluster, add them to a GCP VPC firewall rule, and call it done.

This works until it breaks. The AWS cluster gets a new NAT gateway during a maintenance event. A second cluster spins up in a different region for disaster recovery and needs the same access. The accounts service moves to a new VPC. Each of these events requires a firewall rule update, and each update is a manual step that can be missed at 2 AM during an incident.

The deeper problem is that IP addresses are infrastructure artifacts, not identity. The payments service does not have a stable IP. It has a name, a codebase, a team, and a purpose. SPIFFE encodes that identity into a cryptographic document the service carries everywhere it runs, so policy can be written against the identity rather than the address. Firewall rules stop needing updates every time the infrastructure changes.

How SPIFFE identity works

Every workload that enrolls with SPIRE receives a SPIFFE Verifiable Identity Document (SVID). The SVID is either an X.509 certificate or a JWT token. Its key field is the SPIFFE ID, a URI of the form:

spiffe://<trust-domain>/<path>

A real example for a payments service in a production trust domain:

spiffe://prod.example.com/payments/api

The trust domain is your organizational boundary, roughly analogous to a DNS zone but for identity. The path encodes service-level information: environment, team, service name. The format is a convention; SPIRE does not mandate a specific hierarchy, but a consistent scheme like /<env>/<namespace>/<service> pays off at policy-writing time.

The X.509-SVID is an X.509 certificate with the SPIFFE ID in the Subject Alternative Name (SAN) URI field. The JWT-SVID is a signed JWT with the SPIFFE ID in the sub claim and an intended audience in aud. X.509 SVIDs are the default for mTLS; JWT SVIDs are used for request-level identity in HTTP headers when the transport is already TLS-protected by other means.

SVIDs are short-lived. The default TTL is one hour. The SPIRE agent renews them automatically before expiry, so a workload always holds a current credential without any application-level renewal logic.

SPIRE architecture: server, agent, and the two-stage attestation

SPIRE has two components. The SPIRE server manages the trust domain: it holds the root CA, signs SVIDs, and stores registration entries that map attestation selectors to SPIFFE IDs. The SPIRE agent runs on every node and is the workload’s local contact point for fetching SVIDs via the Workload API.

Figure 1 shows the relationship between components and the two attestation stages.

sequenceDiagram
    participant Node
    participant Agent as SPIRE agent
    participant Server as SPIRE server
    participant WL as Workload

    Note over Agent,Server: Stage 1 - Node attestation
    Agent->>Server: Present node proof (AWS IID / k8s SAT / TPM)
    Server->>Agent: Issue node SVID

    Note over WL,Agent: Stage 2 - Workload attestation
    WL->>Agent: Request SVID (Workload API)
    Agent->>Agent: Inspect workload (pid, k8s labels, uid)
    Agent->>Server: Match selectors against registration entries
    Server->>Agent: Issue workload SVID
    Agent->>WL: Deliver SVID + trust bundle

Figure 1. Two-stage SPIRE attestation: node attestation establishes the agent’s own identity, then workload attestation uses that foothold to verify each process running on the node.

Node attestation happens when the SPIRE agent starts. The agent must prove to the server that it is running on a legitimate node before it is trusted to vouch for any workload on that node. In AWS, this proof is the EC2 Instance Identity Document (IID) signed by AWS. In Kubernetes, it is a service account token (SAT) issued by the cluster’s API server. On bare metal, it can be a TPM-backed attestation.

Workload attestation happens when a workload calls the Workload API. The agent inspects the caller using OS-level mechanisms: the process’s Unix UID, kernel namespace, or (in Kubernetes) the pod’s labels and service account name gathered via the kubelet API. The agent matches those properties against registration entries in the server and returns the matching SVID.

The Workload API is a gRPC API exposed over a Unix domain socket at /run/spire/sockets/agent.sock by default. Any process on the node can call it; the agent uses the OS-level workload attestation to decide what to return to which caller.

Worked example: registering a workload entry

Registration entries are the heart of SPIRE’s policy model. Each entry says: “a workload matching these selectors on this node gets this SPIFFE ID.” Here is a real spire-server entry create command registering the payments API pod in a Kubernetes cluster:

spire-server entry create \
  -spiffeID spiffe://prod.example.com/payments/api \
  -parentID spiffe://prod.example.com/k8s-node/worker-1 \
  -selector k8s:ns:payments \
  -selector k8s:sa:payments-api \
  -ttl 3600

Breaking this down:

After a workload matching both selectors calls the Workload API, it receives an X.509-SVID with spiffe://prod.example.com/payments/api in its SAN. The workload does not need to know about SPIRE’s internals; it just gets a certificate it can use for mTLS.

Fetching an SVID from the Workload API

Applications can fetch SVIDs directly using the SPIFFE Workload API SDK (available for Go, Java, Python, and C). For most infrastructure, though, the integration point is a sidecar, typically Envoy, rather than the application process itself.

Here is the minimal Envoy SDS (Secret Discovery Service) configuration that tells an Envoy sidecar to pull its TLS certificates from the SPIRE agent rather than from a static file:

static_resources:
  clusters:
    - name: spire_agent
      connect_timeout: 0.25s
      http2_protocol_options: {}
      load_assignment:
        cluster_name: spire_agent
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    pipe:
                      path: /run/spire/sockets/agent.sock

  listeners:
    - name: payments_outbound
      address:
        socket_address: { address: 0.0.0.0, port_value: 8001 }
      filter_chains:
        - transport_socket:
            name: envoy.transport_sockets.tls
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
              combined_validation_context:
                default_validation_context:
                  match_typed_subject_alt_names:
                    - san_type: URI
                      matcher:
                        exact: spiffe://prod.example.com/accounts/api
              sds_config:
                resource_api_version: V3
                api_config_source:
                  api_type: GRPC
                  grpc_services:
                    - envoy_grpc:
                        cluster_name: spire_agent

The key points: pipe.path points to the SPIRE agent socket; sds_config tells Envoy to fetch TLS secrets from that socket; match_typed_subject_alt_names enforces that the remote peer presents a specific SPIFFE ID. This is the L7 expression of your microsegmentation policy. Envoy validates the mTLS handshake and checks the peer’s SPIFFE ID before forwarding any traffic, regardless of what IP address the peer is coming from.

Figure 2 shows how the Envoy sidecar fits into the data plane, with SPIRE providing credentials out-of-band.

flowchart LR
    subgraph payments-pod
        PA[payments-api\nprocess] --> PE[Envoy\nsidecar]
    end
    subgraph accounts-pod
        AE[Envoy\nsidecar] --> AA[accounts-api\nprocess]
    end
    SA[SPIRE agent\nnode A] -->|SVID + trust bundle| PE
    SB[SPIRE agent\nnode B] -->|SVID + trust bundle| AE
    PE -->|mTLS, SPIFFE peer check| AE
    SS[SPIRE server] --> SA
    SS --> SB

Figure 2. Data-plane layout with Envoy sidecars and SPIRE agents. The SPIRE server pushes trust material to agents, agents deliver SVIDs to sidecars, and the sidecars enforce mTLS with SPIFFE peer verification on every connection.

The hard part: bootstrapping, rotation, and federation

Attestation bootstrapping

The weakest moment in any SPIRE deployment is the initial node attestation. The agent must prove to the server that it is running on a legitimate node, but the agent has no credential yet to make that proof. The solution is platform-specific trust:

The practical risk: if you deploy SPIRE with an overly permissive selector (for example, attesting any pod in a namespace rather than pods with a specific service account), you issue valid SVIDs to workloads you did not intend to authorize. The SVID will pass mTLS, the peer will accept it, and you will not discover the misconfiguration until an audit.

SVID rotation

SVID rotation is automatic and is one of SPIFFE’s design wins. The SPIRE agent monitors SVID expiry and renews before the TTL elapses. The Workload API delivers the new SVID to the workload without interrupting connections already established. However, automatic rotation has two operational edges:

The SPIRE server must be reachable from every agent for rotation to succeed. If the server is unavailable, agents continue to serve existing SVIDs until they expire. After expiry, workloads holding stale SVIDs will fail mTLS. Server high availability (HA deployment with a shared datastore such as PostgreSQL) is not optional in production.

Short TTLs increase rotation frequency and reduce the blast radius of a stolen SVID, but they also increase server load. One hour is a reasonable default for most workloads. Reducing to five minutes for highly sensitive services is defensible but requires careful server-capacity planning.

Trust domain federation

A single SPIRE server manages one trust domain. Multi-cloud environments need federation. Figure 3 shows the federation topology for a two-cloud deployment.

flowchart TB
    subgraph AWS["AWS trust domain (aws.example.com)"]
        AS[SPIRE server A]
        AA1[Agent] & AA2[Agent]
        AS --> AA1 & AA2
    end
    subgraph GCP["GCP trust domain (gcp.example.com)"]
        GS[SPIRE server G]
        GA1[Agent] & GA2[Agent]
        GS --> GA1 & GA2
    end
    AS <-->|"bundle endpoint exchange\n(HTTPS, periodic refresh)"| GS
    AA1 -->|"SVID (aws.example.com)"| WA[payments workload]
    GA1 -->|"SVID (gcp.example.com)"| WG[accounts workload]
    WA -->|"mTLS, cross-domain SPIFFE check"| WG

Figure 3. Cross-cloud SPIFFE federation. Each cloud runs its own SPIRE server and trust domain. Bundle endpoints exchange root CA material, enabling mTLS between workloads in separate trust domains without shared networking.

Federation is configured on each server with a federatesWith entry that points to the other server’s bundle endpoint. SPIRE fetches the remote trust bundle over HTTPS and refreshes it on a schedule. The bundle endpoint must be publicly reachable (or reachable over a shared network path); SPIRE does not require a VPN, just HTTPS reachability to the endpoint.

Once federation is active, a workload in the AWS trust domain can establish mTLS with a workload in the GCP trust domain. Each side presents its own SVID, and each side verifies the peer using the federated bundle from the other trust domain.

What SPIFFE does and does not give you at L3/L4 vs L7

SPIFFE identity is a credential. It says who is calling, not what they are allowed to do. Microsegmentation with SPIFFE operates at two distinct layers:

At L4 (TCP), network policy enforced by tools like Cilium or Calico can use SPIFFE identity (via the SPIRE agent’s workload metadata) to allow or deny connections before any application data flows. This is coarse but fast: it drops TCP connections from unauthorized workloads at the kernel level.

At L7 (HTTP/gRPC), Envoy with an OPA (Open Policy Agent) sidecar can evaluate per-request policy using the SPIFFE ID from the mTLS peer certificate. The rule “allow spiffe://prod.example.com/payments/api to call spiffe://prod.example.com/accounts/api on path /v1/transfer with method POST” is L7 policy. IP allowlists cannot express this. This is where SPIFFE’s policy precision earns its keep.

SPIFFE does not replace a WAF. It does not provide egress filtering to the public internet. It does not protect against a compromised workload that legitimately holds a valid SVID. If the payments service is compromised, its SVID is valid and will pass all mTLS checks. Identity-based segmentation limits the blast radius of a breach to what the compromised workload’s SVID is authorized to reach, but it does not prevent the compromise itself.

Integration and day-to-day operations

Once SPIRE is running, the operational surface is registration entry management. Entries are typically stored in a GitOps repository and applied via spire-server entry create in CI, or managed through the spire-controller-manager for Kubernetes (a CRD-based interface that generates entries from SpiffeID custom resources).

Watching SVID health day to day:

# list all registration entries
spire-server entry show

# check agent status and connected workloads on a node
spire-agent healthcheck -socketPath /run/spire/sockets/agent.sock

# show the SVIDs currently held by the agent
spire-agent api fetch x509 \
  -socketPath /run/spire/sockets/agent.sock \
  -write /tmp/svids

The SPIRE server’s audit log records every SVID issuance: which entry matched, which workload requested it, and when. This log is your authoritative record of what identity was active on every workload at every point in time, which matters both for security auditing and for post-incident debugging when an mTLS failure produces a cryptic certificate verify failed error.

Istio integration is worth noting explicitly. Istio generates workload certificates that conform to the SPIFFE X.509-SVID format: the SAN URI is a SPIFFE ID of the form spiffe://<cluster-domain>/ns/<namespace>/sa/<service-account>. If you are already running Istio, you already have SPIFFE-format identity on every workload; the question is whether you want to integrate SPIRE for stronger attestation or use Istio’s built-in CA (Citadel/istiod) directly. Both are valid. When SPIRE is the issuing CA, pointing Istio’s Envoy proxies at the SPIRE agent’s SDS socket bypasses istiod/Citadel for workload certificate issuance, so all SVIDs are attested and rotated by SPIRE rather than by Istio’s internal CA. SPIRE adds value when you need attestation beyond what Istio’s CA provides or when you need federation across separate Istio meshes.

IP-based firewalls and security groups are not wrong; they are insufficient for the problem. They are the right tool for perimeter control and for coarse network segmentation. They break at workload identity because they conflate network address with service identity. SPIFFE complements them rather than replacing them; you can run both.

Istio (and Linkerd) provide mTLS between services out of the box using SPIFFE-format certificates, without running a separate SPIRE deployment. If your workloads are all in a single Kubernetes cluster (or a set of clusters in the same mesh), the built-in CA may be sufficient. SPIRE adds value at the edges: multi-cloud without a shared mesh, workloads outside Kubernetes (VMs, Lambda, bare metal), or environments where you need a stricter attestation model than Istio’s service account mapping provides.

Cloud-native IAM (AWS IAM Roles for Service Accounts, GCP Workload Identity Federation) solves a related but different problem: authorization to cloud APIs. A Lambda function using an IAM role can call S3; an AWS STS token is not an SVID and does not support mTLS peer authentication between two internal services. SPIFFE and cloud IAM are complementary; many real deployments map between them at the federation boundary.

Vault with its PKI secrets engine can issue client certificates for mTLS, and HashiCorp has positioned Vault as a workload identity solution. The difference from SPIRE is attestation: Vault issues certificates based on role-based trust, not platform-verified workload properties. SPIRE’s attestation model is stronger when the threat model includes a compromised credential broker.

Where it breaks

Overly broad registration entries. If a registration entry matches any pod in a namespace rather than pods with a specific service account and label set, an attacker who can schedule a pod in that namespace receives a valid SVID for your sensitive service’s identity. This is an ACL misconfiguration, not a SPIFFE design flaw, but it is the most common operational failure mode. Audit your entries with spire-server entry show and verify that selectors are as narrow as operationally feasible.

SPIRE server unavailability. Agents serve existing SVIDs until they expire. After expiry, all workloads holding expired SVIDs fail mTLS simultaneously. The SPIRE server is release-critical infrastructure. A single-server deployment in production is a latent outage waiting for a bad deployment or a disk fill. Deploy with HA (multiple server instances backed by a shared PostgreSQL or MySQL datastore) before going to production.

Federation bundle staleness. Bundle endpoints must be reachable and refreshed on schedule. If the refresh fails silently (network policy tightened, certificate expired on the endpoint), the remote trust bundle ages out and cross-cloud mTLS begins failing with certificate signed by unknown authority. Set monitoring on bundle refresh timestamps, not just on the SPIRE server process itself.

Node attestation spoofing in shared tenancy. The AWS IID attestor is sound in a single-tenant account but can be weaker in shared environments where EC2 instance metadata is accessible across workloads. If your threat model includes a compromised workload attempting to re-attest as a different node, the IID attestor alone may not be sufficient. TPM-backed attestation or Kubernetes node attestation with OIDC-bound tokens provides stronger guarantees.

Schema drift between trust domains. In a federated multi-cloud deployment, each team tends to develop its own SPIFFE ID path conventions independently. Once spiffe://aws.example.com/svc/payments in AWS needs to call spiffe://gcp.example.com/services/accounts in GCP, the policy rules require translating between two naming schemes. Establish a cross-team path convention before federation reaches more than two trust domains; retrofitting it later requires updating every registration entry.

Workloads outside the attestation model. SPIRE’s attestation model covers Kubernetes pods, EC2 instances, and VMs with supported node attestors. Legacy workloads on bare metal without a supported attestor, or short-lived batch jobs that start and finish faster than the SVID issuance round trip, do not fit cleanly. Join tokens work for edge cases but introduce a manual step that undermines automation.

Further reading

The authoritative starting point is the SPIFFE specification itself [1], which defines the SPIFFE ID format, SVID types, and Workload API. The X.509-SVID specification [2] and JWT-SVID specification [3] cover the two credential formats in detail. The SPIRE documentation [4] covers registration entries, node attestors, and the federation configuration. For the Envoy SDS integration, the Envoy documentation on TLS configuration [5] and the SPIFFE/SPIRE Envoy integration guide [6] are the right starting points. The spire-controller-manager for Kubernetes [7] covers the CRD-based entry management model. For OPA integration at L7, see the OPA Envoy plugin documentation [8]. The CNCF SPIFFE/SPIRE project page [9] reflects the current graduated status and links to the upstream community resources. For the broader zero-trust context, NIST SP 800-207 [10] defines zero-trust architecture principles without mandating a specific identity mechanism; SPIFFE is one concrete implementation of the workload identity component it describes.

Written with AI assistance, editorially reviewed.

References

[1] SPIFFE Project, “SPIFFE Specification.” https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE.md

[2] SPIFFE Project, “X.509-SVID Specification.” https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md

[3] SPIFFE Project, “JWT-SVID Specification.” https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md

[4] SPIRE Project, “SPIRE Documentation.” https://spiffe.io/docs/latest/spire-about/

[5] Envoy Proxy, “TLS Configuration.” https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/transport_sockets/tls/v3/tls.proto

[6] SPIFFE Project, “Envoy SDS Integration.” https://spiffe.io/docs/latest/microservices/envoy/

[7] SPIFFE Project, “SPIRE Controller Manager.” https://github.com/spiffe/spire-controller-manager

[8] Open Policy Agent, “Envoy Plugin.” https://www.openpolicyagent.org/docs/latest/envoy-introduction/

[9] CNCF, “SPIFFE Project.” https://www.cncf.io/projects/spiffe-and-spire-projects/

[10] NIST, “SP 800-207: Zero Trust Architecture.” https://doi.org/10.6028/NIST.SP.800-207