MastertheMesh
Solo Enterprise for Istio · Reference
Visual reference

Trust & identity — from RootTrustPolicy to an mTLS handshake

TO
Tom O'Rourke
EMEA Field CTO · Solo.io

Trace how a workload earns its SPIFFE identity across a multi-cluster ambient mesh, from an offline root through RootTrustPolicy in the mgmt cluster down to the SVID a ztunnel presents in a cross-cluster HBONE handshake. You come away knowing which CRDs to write, how the intermediate reaches each cluster, and what is inside the cert on the wire.

RootTrustPolicy SPIFFE cert-manager Vault cross-cluster mTLS

Single-cluster Istio hides the identity story. istiod spins up its own CA, signs every workload a SPIFFE cert, mTLS Just Works inside that one trust domain. No CRD to write — it happens by default.

Multi-cluster breaks that. Two clusters, two auto-generated roots, two trust domains that have never heard of each other. A ztunnel in cluster-east tries to validate the cert a ztunnel in cluster-west presents — and refuses. The fix isn't "configure trust on both clusters". It's "configure trust once, in the mgmt cluster, and let the operator push the same intermediate everywhere." That's what RootTrustPolicy is. Everything below is the trust chain drawn out end to end.

What you'll learn

By the end you can follow a workload identity all the way from the root down to a live handshake, and check each hop when something breaks:

How a SPIFFE identity is born

ROOT OF TRUST · offline / off-cluster choose ONE MANAGEMENT CLUSTER · signs & distributes intermediates cluster: gloo-mgmt DESIRED STATE · CRDs that pin the trust story WORKLOAD CLUSTERS · receive intermediate · sign SVIDs · serve mTLS east / west / central cert-manager ClusterIssuer In-cluster issuer that signs intermediates on demand for the mgmt CA cert-manager.io/v1 HashiCorp Vault PKI secrets engine External root + pki_int. Mgmt server authenticates via Kubernetes auth method enterprise CA story Secret BYO CA bundle Pre-baked Secret with ca-cert.pem / ca-key.pem / root-cert.pem applied by hand simplest, least automated root cert + signing key MANAGEMENT CLUSTER gloo-mesh-mgmt-server Deployment · xDS aggregator + CA distributor signs an intermediate per workload cluster, ships it over the relay channel CR RootTrustPolicy use this CA RootTrustPolicy admin.gloo.solo.io/v2 declares the shared root + rotation settings one per mesh CASource admin.gloo.solo.io/v2 names the issuer (ClusterIssuer · Vault · Secret) referenced by RootTrustPolicy IdentityProvider admin.gloo.solo.io/v2 SPIFFE trust-domain config + per-cluster cluster name shapes the SVID URI cluster-east eu-west-1 · trust domain: cluster.local gloo-mesh-agent receives intermediate from mgmt istiod-gloo loads cacerts Secret · signs SVIDs ztunnel holds workload SVID · serves mTLS on HBONE :15008 cluster-west us-east-2 · trust domain: cluster.local gloo-mesh-agent receives intermediate from mgmt istiod-gloo loads cacerts Secret · signs SVIDs ztunnel holds workload SVID · serves mTLS on HBONE :15008 cluster-central ap-south-1 · trust domain: cluster.local gloo-mesh-agent receives intermediate from mgmt istiod-gloo loads cacerts Secret · signs SVIDs ztunnel holds workload SVID · serves mTLS on HBONE :15008 distributes intermediate CA + signing key over relay (gRPC mTLS) HBONE HBONE Every cluster chains to the same root · ztunnel ↔ ztunnel HBONE validates across cluster boundaries
root of trust (offline) RootTrustPolicy CASource IdentityProvider · workload clusters management plane

Top-down: the root lives off-cluster — cert-manager ClusterIssuer, Vault PKI, or a pre-applied Secret. gloo-mesh-mgmt-server reads the RootTrustPolicy (which points at a CASource and an IdentityProvider), asks that root to sign a per-cluster intermediate, and ships it to each cluster's gloo-mesh-agent over the relay. The agent drops it into istio-system as the cacerts Secret. istiod-gloo picks it up and starts signing SVIDs. Every cluster's intermediate chains to the same root, so when ztunnel-east meets ztunnel-west on the wire, both sides build the chain and the handshake succeeds.

What's in the SVID handshake

The diagram above stops at "ztunnel has a cert." What does ztunnel actually do with it? When a pod in cluster-east talks to a pod in cluster-west, the local ztunnel grabs the connection, opens an HBONE tunnel to the remote east-west gateway, and the two sides swap X.509 certs whose SAN is a SPIFFE URI. Step by step:

ztunnel ↔ ztunnel · HBONE mTLS

  cluster-east                                                         cluster-west
  ┌──────────────────────────┐                            ┌──────────────────────────┐
  │ ztunnel (client side)    │                            │ ztunnel (server side)    │
  │ SA: payments/checkout    │                            │ SA: payments/ledger      │
  └──────────────┬───────────┘                            └─────────────▲────────────┘
                 │   ① TCP :15008 (HBONE) ──────────────────────────────│
                 │                                                      │
                 │   ② ClientHello                                      │
                 │      ALPN: h2                                        │
                 │      SNI : outbound_.15008_._.eastwest-gw.west       │
                 │                                                      │
                 │   ③ ServerHello + Certificate                        │
                 │ ◀──────────────────────────────────────────────────  │
                 │      X.509 leaf:                                     │
                 │        Subject: O=spiffe.io                          │
                 │        SAN URI: spiffe://cluster.local/ns/payments/  │
                 │                 sa/ledger                            │
                 │        Issuer : intermediate-cluster-west            │
                 │      Chain: leaf → int-west → SHARED ROOT  ✓         │
                 │                                                      │
                 │   ④ Certificate (client side, mTLS)                  │
                 │ ──────────────────────────────────────────────────▶  │
                 │      SAN URI: spiffe://cluster.local/ns/payments/    │
                 │               sa/checkout                            │
                 │      Chain: leaf → int-east → SHARED ROOT  ✓         │
                 │                                                      │
                 │   ⑤ Finished · session keys derived                  │
                 │ ◀───────────────────────────────────────────────▶    │
                 │                                                      │
                 │   ⑥ HBONE CONNECT  payments/ledger:8080              │
                 │ ──────────────────────────────────────────────────▶  │
                 │       (inner stream — original L4 / L7 payload)      │
                 │                                                      │
                 ▼                                                      ▼

  Both peers chain the presented leaf back to the SAME root —
  the one declared in RootTrustPolicy. If either side cannot build
  that chain, the handshake fails before any HBONE bytes flow.

Two things to hold onto. The SPIFFE URI in the SAN is the workload's identity — namespace plus ServiceAccount inside the trust domain, not an IP and not a hostname. AuthorizationPolicy rules key on this URI, full stop. And mTLS here is properly symmetric: at step ④ the client side presents its own SVID, and the server validates it against the same root. Cross-cluster only works because both intermediates chain back to that one shared root — which is the whole reason RootTrustPolicy exists.

The CRDs, group by group

🔐 RootTrustPolicy admin.gloo.solo.io/v2

This is the one knob. Pick the root, set the rotation policy, tell the mgmt-server where the per-cluster intermediates are coming from — in-cluster generator, cert-manager, or Vault. You write exactly one of these per mesh, in the mgmt cluster. If you're tempted to write a second, you've misunderstood it; back up.

RootTrustPolicy · generated CAdemo / lab — auto-generates root + intermediates
apiVersion: admin.gloo.solo.io/v2
kind: RootTrustPolicy
metadata:
  name: shared-trust
  namespace: gloo-mesh
spec:
  config:
    # Mgmt-server generates a root keypair on first apply, and signs
    # a fresh intermediate for every registered workload cluster.
    mgmtServerCa:
      generated: {}
    intermediateCertOptions:
      # Rotate when 10% of the cert's lifetime is left.
      secretRotationGracePeriodRatio: 0.10
    # Bounce pods that hold an SVID so the new one is picked up.
    autoRestartPods: true
RootTrustPolicy · cert-manager ClusterIssuerproduction — root lives in cert-manager
apiVersion: admin.gloo.solo.io/v2
kind: RootTrustPolicy
metadata:
  name: shared-trust
  namespace: gloo-mesh
spec:
  config:
    intermediateCertOptions:
      secretRotationGracePeriodRatio: 0.10
    autoRestartPods: true
    # Each workload cluster's intermediate is issued by a
    # cert-manager ClusterIssuer. The mgmt-server creates a
    # Certificate resource per cluster and waits for it to be
    # signed before shipping the result over the relay channel.
    agentCa:
      certManager:
        issuerRef:
          group: cert-manager.io
          kind: ClusterIssuer
          name: gloo-mesh-root
RootTrustPolicy · HashiCorp Vault PKIenterprise — root lives off-cluster in Vault
apiVersion: admin.gloo.solo.io/v2
kind: RootTrustPolicy
metadata:
  name: shared-trust
  namespace: gloo-mesh
spec:
  config:
    intermediateCertOptions:
      secretRotationGracePeriodRatio: 0.10
    autoRestartPods: true
    agentCa:
      vault:
        # Sign intermediates against the pki_int mount; pki (root)
        # itself stays sealed and never leaves Vault.
        caPath:   pki_int/sign/gloo-mesh
        rolePath: pki_int/roles/gloo-mesh
        server:   https://vault.internal:8200
        authMethod:
          # The mgmt-server's ServiceAccount token is exchanged for
          # a short-lived Vault token via the kubernetes auth method.
          kubernetes:
            mountPath: kubernetes
            role:      gloo-mesh

🪪 CASource & IdentityProvider admin.gloo.solo.io/v2

Two CRDs, one job each. CASource says where the CA bytes physically come from — a Secret, a cert-manager ClusterIssuer, a Vault PKI mount. IdentityProvider pins the SPIFFE trustDomain and the per-cluster name that ends up in the SVID URI. The names don't help — read it as "source of bytes" plus "shape of URI".

CASource · BYO bundle in a Secretreferences a Secret holding ca-cert + ca-key
apiVersion: admin.gloo.solo.io/v2
kind: CASource
metadata:
  name: byo-root
  namespace: gloo-mesh
spec:
  # The Secret must contain:
  #   ca-cert.pem        — the intermediate cert
  #   ca-key.pem         — its private key
  #   root-cert.pem      — the root cert all clusters trust
  #   cert-chain.pem     — leaf-to-root chain
  secret:
    name:      shared-ca-bundle
    namespace: gloo-mesh
IdentityProvider · SPIFFE trust-domain configdrives the SAN URI shape
apiVersion: admin.gloo.solo.io/v2
kind: IdentityProvider
metadata:
  name: spiffe-default
  namespace: gloo-mesh
spec:
  spiffe:
    # Trust-domain segment of every SVID URI. Single value, shared
    # by all clusters in this mesh.
    trustDomain: cluster.local
  # Per-cluster overlay: each KubernetesCluster gets a cluster name
  # baked into istiod-gloo so it can write the cluster boundary into
  # the workload SAN if you choose a per-cluster trust domain later.
  clusterName: cluster-east

📜 SVID format on the wire

Not a CRD — this is the actual cert your workload presents. Here's the URI shape, and three different ways to peek at one in flight depending on whether you're in sidecar mode, ambient mode, or just hammering the gateway with openssl from your laptop.

spiffe://... · the SVID URI shapewhat lands in the leaf's SAN
# Generic shape
spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>

# A real one — the "checkout" ServiceAccount in the "payments"
# namespace inside a single shared trust domain "cluster.local":
spiffe://cluster.local/ns/payments/sa/checkout

# Inside the X.509 leaf, this lives in:
#   X509v3 Subject Alternative Name:
#       URI:spiffe://cluster.local/ns/payments/sa/checkout
# Istio authorization policies key on this URI, not on IP or
# hostname.
istioctl proxy-config secret · dump SDS from a sidecar podsidecar mode
# List the SDS secrets the sidecar holds — there are usually two:
#   default                — the workload's own SVID
#   ROOTCA                 — the trust bundle the sidecar validates
#                            peers against
istioctl proxy-config secret -n payments deploy/checkout

# Get the PEM of the leaf and pipe through openssl to see the SAN:
istioctl proxy-config secret -n payments deploy/checkout \
  -o json \
  | jq -r '.dynamicActiveSecrets[]
           | select(.name=="default")
           | .secret.tlsCertificate.certificateChain.inlineBytes' \
  | base64 -d \
  | openssl x509 -noout -text \
  | grep -A1 'Subject Alternative Name'
ztunnel debug · workload identitiesambient mode — exact command may differ by ztunnel rev
# ztunnel exposes a localhost admin endpoint (default :15000) with
# JSON over /config_dump, /workloads, /certs and friends. The exact
# path / command name moves between ztunnel revisions, so check the
# binary on your cluster.
#
# Approximate: dump the workload table on one node.
kubectl -n istio-system exec ds/ztunnel -- \
  curl -s http://localhost:15000/config_dump | jq '.workloads'

# Approximate: dump the certs ztunnel currently holds.
kubectl -n istio-system exec ds/ztunnel -- \
  curl -s http://localhost:15000/config_dump | jq '.certificates'

# Or, on newer builds, a dedicated ztunnel sub-command:
#   kubectl -n istio-system exec ds/ztunnel -- ztunnel admin certs
# Substitute the real flag for the ztunnel rev installed on your
# cluster — `meshctl version` will tell you which it is.
openssl s_client · peek at the east-west gateway's certno kubectl needed if the LB is reachable
# Open the eastwest gateway on :15008 (HBONE) and dump the cert it
# presents. Useful when validating that cluster-east and cluster-west
# both chain to the same root.
EW_GW=$(kubectl --context=cluster-west -n istio-eastwest \
  get svc istio-eastwestgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

openssl s_client \
  -connect $EW_GW:15008 \
  -servername outbound_.15008_._.eastwest-gw.istio-eastwest.svc.cluster.local \
  -showcerts < /dev/null \
  | openssl x509 -noout -text \
  | grep -E 'Subject:|Issuer:|URI:spiffe'

CLI — inspect identity at runtime

Once the policy is applied and the agents are talking, day-2 boils down to two questions: did the intermediate actually land in this cluster, and is this specific pod presenting the SVID it should be? Five commands, in roughly that order.

🔍 Day-2 — verify trust is wired end-to-end ops

Walk the chain the same way the bytes flow. Relay healthy? Did cacerts show up in istio-system? Is the pod (or ztunnel) actually serving the right SDS? And finally, what does the gateway hand out when something on the other side of the internet asks? If all four answer yes, trust is wired.

meshctl check relay · is the agent connected at all?cert distribution rides this channel
# The intermediate + signing key are pushed from the mgmt-server to
# each cluster's gloo-mesh-agent over the relay channel (gRPC mTLS).
# If relay is unhealthy, cert rotation will silently lag — so this
# is the first thing to check when SVIDs go stale.
meshctl check relay \
  --kubecontext=gloo-mgmt \
  --remote-context=cluster-east

# Full health check (relay + agent + CA reconcile state):
meshctl check --kubecontext=cluster-east
kubectl get secret cacerts · the intermediate that landedone per workload cluster
# The agent writes the intermediate + key into a Secret called
# `cacerts` in istio-system. istiod-gloo mounts that Secret and
# uses it as its SPIFFE CA.
for C in cluster-east cluster-west cluster-central; do
  echo "--- $C ---"
  kubectl --context=$C -n istio-system get secret cacerts \
    -o jsonpath='{.data.ca-cert\.pem}' \
    | base64 -d \
    | openssl x509 -noout -subject -issuer -dates
done

# Sanity-check: every cluster's intermediate should share the same
# Issuer DN (= the shared root).
istioctl proxy-config secret · what SDS the sidecar holdssidecar mode
# A sidecar holds two SDS secrets:
#   default  — the workload's leaf SVID (rotated automatically)
#   ROOTCA   — the trust bundle it validates peers against
istioctl proxy-config secret -n payments deploy/checkout

# Expected output: 2 dynamic active secrets, both Valid. If "default"
# is in state Warming or Stale, the SDS dance with istiod is failing.
ztunnel debug · what identity ztunnel is servingambient mode · command name varies
# Ambient mode: each pod's identity is held by the local ztunnel,
# not by a sidecar. Hit ztunnel's admin endpoint to see the SVIDs
# it currently has and which workloads they map to.
kubectl -n istio-system exec ds/ztunnel -- \
  curl -s http://localhost:15000/config_dump \
  | jq '.workloads, .certificates'

# Newer ztunnel revisions expose a dedicated CLI:
#   kubectl -n istio-system exec ds/ztunnel -- ztunnel admin certs
# Check `ztunnel --help` on the binary in your cluster — the exact
# subcommand name has moved between revisions.
openssl s_client · peek at the gateway's cert from outsideno kubectl needed
# Confirm two clusters' east-west gateways chain to the same root.
for CTX in cluster-east cluster-west cluster-central; do
  IP=$(kubectl --context=$CTX -n istio-eastwest \
    get svc istio-eastwestgateway \
    -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo "--- $CTX  ($IP) ---"
  openssl s_client \
    -connect $IP:15008 \
    -servername outbound_.15008_._.eastwest-gw.istio-eastwest.svc.cluster.local \
    -showcerts < /dev/null 2>/dev/null \
    | openssl x509 -noout -issuer
done

# Every line should show the same Issuer DN — that's the root from
# RootTrustPolicy.

Negative tests — what trust rejects

The CRDs above only matter if they reject what they're supposed to reject. Each case below names which control does the work, the curl/exec to reproduce, and where ztunnel / istiod records the decision. Cluster setup assumed is the three-cluster ambient mesh from the Gloo Operator CRD reference (cluster-east, cluster-west, cluster-central) with RootTrustPolicy applied.

Area 1 · mTLS client auth Rogue workload with no SVID

Launch a curl pod in a namespace that is not ambient enrolled — it has no SPIFFE identity and no ztunnel on the wire to initiate HBONE. Then try to reach a service behind a STRICT-mTLS waypoint.

$ kubectl --context=cluster-east create ns rogue
$ kubectl --context=cluster-east -n rogue run rogue \
    --image=curlimages/curl -i --tty --rm -- \
    curl -is http://reviews.payments.svc:9080 | head -3
curl: (52) Empty reply from server

Config snippet — the PeerAuthentication that makes plaintext a non-starter:

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata: { name: default, namespace: payments }
spec: { mtls: { mode: STRICT } }

Ztunnel log on the receiving node — connection dropped before any L7 byte:

$ kubectl --context=cluster-east -n istio-system logs ds/ztunnel \
    --tail=2 | jq -c .
{"level":"info","msg":"connection refused: peer is not part of the mesh",
 "src.ip":"10.244.3.17","dst.workload":"reviews",
 "dst.service":"payments/reviews","reason":"no_hbone_no_identity"}

Area 1 · SPIFFE-keyed AuthZ Workload from a denied namespace

Both source and destination are ambient (both have SVIDs) but an AuthorizationPolicy on the destination filters on source.principals. Place a curl pod in marketing instead of payments and call reviews.

$ kubectl --context=cluster-east -n marketing exec deploy/sleep -- \
    curl -is http://reviews.payments.svc:9080/healthz | head -3
HTTP/1.1 403 Forbidden
content-length: 19
content-type: text/plain

RBAC: access denied

Config snippet — only the checkout SA in payments can reach reviews; anything else, including a peer in the same mesh, is rejected by SPIFFE URI:

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata: { name: reviews-only-checkout, namespace: payments }
spec:
  selector: { matchLabels: { app: reviews } }
  action: ALLOW
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/payments/sa/checkout"]

Waypoint access log — the SPIFFE URI of the caller is the only thing that drove the decision, no header spoof would change it:

$ kubectl --context=cluster-east -n payments logs deploy/payments-waypoint \
    --tail=1 | jq -c '{src:.source.principal, decision:.rbac.decision, policy:.rbac.matched}'
{"src":"spiffe://cluster.local/ns/marketing/sa/sleep",
 "decision":"deny","policy":"reviews-only-checkout"}

Area 1 · Trust domain SVID signed by a different root

Pretend a fourth cluster (cluster-rogue) is enrolled with its own istiod and its own self-signed root — i.e. the operator never ran RootTrustPolicy for it. Its ztunnel tries to open an HBONE tunnel to cluster-east's east-west gateway. The handshake fails before any payload byte:

# From cluster-rogue, attempting to reach cluster-east:
$ EAST_IP=$(kubectl --context=cluster-east -n istio-eastwest \
    get svc istio-eastwestgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ openssl s_client -connect $EAST_IP:15008 \
    -cert /etc/certs/rogue-svid.pem -key /etc/certs/rogue-key.pem \
    -CAfile /etc/certs/rogue-root.pem 2>&1 | head -8
CONNECTED(00000003)
depth=2 O = istio.example, CN = Rogue Root CA
verify error:num=19:self-signed certificate in certificate chain
verify return:0
SSL_connect:error in SSLv3/TLS read server certificate verify (alert 48 — unknown_ca)

Config snippet — the trust bundle that cluster-east's ztunnel uses to validate peers is built from the shared root pinned by RootTrustPolicy. Any cert chain that doesn't terminate in that bundle is unknown_ca:

apiVersion: admin.gloo.solo.io/v2
kind: RootTrustPolicy
metadata: { name: root-trust-policy, namespace: gloo-mesh }
spec:
  config:
    mgmtServerCa:
      generated: {}                     # one root, pinned cluster-wide
    autoRestartPods: true
    intermediateCertOptions:
      ttlDays: 365

Ztunnel log on the receiver side — note no src.workload, because the handshake never reached the HBONE inner header:

$ kubectl --context=cluster-east -n istio-system logs ds/ztunnel \
    --tail=1 | jq -c .
{"level":"warn","msg":"tls handshake failed",
 "src.ip":"203.0.113.14","reason":"x509: certificate signed by unknown authority",
 "phase":"hbone_inbound"}

Area 4 · Outbound mTLS Upstream presents an unverified cert

Now flip direction. The waypoint in payments is configured to mTLS to an external upstream (an enterprise API outside the mesh) using a specific CA bundle. The upstream rotates its leaf to one signed by a CA not in that bundle. The egress call fails closed:

$ kubectl --context=cluster-east -n payments exec deploy/checkout -- \
    curl -is https://billing.partner.example.com/charge | head -3
HTTP/1.1 503 Service Unavailable
x-envoy-upstream-service-time: 7
content-length: 95

upstream connect error or disconnect/reset before headers. reset reason: connection failure

Config snippet — the DestinationRule pins the upstream's CA. Cert rotation outside that bundle = fail-closed, no fallback to system roots:

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata: { name: billing-partner, namespace: payments }
spec:
  host: billing.partner.example.com
  trafficPolicy:
    tls:
      mode: MUTUAL
      clientCertificate: /etc/certs/partner-client.pem
      privateKey:        /etc/certs/partner-client-key.pem
      caCertificates:    /etc/certs/partner-ca-bundle.pem
      sni:               billing.partner.example.com

Waypoint log — the response_code_details name the exact handshake failure, distinct from a TCP reset:

$ kubectl --context=cluster-east -n payments logs deploy/payments-waypoint \
    --tail=1 | jq -c '{up:.upstream_host, code:.response_code, details:.response_code_details}'
{"up":"billing.partner.example.com:443","code":503,
 "details":"upstream_reset_before_response_started{remote_reset|TLS_error:|268435581:SSL_routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED}"}

Trust & identity reference

Every moving part in the identity chain, and which cluster it lives in.

Resource Group API What it does Lives where
RootTrustPolicy trust admin.gloo.solo.io/v2 Top-level CRD that pins the shared root, names the issuer (generated / cert-manager / Vault), and sets rotation policy. mgmt cluster · namespace gloo-mesh
CASource source admin.gloo.solo.io/v2 Abstracts where the CA bytes come from — Secret, cert-manager ClusterIssuer or Vault PKI. Referenced by RootTrustPolicy. mgmt cluster · namespace gloo-mesh
IdentityProvider identity admin.gloo.solo.io/v2 Pins the SPIFFE trustDomain and per-cluster name. Shapes the SAN URI on every issued SVID. mgmt cluster · namespace gloo-mesh
cacerts Secret artifact v1/Secret The per-cluster intermediate cert + signing key + root chain that istiod-gloo loads as its SPIFFE CA. each workload cluster · istio-system
SPIFFE SVID artifact X.509 + SAN URI Per-workload identity cert. SAN URI = spiffe://<trust-domain>/ns/<ns>/sa/<sa>. Authorization policies key on this. in-memory · held by sidecar or ztunnel
gloo-mesh-mgmt-server component Signs a per-cluster intermediate (or asks cert-manager / Vault to do so) and ships it to each agent over the relay channel. mgmt cluster
gloo-mesh-agent component Receives the intermediate over relay and writes it as cacerts in istio-system; restarts pods if autoRestartPods is true. each workload cluster
istiod-gloo component Mounts cacerts, becomes the SPIFFE CA, services SDS / CSR requests from local ztunnels (and sidecars). each workload cluster · istio-system
ztunnel component Per-node L4 proxy. Holds each local workload's SVID, presents it in HBONE handshakes, validates peer certs against the trust bundle. each workload cluster · DaemonSet

Where to go from here

Read this next to the Gloo Operator CRD map — that's where RootTrustPolicy, CASource and IdentityProvider sit alongside the lifecycle CRDs that put Ambient on the cluster in the first place. And if you want to see what the SVID earns its keep doing once it's on the wire, the HBONE east-west reference picks up where this page leaves off.

Upstream references worth bookmarking: