Open-Source Wikis

/

Istio

/

Features

/

mTLS and identity

istio/istio

mTLS and identity

Active contributors: hzxuzhonghu, costinm, ramaraochavali, keithmattix

What users do

  • Encrypt all traffic between mesh workloads with mTLS, automatically.
  • Verify the identity of the calling workload via SPIFFE.
  • Authenticate end users with JWT (RequestAuthentication).
  • Authorize calls based on identity, JWT claims, methods, and paths (AuthorizationPolicy).

CRDs

CRD Purpose Schema
PeerAuthentication Govern peer (workload-to-workload) mTLS mode security/v1beta1/peer_authentication.proto
RequestAuthentication JWT/OIDC validation for incoming requests security/v1beta1/request_authentication.proto
AuthorizationPolicy Allow/deny rules at L4/L7 security/v1beta1/authorization_policy.proto

How identity is established

sequenceDiagram
    participant Pod as App pod (with sidecar)
    participant Agent as pilot-agent
    participant Istiod
    participant CA as IstioCA

    Pod->>Pod: SA token projected to /var/run/secrets/tokens/istio-token
    Agent->>Agent: read SA token
    Agent->>Istiod: Authenticated CSR over mTLS<br/>(JWT bearer)
    Istiod->>CA: Sign with SPIFFE URI
    CA-->>Istiod: Signed cert chain
    Istiod-->>Agent: Cert + chain via SDS
    Note over Agent,Pod: Agent serves cert to Envoy via UDS<br/>Envoy uses cert in TLS handshake

The SPIFFE URI for a workload is spiffe://<trust-domain>/ns/<namespace>/sa/<serviceaccount>. The trust domain defaults to cluster.local and is configurable via MeshConfig.trustDomain.

The agent caches certs in memory and rotates ahead of expiry (default cert TTL: 24h, rotation at 50% of lifetime).

How mTLS happens on the wire

In sidecar mode:

  • Outbound: Envoy intercepts the connection, opens an mTLS connection to the destination's Envoy, and tunnels the original payload.
  • Inbound: Envoy on port 15006 terminates the mTLS, validates the peer cert, and forwards plaintext to the app.
  • Mode (STRICT, PERMISSIVE, DISABLE) is per-port and resolved by merging mesh-default + namespace + workload-level PeerAuthentication. Resolution lives in pilot/pkg/security/authn/ and is consumed by the listener compiler in networking/core/.

In Ambient mode:

  • Ztunnel terminates and originates HBONE (HTTP/2 CONNECT over mTLS). It uses the same SPIFFE identities and the same CA.
  • Mode is L4 only at this layer; L7 mTLS / per-method authn happens at the waypoint Envoy.

AuthorizationPolicy compilation

AuthorizationPolicy is compiled into Envoy RBAC filter config. Compilers:

  • Sidecar / waypoint Envoy: pilot/pkg/security/authz/builder/. Produces envoy.filters.http.rbac and envoy.filters.network.rbac config.
  • Ztunnel (L4 only): pilot/pkg/serviceregistry/kube/controller/ambient/authorization.go. Produces the Authorization proto in pkg/workloadapi/security/.

Selector resolution (which policies apply to a given proxy) is in pilot/pkg/model/authorization.go. The selector matches workload labels; targetRef (Gateway-API style) takes precedence over selector.

Order of operations on a request:

  1. Layered-deny — DENY rules at workload, namespace, and root namespace levels (AuthorizationPolicy with action: DENY).
  2. Allow — at least one matching ALLOW rule must permit.
  3. Audit — separate ALLOW-with-AUDIT mode emits logs without affecting the decision.

Custom action types (CUSTOM) integrate ext-authz services.

RequestAuthentication

RequestAuthentication configures JWT validation per workload. The compiler builds Envoy envoy.filters.http.jwt_authn config. Behavior:

  • Tokens are validated against the configured issuer + jwks (or jwksUri).
  • Validated claims become available to AuthorizationPolicy via request.auth.*.
  • A request without a valid token still enters the proxy chain — it is up to AuthorizationPolicy to deny it.

The compiler is in pilot/pkg/security/authn/. JWT keys are cached in istiod and refreshed periodically.

Trust bundle and cross-trust-domain

pilot/pkg/trustbundle/ aggregates roots from:

  • The local CA.
  • MeshConfig.caCertificates.
  • Cert-manager ClusterTrustBundle (when enabled).
  • Federated trust domains (multi-cluster mesh federation).

The aggregated bundle is shipped to proxies via SDS as ROOTCA. Cross-trust-domain auth means a workload from cluster1.local can present its cert to a workload that trusts only cluster2.local's root, provided both roots are in the bundle. The SPIFFE URI's trust-domain field stays as-issued.

Trade-offs

  • STRICT vs PERMISSIVE — strict mTLS rejects any plaintext peer, including kubelet probes. The probe rewriter in injection (pkg/kube/inject/app_probe.go) avoids this for k8s probes, but other plaintext paths (port-forwarded debugging, third-party operators) require namespace-level PeerAuthentication overrides.
  • mTLS cost — with sidecars, the encryption/decryption happens in Envoy. With Ambient, ztunnel does it. Either way, expect ~10-30% latency overhead and CPU cost vs cleartext.
  • Cert rotation visibility — when a CA rolls, every workload must re-sign within MAX_WORKLOAD_CERT_TTL. Slow rollouts can leave a long tail of invalid certs. The citadel_server_cert_chain_expiry_timestamp metric is the canary.

Key source files

File Purpose
pilot/pkg/security/authn/policy_applier.go mTLS + JWT compilation (sidecar/waypoint)
pilot/pkg/security/authz/builder/builder.go RBAC compilation (sidecar/waypoint)
pilot/pkg/serviceregistry/kube/controller/ambient/authorization.go RBAC compilation (ztunnel L4)
pilot/pkg/model/authorization.go Policy selector resolution
pilot/pkg/trustbundle/trustbundle.go Aggregated root certs
security/pkg/pki/ca/ca.go CSR signer
security/pkg/server/ca/server.go CSR gRPC server
pilot/pkg/keycertbundle/watcher.go Live root reload

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

mTLS and identity – Istio wiki | Factory