
A myth versus reality examination of service mesh mTLS for platform and security architects, built from Istio 1.31, Linkerd 2.20, Kubernetes 1.37, SPIFFE, Google Cloud, Microsoft and AWS documentation reviewed in October 2026. It maps the identity inside mesh certificates, the plaintext and waypoint paths that avoid it, certificate lifetimes, pod certificates and the App Mesh retirement, and ends with in-cluster tests and an order of operations.
At a glance
Key findings
- Istio sidecar and ambient modes, Google Cloud Service Mesh and Linkerd all accept plaintext from clients outside the mesh by default, and Istio's migration task says permissive mode applies no authentication or authorization checks to plaintext by default. [4][5][6][2][13]
- A mesh certificate identifies a service account in a namespace and trust domain, not a user or process, and Istio's own guidance says mutual TLS provides authentication, not authorization. [9][2][3]
- In ambient mode a waypoint presents its own identity to the destination, and out-of-mesh clients, sidecars, gateways and mismatched pod-IP traffic skip it; only an ALLOW policy that admits just the waypoint's identity stops those requests reaching the workload without layer 7 policy. [5][7][18]
- Istio, Linkerd and Kubernetes pod certificates all default to 24-hour workload certificates, while documented ceilings run from one day for Kubernetes built-in signers, none of which ships yet, to 90 days in Istio and 91 days for other pod certificate signers. [21][2][22][24]
- AWS App Mesh support ended on September 30, 2026; AWS's September 2024 migration comparison said the mTLS peer authentication App Mesh offered was not yet available in ECS Service Connect, and the current Service Connect TLS page still describes no client certificates. [25][26][23]
What one mesh handshake verifies
Mesh mTLS proves less about the caller than the name suggests. When two Istio sidecars or two Linkerd proxies complete a handshake, the server side learns that the peer proxy held the private key for a certificate the mesh's certificate authority signed, and that the certificate names a Kubernetes service account in a namespace. The client side checks the server's certificate against the identity it expected for that destination. The bytes between the two proxies are encrypted. That is the whole of what the handshake establishes. [1][2]
It says nothing about which person or upstream request caused the call, which container or process in the pod wrote the bytes, or whether this caller should be allowed to perform this operation on this record. Istio's security guidance states the limit directly: mutual TLS provides authentication, not authorization, so anything holding a valid certificate can still reach a service until an authorization policy says otherwise. [3]
That protection also covers only the connections that actually travel through the tunnel, and the defaults leave several that do not. Istio in sidecar and ambient mode, Google Cloud Service Mesh and Linkerd all accept plaintext from clients outside the mesh until someone changes a setting. Kubelet health probes and ports excluded from traffic capture never enter the tunnel. In ambient mode a waypoint proxy presents its own identity to the destination, and clients outside the mesh skip the waypoint entirely. [4][5][6][2][7]
The sections that follow take one widely held belief at a time and test it against current documentation: Istio 1.31, Linkerd 2.20, Google Cloud Service Mesh with Istio APIs, the AKS Istio add-on, Kubernetes 1.37 pod certificates and the AWS App Mesh retirement, all reviewed on October 9, 2026. Istio 1.31 was released on August 27, 2026, and 1.29 and 1.30 remain supported. The sequence below follows one sidecar-mode request and marks where each identity check happens. [8]
Four checks on one sidecar request, none of them about the user
The handshake binds the connection to a service account; only the authorization step decides whether the request may proceed. [1][9]

Source. Istio security concepts (identity provisioning, mutual TLS authentication, principals) and Authorization Policy reference, Istio 1.31 documentation reviewed October 9, 2026. [1][9]
Method. Message order transcribed from the cited Istio documentation for sidecar mode and condensed into short labels. Certificate rotation, request authentication and telemetry are left out.
Accessible table and figure data
| From | To | Message |
|---|---|---|
| Client sidecar | istiod | CSR with the pod's credentials |
| istiod | Client sidecar | Certificate for namespace and service account |
| istiod | Client sidecar | Secure naming map: identities allowed per service |
| Client app | Client sidecar | Plaintext request, redirected inside the pod |
| Client sidecar | Server sidecar | mTLS handshake; client checks server identity against the map |
| Server sidecar | Server sidecar | Read peer principal, apply AuthorizationPolicy |
| Server sidecar | Server app | Forward over localhost if allowed |
| From | To | Message |
|---|---|---|
| Client sidecar | istiod | CSR with the pod's credentials |
| istiod | Client sidecar | Certificate for namespace and service account |
| istiod | Client sidecar | Secure naming map: identities allowed per service |
| Client app | Client sidecar | Plaintext request, redirected inside the pod |
| Client sidecar | Server sidecar | mTLS handshake; client checks server identity against the map |
| Server sidecar | Server sidecar | Read peer principal, apply AuthorizationPolicy |
| Server sidecar | Server app | Forward over localhost if allowed |
The certificate names a service account, not a caller
A common reading of mesh telemetry is that the certificate tells the server which service called. What it carries is narrower. In Istio the identity in a workload certificate is the Kubernetes service account, and authorization policies refer to it as a principal of the form <trust domain>/ns/<namespace>/sa/<service account>, for example cluster.local/ns/default/sa/productpage. Linkerd binds each proxy certificate to the pod's ServiceAccount, and its tooling prints identities as service account and namespace. Google's managed certificate authority for Cloud Service Mesh writes the project ID, the GKE namespace and the service account name into each certificate. [1][9][2][10][6]
A service account is a label shared by everything that runs under it. It follows that every replica of a Deployment, every Job that sets the same serviceAccountName and every container in those pods presents the same certificate identity. Istio's security guidance adds that there is minimal security boundary between an application and its sidecar: they share the pod's network and process namespaces, and the application can remove redirection rules or replace the proxy. The boundary Istio does describe is narrower: a client may not bypass another pod's sidecar. [3]
Who can obtain the certificate decides what it is worth. An Istio agent generates a private key and sends a certificate signing request with the pod's credentials to istiod, whose CA validates those credentials before signing. A Linkerd proxy sends its service account token with the request for the same reason. In ambient mode the node's ztunnel requests certificates on behalf of each service account with a pod on that node, and the CA must refuse identities that are not running there; Istio calls that check critical to stopping one compromised node from compromising the whole mesh. The consequence is that permission to create a pod with a given serviceAccountName in a namespace amounts to permission to obtain that mesh identity, and the Kubernetes RBAC around pod creation belongs in the mesh's trust model. [1][2][7]
SPIFFE supplies the vocabulary. A SPIFFE ID is a URI made of a trust domain and a path whose meaning is left to the administrator, and the standard warns that trust domain names are self-registered, with no guarantee of global uniqueness. Istio's ambient documentation says SPIFFE identities identify the workloads on each side of an HBONE connection, and Istio's examples use the trust domain cluster.local. A reasonable reading is that a principal string proves little on its own: two clusters that share a root of trust and both keep cluster.local issue principals nobody can tell apart. Istio 1.31 added trustDomains and notTrustDomains to AuthorizationPolicy so that a rule can match the trust domain taken from the peer certificate. [11][7][9][12]
Joining the mesh does not switch on enforcement
Teams often assume that once workloads are in the mesh, they accept only mTLS. Every mesh covered here starts in the opposite state, because enforcement on day one would break clients that have not joined yet. Istio peer authentication offers PERMISSIVE, STRICT and DISABLE, and a mesh-wide policy with no mode set uses PERMISSIVE, which accepts mTLS and plaintext side by side; an UNSET mode inherits from its parent and is otherwise treated as PERMISSIVE. Istio's migration task spells out the consequence: in permissive mode, plaintext traffic receives no authentication or authorization checks by default. [1][4][13]
Ambient mode keeps that default. The receiving ztunnel accepts both HBONE and plaintext connections, plaintext from an out-of-mesh source arrives with no peer identity, and only STRICT or an authorization rule that requires an identity turns it away. DISABLE is not supported in ambient mode, and policies that set it are ignored, because ztunnel always tunnels mesh traffic. [7][5][4]
The managed and alternative meshes behave the same way. Google Cloud Service Mesh turns on auto mTLS, under which a client sidecar sends mTLS to workloads with sidecars and plaintext to workloads without them, while services accept both; Google recommends migrating services to accept only mTLS. Linkerd requires mTLS between two meshed proxies but accepts plaintext from non-meshed sources by default. Its cluster-wide default inbound policy is all-unauthenticated, and the stricter all-authenticated, cluster-authenticated and deny values have to be chosen. A pod's default policy is fixed when its proxy starts, so changing the annotation later has no effect until the pod is recreated. Microsoft's overview of the AKS add-on describes it as built on open-source Istio and states no different default, so the safe assumption is Istio's permissive behavior until a test shows otherwise. [6][2][14][15]
Permissive mode does more than admit plaintext; it changes what authorization rules mean. The source fields principals and namespaces, their negative forms and the conditions source.principal and source.namespace are all read from the peer certificate, so a plaintext request presents empty values. Istio's advisory ISTIO-SECURITY-2021-004, which covers every release from 1.5 onward, shows the effect: an ALLOW rule with notNamespaces: ["foo"] admits a plaintext request that came from foo, and a DENY rule on namespaces: ["foo"] fails to block one. The remedy is STRICT, or rules that explicitly reject an empty namespace. [16][1]
| Mesh | Plaintext accepted by default | Setting that rejects it |
|---|---|---|
| Istio sidecar mode | Yes, PERMISSIVE | PeerAuthentication STRICT |
| Istio ambient mode | Yes, ztunnel accepts both | STRICT, or ALLOW rules requiring a principal |
| Google Cloud Service Mesh | Yes, under auto mTLS | Migrate services to mTLS only |
| Linkerd 2.20 | Yes, from non-meshed sources | Default policy all-authenticated or deny |
| AKS Istio add-on | No separate default documented | Istio PeerAuthentication, sidecar mode only |
Three ways plaintext reaches a permissive server
Only the tunnel carries a peer identity; unmeshed callers, node probes and excluded ports arrive without one. [4][17][2]

Source. Conceptual illustration based on Istio PeerAuthentication, ambient NetworkPolicy and security guidance pages and Linkerd automatic mTLS documentation, reviewed October 9, 2026. [4][17][3][2]
Method. Conceptual hand-authored illustration. It simplifies sidecar and ambient data paths into one server pod and does not show gateways or waypoints.
Accessible table and figure data
| Element | What it represents |
|---|---|
| Meshed client | Pod with a sidecar or in ambient mode |
| Doubled tunnel | mTLS between proxies, peer identity checked |
| Proxy (PERMISSIVE) | Server proxy accepting mTLS and plaintext |
| Unmeshed pod | Client with no proxy, sends plaintext |
| Kubelet probe | Node health check, no mesh certificate |
| Excluded port | Capture exclusion or skip port, bypasses proxy |
| No peer identity | Policy sees an empty principal |
| Element | What it represents |
|---|---|
| Meshed client | Pod with a sidecar or in ambient mode |
| Doubled tunnel | mTLS between proxies, peer identity checked |
| Proxy (PERMISSIVE) | Server proxy accepting mTLS and plaintext |
| Unmeshed pod | Client with no proxy, sends plaintext |
| Kubelet probe | Node health check, no mesh certificate |
| Excluded port | Capture exclusion or skip port, bypasses proxy |
| No peer identity | Policy sees an empty principal |
STRICT still leaves traffic outside the tunnel
Switching to STRICT is sometimes treated as closing every plaintext path. It closes the one it governs: inbound connections to a proxy that should have arrived through the tunnel. Several kinds of traffic never reach that decision, and kubelet health probes are the most common. The kubelet runs on the node, holds no mesh certificate and sends plaintext. Linkerd's tap output marks these requests tls=no_tls_from_remote, and Linkerd authorizes probes automatically unless a Server has HTTPRoutes or GRPCRoutes attached. Istio ambient translates the source of probe packets that provably come from the local node to 169.254.7.127 and fd16:9254:7127:1337:ffff:ffff:ffff:ffff, and its policy enforcement ignores them as unsecured probe traffic. A NetworkPolicy rule that admits those addresses is therefore admitting node-originated plaintext by design. [10][14][17]
Capture exclusions come next. Istio sidecar redirection handles TCP only, so UDP and ICMP are never captured; inbound capture skips port 22 and the sidecar's own ports, and annotations such as traffic.sidecar.istio.io/excludeInboundPorts and excludeOutboundPorts widen the gap pod by pod. Linkerd's skip ports bypass the proxy entirely. A workload-scoped PeerAuthentication can also set portLevelMtls to DISABLE for one workload port, a legitimate migration tool that is easy to forget once the migration ends. [3][2][4]
Then there is the pod itself. Because an application can alter its own sidecar's redirection, Istio's documentation says it is not secure to rely on all traffic being captured unconditionally. Its recommended defense is to layer Kubernetes NetworkPolicy under mesh policy and to route egress through an egress gateway that NetworkPolicy makes mandatory. It warns that outboundTrafficPolicy: REGISTRY_ONLY on its own is a best-effort guard against accidental dependencies, not a security boundary. [3]
Name resolution is the last gap. Istio's secure naming check lets a client reject a server whose certificate identity is not authorized to run the destination service, which defeats a forged server holding a valid certificate for some other identity. For traffic that is not HTTP or HTTPS, the documentation says secure naming does not protect against DNS spoofing: a TCP connection carries no host information, the proxy can only route on the destination address, and a spoofed address can steer the connection to whatever service holds it. Istio authorization also cannot inspect the first bytes of server-first TCP protocols, which reach the client before any check runs. [1][3]
Ambient mode changes who the destination sees
Ambient mode is often described as the sidecar model with fewer proxies. At layer 4 the identity model is close. Ztunnel holds a separate certificate for each service account with a pod on its node, never uses its own identity for workload-to-workload mTLS, and encrypts and decrypts HBONE traffic inside the source and destination pods' network namespaces, on port 15008. The receiving ztunnel enforces AuthorizationPolicy, and layer 4 rules behave as they do in sidecar mode. [7][17][5]
The differences start at layer 7. Ztunnel cannot evaluate HTTP attributes, so a policy with a method, path or request principal that lands on a ztunnel fails safe and denies the connection. L7 policy runs only at a waypoint, and a waypoint does not impersonate the client. Once traffic passes through one, the destination ztunnel sees the waypoint's service account, which is named after its Gateway. A destination rule that allowed cluster.local/ns/shop/sa/frontend now meets a different principal, which is why Istio's guidance moves source-identity policy onto the waypoint. [5][18]
The waypoint is also easy to skip. A workload outside the mesh knows nothing about waypoints and sends straight to the destination, and the data plane page says traffic from sidecars and gateways does not pass through waypoints either, with awareness planned for a future release. Ingress gateway traffic uses the destination's waypoint only when the Service or Namespace carries istio.io/ingress-use-waypoint and istiod runs with ENABLE_INGRESS_WAYPOINT_ROUTING, which defaults to false. Ztunnel routes around the waypoint, instead of failing, when the named waypoint does not exist or has no address, or when the traffic type does not match, such as a request to a pod IP when the waypoint handles only service traffic. In each case the L7 policy never runs. [7][18]
The documented remedy makes the waypoint mandatory: an ALLOW policy selected by workload labels, so that ztunnel enforces it, which admits only the waypoint's identity. It holds when the waypoint is unavailable and when a client dials the pod directly, and because it requires a peer principal it also rejects plaintext from outside the mesh. Maturity varies by feature: multi-network multicluster ambient reached beta in Istio 1.29 on February 16, 2026, shifting traffic between waypoints is alpha in 1.31, and the AKS Istio add-on does not yet support ambient mode. [18][7][19][15]
# Example: only the waypoint may reach app: orders in namespace shop.
# A selector (not targetRefs) makes ztunnel enforce this at layer 4.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: require-orders-waypoint
namespace: shop
spec:
selector:
matchLabels:
app: orders
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/shop/sa/orders-waypoint
An authenticated peer is not an authorized caller
With STRICT in place, it is tempting to believe only the right callers get through. Istio allows every request to a workload that has no ALLOW policy, in sidecar and ambient mode alike, so a fully encrypted, fully authenticated mesh with no authorization policies admits any workload holding a valid certificate. When several actions apply, CUSTOM is evaluated first, then DENY, then ALLOW, while AUDIT only marks requests for logging. Istio recommends starting from a default-deny policy so that a missing rule produces a rejection rather than an exposure. [9][5][3]
The source fields map onto what the certificate carries. principals and namespaces require mTLS. serviceAccounts takes <namespace>/<serviceaccount> pairs, allows no wildcards and cannot be combined with those two. trustDomains and notTrustDomains, new in Istio 1.31, match the trust domain from the peer certificate by exact, prefix, suffix or presence. A policy built from these fields answers one question well, which workload identity may open this connection or send this request, and nothing beyond it. [9][12]
The end user is a different identity that arrives by a different route. Istio's request authentication validates a JWT and exposes its issuer and subject as request.auth.principal, matched with requestPrincipals, but a request that carries no token passes request authentication by default; only an authorization rule that requires a request principal rejects it. At ingress the difference is easy to see. Istio's namespace-isolation example admits the ingress gateway's service account, cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account, alongside workloads in the namespace. The example implies that, to the backend, every external request arrives with the gateway's certificate, so whatever the backend learns about the original user has to come from a token or header the gateway forwarded. [1][9][20]
Linkerd builds the same separation from different objects. A Server selects a port on a set of pods, MeshTLSAuthentication names client identities, NetworkAuthentication names address ranges, and an AuthorizationPolicy joins a target to an authentication. A Server's accessPolicy defaults to deny, and an audit value logs would-be denials during rollout. For decisions the mesh cannot express, Istio's CUSTOM action delegates to an external authorizer, with at most one provider per workload, and that extension cannot override native ALLOW and DENY results. Neither mesh knows whether this customer may read that invoice. That rule belongs in the application, exactly as it does for Cloud Run services that authenticate each other with IAM. [14][9]
# Example for Istio 1.31 sidecar mode in namespace shop (placeholder names).
# 1. Reject plaintext.
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: shop
spec:
mtls:
mode: STRICT
---
# 2. Deny everything that no ALLOW policy matches.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-nothing
namespace: shop
spec: {}
---
# 3. Let only the frontend service account read orders.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: orders-read-from-frontend
namespace: shop
spec:
selector:
matchLabels:
app: orders
action: ALLOW
rules:
- from:
- source:
serviceAccounts: ["shop/frontend"]
to:
- operation:
methods: ["GET"]
paths: ["/orders/*"]
What each layer proves and what it leaves open
Each layer answers one question; the user and the operation are only covered if the top two layers have rules. [3][9]

Source. Conceptual model by the editors, based on Istio security concepts, security guidance and the Authorization Policy reference, and Linkerd authorization policy documentation. [1][3][9][14]
Method. Conceptual layering of documented behavior. Each layer depends on the one below it; the model leaves out gateways and application code.
Accessible table and figure data
| Layer | Proves | Leaves open |
|---|---|---|
| Transport encryption | the hop between proxies is private | whether every hop used the tunnel |
| Peer identity | peer holds a key for a namespace and service account | which process, replica or user |
| Workload authorization | this identity may reach this route | whether the action suits the record |
| Request and user authorization | a validated JWT subject may act | anything, if no rule needs a token |
| Layer | Proves | Leaves open |
|---|---|---|
| Transport encryption | the hop between proxies is private | whether every hop used the tunnel |
| Peer identity | peer holds a key for a namespace and service account | which process, replica or user |
| Workload authorization | this identity may reach this route | whether the action suits the record |
| Request and user authorization | a validated JWT subject may act | anything, if no rule needs a token |
Short lifetimes shrink a stolen key's window without closing it
Short-lived certificates are often credited with making a stolen key harmless. The defaults are indeed short. Istio's CA issues workload certificates for 24 hours when the request does not ask for a lifetime, Linkerd proxy certificates last 24 hours, and a Kubernetes podCertificate projection asks for 24 hours when maxExpirationSeconds is not set. Amazon ECS Service Connect rotates its certificates every five days by default, and AWS recommends running AWS Private CA in short-lived mode for it. Google's Cloud Service Mesh security page states no workload certificate lifetime, only that shorter refresh intervals can be configured. [21][2][22][23][6]
Defaults are not ceilings. Istio's MAX_WORKLOAD_CERT_TTL is 2160 hours, or 90 days. A third-party pod certificate signer may be asked for up to 91 days, while signers built into Kubernetes are limited to one day, although none ships in core yet, and Service Connect's short-lived mode allows seven days. It follows that a copied key and certificate keep working against any peer that trusts the issuer until they expire, unless something checks revocation. Service Connect documents no revocation and relies on frequent rotation instead, and Istio added certificate revocation list support to ztunnel in 1.29, for plugged-in certificate authorities only. [21][22][24][23][19]
The signing root carries more weight than any leaf. Istio's self-signed root defaults to 87600 hours, ten years, and istiod checks it for rotation every hour. Linkerd's default install creates a trust anchor that expires after 365 days and must then be rotated by hand, a task Linkerd itself calls non-trivial, plus an issuer certificate and key that expire after a year; clusters joined by Linkerd multicluster must share an explicitly provided anchor. Linkerd stores the issuer key in a Secret that only the identity component's service account can read. Whoever can read a signing key can, in effect, mint any identity in its trust domain, so the RBAC on that Secret, or the access policy on the external CA behind it, deserves more review than the leaf lifetime does. [21][2]
| Platform | Workload certificate | Signing root or anchor |
|---|---|---|
| Istio 1.31, istiod CA | 24 h default, 2160 h maximum | Self-signed root 87600 h |
| Linkerd 2.20 | 24 h, rotated automatically | Trust anchor 365 days, issuer 1 year |
| Kubernetes 1.37 podCertificate | 24 h default, 1 h minimum, 91 days maximum (1 day for built-in signers) | Set by the signer you deploy |
| ECS Service Connect | Rotated every 5 days by default, 7 days maximum in short-lived mode | The AWS Private CA you provide |
| Google Cloud Service Mesh | Not stated | Managed CA or CA Service |
Defaults agree on one day; ceilings run to three months
Istio, Linkerd and pod certificates all default to 24 hours, but the documented maximums run from 1 to 91 days. [21][2][22][23]

Source. Istio pilot-discovery environment reference (MAX_WORKLOAD_CERT_TTL 2160h), Kubernetes projected volumes documentation (maxExpirationSeconds at most 7862400, built-in signers at most 86400), Amazon ECS Service Connect TLS documentation (short-lived maximum seven days) and the Kubernetes v1.37 pod certificates announcement (no signers in core yet), reviewed October 9, 2026 and rechecked October 10, 2026. [21][22][23][24]
Method. Calculated: days equal hours divided by 24 or seconds divided by 86400; the ECS value is copied in days. Linkerd documents a 24-hour certificate but no configurable ceiling, and Google Cloud Service Mesh states no lifetime, so both are excluded.
Accessible table and figure data
| Platform and setting | Documented ceiling in days |
|---|---|
| Kubernetes built-in signers (none shipped yet) | 1 |
| ECS Service Connect short-lived CA | 7 |
| Istio maximum workload certificate TTL | 90 |
| Kubernetes podCertificate, other signers | 91 |
| Platform and setting | Documented ceiling in days |
|---|---|
| Kubernetes built-in signers (none shipped yet) | 1 |
| ECS Service Connect short-lived CA | 7 |
| Istio maximum workload certificate TTL | 90 |
| Kubernetes podCertificate, other signers | 91 |
Pod certificates are an issuance API, not a mesh
Kubernetes 1.37 is sometimes summarized as giving every pod an mTLS identity. Pod certificates, first available in 1.34, did become stable in 1.37, together with the older ClusterTrustBundle API, and the Kubernetes blog post announcing them is dated August 28, 2026. The kubelet generates a private key for each podCertificate volume source, creates a PodCertificateRequest addressed to a named signer, writes the issued chain into the container filesystem and refreshes it once the signer's beginRefreshAt time passes. The NodeRestriction admission plugin keeps one compromised node from requesting certificates for pods scheduled elsewhere, the same node check Istio requires of its ambient CA. [22][24][7]
What Kubernetes does not ship is a signer. The announcement says the project does not yet include any pod certificate signers in core, so trying the feature means installing a third-party signer. Its author's Tinycert is described as a starting point rather than a production solution, and one of its signers issues SPIFFE-compatible certificates naming the pod's namespace and service account. Any signer that Kubernetes later ships in core will issue certificates of at most 24 hours; other signers may go to 91 days. [24][22]
The API enforces nothing on the wire either. It delivers a key, a chain and trust anchors; the application, or a mesh that adopts the API, still has to require client certificates, verify them and decide what each identity may do. The application must reload the files when they change, ideally from the single credentialBundlePath file so it never pairs a new key with an old certificate. Signers receive userAnnotations copied into spec.unverifiedUserAnnotations, which the documentation tells them not to trust without verification. Istio's own support for ClusterTrustBundles is still marked experimental, behind ENABLE_CLUSTER_TRUST_BUNDLE_API. [22][1][28]
The motivation for the feature bears directly on what mTLS proves. The announcement contrasts certificates with service account JWTs, which are bearer tokens: whoever holds a copy is the identity, and every peer that receives one could replay it. A private key that stays in the pod gives proof of possession instead. Mesh mTLS gives each proxy the same property and carries the same limit, because the certificate still names a namespace and service account and proves nothing narrower about the caller. Projected tokens remain the route to cloud provider credentials. [24]
The trust domain ends at the mesh edge
Mesh identity is easy to picture as extending to whatever a service talks to. It ends where the proxies end. Linkerd states that it can enforce policy only on meshed pods and advises pairing policy with high-availability mode, which requires the proxy to be present when pods start, wherever policy is a strict requirement. Callers without a proxy, which can include batch pods, managed services and anything running in another cloud, reach a meshed service as plaintext, through a gateway, or with an identity system of their own. [14]
Gateways are where outside identity is converted, and the conversion can be loosened. Istio 1.31 implements the Gateway API AllowInsecureFallback option for client certificate validation: the gateway requests a client certificate but still admits the connection when none is presented or validation fails, and populates x-forwarded-client-cert so backends can verify for themselves. In that configuration a backend that trusts the gateway's principal without checking the header or a token holds no proof about the client at all. Egress is the mirror image; Istio's advice is an egress gateway made mandatory by NetworkPolicy, not REGISTRY_ONLY. [12][3]
Trust between clusters is configured, never inherited. Linkerd multicluster requires an explicit shared trust anchor. With its managed CA, Cloud Service Mesh fails authentication between different workload identity pools by default, and it supports identity-aware policies only across clusters in the same Google Cloud project. Multi-network multicluster ambient is still listed as beta in the Istio 1.31 feature status table. A service in another cloud that needs to call a meshed workload has a federation problem, with its own issuer, subject and audience checks. [2][6][19][28]
AWS App Mesh is the clearest case of a mesh ending outright. AWS ended support on September 30, 2026, after which the App Mesh console and resources are no longer accessible. AWS's migration guidance sends ECS users to Amazon ECS Service Connect and EKS users to Amazon VPC Lattice. Its September 24, 2024 comparison said App Mesh offered two-way peer authentication with mTLS, a feature not yet available for Service Connect, and the current Service Connect TLS page describes TLS 1.3 encryption with AWS Private CA certificates and managed rotation without describing client certificates. A reasonable reading is that teams who used App Mesh mTLS to authenticate callers need a replacement for that check, not only for routing. [25][26][23]
Prove it in your own cluster
Documentation describes defaults; only a test shows what one cluster actually does. Run these checks from a disposable namespace against harmless endpoints, and store the results with the policy revision they tested. Istio's migration task uses the same pattern: a client without a sidecar calls a service before and after STRICT is applied, and the second call fails, with curl reporting exit code 56. [13]
- Plaintext from outside: from a pod with no proxy, call every port of each protected service and expect a refused or reset connection, not an HTTP response. [13]
- Wrong identity: from a meshed pod running under a different service account in the same namespace, expect a denial; Linkerd documents HTTP 403 for HTTP traffic and a refused connection for everything else. [14]
- Waypoint bypass: with a service-scoped waypoint, dial the pod IP directly; without a require-waypoint policy the request succeeds and skips layer 7 policy. [18]
- Telemetry: on the destination side, look for
istio_requests_totaloristio_tcp_connections_opened_totalwithreporter="destination"and aconnection_security_policyother thanmutual_tls; in Linkerd, read the SECURED column oflinkerd viz edgesand thetls=field inlinkerd viz tap. [27][7][10] - Exceptions: list
portLevelMtlsentries, capture-exclusion annotations and skip ports, and give each one an owner and an end date. [4][3][2] - Advisory pattern: search authorization policies for
notNamespacesornotPrincipalson workloads that are stillPERMISSIVE. [16]
# Example read-only inventory for Istio 1.31 (needs kubectl and jq).
# Peer authentication modes, including port-level exceptions.
kubectl get peerauthentication -A -o json | jq -r '.items[] |
[.metadata.namespace, .metadata.name, (.spec.mtls.mode // "UNSET"),
((.spec.portLevelMtls // {}) | tostring)] | @tsv'
# Pods that exclude ports from sidecar capture.
kubectl get pods -A -o json | jq -r '.items[] |
select((.metadata.annotations // {}) | keys | any(test("exclude(Inbound|Outbound)Ports"))) |
[.metadata.namespace, .metadata.name] | @tsv'
# Plaintext probe from a namespace with no injection or ambient label.
# Placeholder names; point it only at a test endpoint.
kubectl run mesh-probe -n no-mesh --rm -i --restart=Never \
--image=curlimages/curl:8.10.1 -- \
curl -sS -o /dev/null -w '%{http_code}\n' \
http://orders.shop.svc.cluster.local:8080/healthz
When a mesh is not the right control
Mesh mTLS is the right tool for one job: authenticating and encrypting workload-to-workload connections inside a trust domain you operate, without changing application code. It becomes the wrong tool as soon as the question concerns something the certificate does not name. A workable rule follows from the sections above. If a decision depends only on which service account, in which namespace and trust domain, opened the connection, express it in mesh authorization. If it depends on a person, a tenant, a record or the business meaning of an action, check it at the gateway and in the application, using a token or session that the mesh merely carries.
Some callers fall outside the mesh altogether: workloads that cannot run a proxy, separate processes inside one pod, managed services, other clouds, and ECS services after App Mesh, where Service Connect's TLS documentation describes no client authentication. Give those callers an identity from their own system, such as cloud IAM or federated OIDC tokens, and use NetworkPolicy for connections that must never happen at all. Where a mesh is the right control, this order of operations gets the most from it.
- Set
STRICT, or Linkerd'sall-authenticatedordeny, one namespace at a time, and prove that plaintext from an unmeshed pod is refused. - Add default-deny authorization, allow by
serviceAccountsor MeshTLSAuthentication identities, and move caller rules onto waypoints wherever one sits in the path. - Inventory every probe path, capture exclusion, skip port and port-level exception, and give each an owner.
- Write down the trust domain and anchor: who can read the signing key, when the root or anchor expires, and which clusters share it.
- Require end-user and tenant checks in tokens and application code, and test that a request with no token is rejected.
Method and provenance
Source-led analysis of Istio, Linkerd, Kubernetes, SPIFFE, Google Cloud, Microsoft and AWS documentation, release announcements and one Istio security advisory, organized as a test of common beliefs about mesh mTLS. Sources were reviewed on October 9, 2026. An independent fact-check on October 10, 2026 re-opened every source.
No mesh, cluster or cloud account was configured or tested. Behavior is limited to the cited documentation for Istio 1.29 to 1.31, Linkerd 2.20, Kubernetes 1.37 and the named managed services as of the review date; ambient waypoint routing in particular is documented as changing between releases.
AI assistance. AI assisted research synthesis, drafting, diagram planning and visual production, with deterministic editorial checks. No personal deployment experience, independent human review or live test is claimed.
Published under the Cloud Security Desk organizational byline. Read the practitioner guide policy.
References
- Security concepts (Istio 1.31 documentation) Istio. Accessed .
- Automatic mTLS (Linkerd 2.20 documentation) Linkerd. Accessed .
- Security best practices (Istio 1.31 documentation) Istio. Accessed .
- PeerAuthentication reference (Istio 1.31) Istio. Accessed .
- Use Layer 4 security policy (Istio ambient) Istio. Accessed .
- Cloud Service Mesh security overview Google Cloud. Accessed .
- Ambient data plane (Istio architecture) Istio. Accessed .
- Supported releases (Istio) Istio. Accessed .
- Authorization Policy reference (Istio 1.31) Istio. Accessed .
- Validating your mTLS traffic (Linkerd 2.20 documentation) Linkerd. Accessed .
- The SPIFFE Identity and Verifiable Identity Document (SPIFFE-ID standard) SPIFFE project. Accessed .
- Announcing Istio 1.31.0 Istio. Published . Accessed .
- Mutual TLS migration (Istio task) Istio. Accessed .
- Authorization policy (Linkerd 2.20 documentation) Linkerd. Accessed .
- Istio-based service mesh add-on for Azure Kubernetes Service Microsoft. Accessed .
- ISTIO-SECURITY-2021-004: potential misuse of mTLS-only fields in AuthorizationPolicy with plain text traffic Istio. Published . Accessed .
- Ambient and Kubernetes NetworkPolicy (Istio ambient) Istio. Accessed .
- Configure waypoint proxies (Istio ambient) Istio. Accessed .
- Announcing Istio 1.29.0 Istio. Published . Accessed .
- Security policy examples (Istio) Istio. Accessed .
- pilot-discovery command and environment reference (Istio 1.31) Istio. Accessed .
- Projected volumes: podCertificate projected volumes (Kubernetes 1.37 documentation) Kubernetes. Accessed .
- Encrypt Amazon ECS Service Connect traffic Amazon Web Services. Accessed .
- Kubernetes v1.37: Pod Certificates and Cluster Trust Bundles Kubernetes. Published . Accessed .
- What is AWS App Mesh? (end of support notice) Amazon Web Services. Accessed .
- Migrating from AWS App Mesh to Amazon ECS Service Connect Amazon Web Services. Published . Accessed .
- Istio standard metrics Istio. Accessed .
- Feature Status (Istio 1.31 documentation) Istio. Accessed .