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.

# Check if any namespace is still in PERMISSIVE mode: kubectl get peerauthentication --all-namespaces # A namespace with no PeerAuthentication inherits the mesh default # If the mesh-wide default is PERMISSIVE (the Istio default), # ALL namespaces without explicit policies accept plain text # Enforce STRICT mesh-wide (do this after verifying all pods have sidecars): kubectl apply -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: istio-system spec: mtls: mode: STRICT EOF

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.

Pod DeployAttacker deploys pod with sidecar.istio.io/inject: false
No SidecarPod starts without Envoy proxy, traffic not intercepted
Plain HTTPPod calls mesh services directly; PERMISSIVE mode accepts it
No IdentityAuthorizationPolicies see empty source.principal, may allow access

AuthorizationPolicy 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

  1. Enforce STRICT PeerAuthentication cluster-wide: Set a mesh-wide PeerAuthentication with mode: STRICT. Audit all namespaces and pods for missing sidecars before enabling STRICT mode and fix any that are missing injection.
  2. 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.
  3. Set a default-deny AuthorizationPolicy per namespace: Deploy a DENY all AuthorizationPolicy as the default in every namespace and selectively allow only required communication paths.
  4. 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.
  5. 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.