The Mesh Security Promise
Service meshes solve a genuine security problem in microservice architectures: without them, service-to-service traffic inside a Kubernetes cluster travels in plain text. Any compromised pod in the cluster can sniff traffic from other services using standard network tools. Service meshes inject a sidecar proxy (Envoy, in Istio's case) into every pod and use that proxy to terminate and re-establish mTLS for every connection. The result is encrypted, mutually authenticated communication between every service pair, without any application code changes.
This promise is compelling enough that many organisations deploy Istio specifically to claim "zero-trust networking" in compliance questionnaires and architecture reviews. The problem is that the gap between "Istio is installed" and "all traffic is mTLS" is significant, and the default Istio installation does not close that gap automatically.
PERMISSIVE Mode: The Default That Breaks Everything
Istio's PeerAuthentication resource controls whether mTLS is required. The default mode is PERMISSIVE β the sidecar accepts both mTLS and plain-text traffic. This default exists for migration convenience: it allows non-mesh services to communicate with mesh services while the mesh is being rolled out. However, many clusters remain in PERMISSIVE mode indefinitely because rolling out STRICT mode can break services that were accidentally left without sidecars.
PERMISSIVE is not mTLS: In PERMISSIVE mode, an attacker's pod that has no Istio sidecar can make plain-text HTTP calls to any mesh service. The receiving sidecar accepts the plain-text connection and forwards it to the service. The service receives the request with the source identity missing or spoofable. AuthorizationPolicy rules that check the source principal will evaluate against an empty principal, potentially matching wildcard rules.
Sidecar Bypass Techniques
Istio's sidecar injection relies on a mutating admission webhook that modifies pod manifests to add the Envoy sidecar container and the required initContainer that sets up iptables rules for traffic interception. Bypassing the sidecar means traffic travels outside the mesh β unencrypted and unauthenticated β while the operator believes it is protected.
Annotation-Based Opt-Out
The annotation sidecar.istio.io/inject: "false" on a pod disables sidecar injection. Any developer who can deploy pods in a mesh-enabled namespace can opt their workload out of the mesh entirely. If AuthorizationPolicy rules check the source principal for authorisation, a pod without a sidecar has no principal and may be treated differently than intended.
Direct IP Communication
Istio's traffic interception uses iptables to redirect all inbound and outbound traffic through the Envoy sidecar. An attacker with the NET_ADMIN capability or CAP_NET_RAW can modify iptables rules to bypass the sidecar and communicate directly with pod IPs, bypassing both mTLS and any AuthorizationPolicy enforcement that relies on sidecar-mediated identity.
sidecar.istio.io/inject: falseAuthorizationPolicy Gaps
Istio's AuthorizationPolicy is the mechanism for enforcing which services can communicate with which. It operates on the SPIFFE identity of the client service account. But several common misconfigurations silently disable enforcement: policies with action: ALLOW and no from clause allow all sources; policies that use source.namespaces rather than source.principals can be spoofed by injecting a pod into the allowed namespace; and the absence of a DENY default policy means that traffic not matching any ALLOW policy is allowed β the opposite of what most operators expect.
Default allow: Istio's default behaviour in the absence of any AuthorizationPolicy is to allow all traffic. Adding an ALLOW policy to one service does not implicitly deny traffic to other services. You must explicitly add policies to every service, or set a mesh-wide default deny and selectively allow traffic.
Control Plane Attacks
Istiod, the Istio control plane, issues SPIFFE certificates to all sidecars and distributes configuration to the Envoy proxies. Istiod runs as a pod in the istio-system namespace and has cluster-wide read access to Kubernetes resources. Compromising Istiod means issuing fraudulent certificates to attacker-controlled identities, distributing malicious Envoy configuration that redirects or decrypts traffic, and reading every VirtualService, DestinationRule, and AuthorizationPolicy in the cluster.
Hardening the Mesh
- Enforce STRICT PeerAuthentication cluster-wide: Set a mesh-wide
PeerAuthenticationwithmode: STRICT. Audit all namespaces and pods for missing sidecars before enabling STRICT mode and fix any that are missing injection. - Prohibit sidecar opt-out via admission control: Use OPA Gatekeeper or Kyverno to deny pod deployments that include the
sidecar.istio.io/inject: "false"annotation in mesh-enforced namespaces. - Set a default-deny AuthorizationPolicy per namespace: Deploy a
DENY allAuthorizationPolicy as the default in every namespace and selectively allow only required communication paths. - Use source.principals not source.namespaces in policies: Namespace-based policies can be spoofed by anyone who can deploy to that namespace. Principal-based policies (tied to Kubernetes service accounts) are more precise and harder to abuse.
- Protect Istiod with network policies: Restrict access to the Istiod API (port 15010/15012) to only workloads that legitimately need it β typically the Envoy sidecars and the istio-operator.
Verify, don't assume: Use istioctl analyze and istioctl x authz check regularly to verify that your mesh configuration matches your security intent. The gap between intended and actual mesh state is where attacks live.