Istio is the most widely deployed service mesh, providing mTLS, fine-grained authorization policies, JWT validation, and traffic management via Envoy proxies. Its security model depends on correct PeerAuthentication (mTLS enforcement), AuthorizationPolicy (RBAC), and RequestAuthentication (JWT) configuration. Critical issues include PeerAuthentication in PERMISSIVE mode (allows plaintext from uninjected pods), AuthorizationPolicy with missing or overly broad ALLOW rules, Envoy admin interface exposure on port 15000 inside pods, istiod API accessible to unauthorized clients, and egress control bypass. This guide covers systematic Istio security assessment.
# PeerAuthentication controls mTLS enforcement mode:
# STRICT = only mTLS connections accepted (secure)
# PERMISSIVE = both mTLS and plaintext accepted (insecure default)
# DISABLE = only plaintext (no mTLS)
# Check PeerAuthentication policies
kubectl get peerauthentication -A 2>/dev/null
kubectl get peerauthentication -n istio-system default -o json 2>/dev/null | python3 -c "
import json,sys
r = json.load(sys.stdin)
spec = r.get('spec',{})
mtls = spec.get('mtls',{}).get('mode','NOT SET')
print(f'Global mTLS mode: {mtls}')
# PERMISSIVE or NOT SET = plaintext allowed from uninjected sources
"
# Check namespace-level overrides
kubectl get peerauthentication -n production 2>/dev/null
# If none exists: inherits global policy
# Test: from an uninjected pod, reach an injected service
kubectl run test-bypass --image=curlimages/curl --restart=Never \
--annotations='sidecar.istio.io/inject=false' \
-n production -- sleep 3600
kubectl exec test-bypass -n production -- \
curl -s http://payment-service.production.svc.cluster.local/health 2>/dev/null
# If returns data AND PeerAuthentication is PERMISSIVE:
# Plaintext access confirmed — uninjected pods bypass mTLS entirely
# AuthorizationPolicy controls which services/identities can communicate
# Default: no AuthorizationPolicy = ALLOW ALL (deny-all requires explicit policy)
# Check if a default deny-all policy exists
kubectl get authorizationpolicy -n istio-system default 2>/dev/null
kubectl get authorizationpolicy -n production 2>/dev/null
# If no deny-all policy: all service-to-service traffic is permitted
# Audit AuthorizationPolicy for overly broad ALLOW rules
kubectl get authorizationpolicies -A -o json 2>/dev/null | python3 -c "
import json,sys
r = json.load(sys.stdin)
for p in r.get('items',[]):
ns = p['metadata']['namespace']
name = p['metadata']['name']
spec = p.get('spec',{})
for rule in spec.get('rules',[]):
from_field = rule.get('from',[])
for src in from_field:
principals = src.get('source',{}).get('principals',[])
if '*' in principals or not principals:
print(f'BROAD ALLOW: {ns}/{name} — principals: {principals}')
"
# Test: access a restricted service from an unauthorized service account
# (assumes you've compromised a pod in a different namespace)
kubectl exec -n attacker-ns attacker-pod -- \
curl -s http://admin-service.production.svc.cluster.local/users 2>/dev/null
# Should return RBAC: access denied if policies are correct
# Envoy admin interface runs on port 15000 inside each pod
# If accessible from other pods, it leaks config and certificates
# From inside the same pod (always accessible):
kubectl exec -n production payment-pod -c istio-proxy -- \
curl -s localhost:15000/config_dump 2>/dev/null | python3 -c "
import json,sys
r = json.load(sys.stdin)
# Look for cluster TLS context (contains certificate data)
for config in r.get('configs',[]):
if 'static_secrets' in str(config):
print('Found secret data in Envoy config')
"
# Check if Envoy admin port 15000 is accessible from OTHER pods
kubectl exec -n attacker-ns attacker-pod -- \
curl -s http://VICTIM-POD-IP:15000/stats 2>/dev/null | head -20
# If returns stats: Envoy admin is network-accessible — critical
# Envoy admin has dangerous endpoints:
# /quitquitquit — kills the Envoy process (DoS)
# /reset_counters — resets metrics
# /logging?level=debug — enables debug logging (may log secrets)
# /config_dump — dumps full Envoy configuration including TLS certs
# Extract certificate data from Envoy admin
kubectl exec -n production payment-pod -c istio-proxy -- \
curl -s localhost:15000/config_dump 2>/dev/null | \
python3 -c "
import json,sys,re
data = sys.stdin.read()
# Find certificate/key patterns
certs = re.findall(r'-----BEGIN [^-]+ CERTIFICATE-----.*?-----END [^-]+ CERTIFICATE-----', data, re.DOTALL)
for cert in certs[:3]:
print(cert[:200])
"
spec.mtls.mode: STRICT in the istio-system namespaceproxyMetadata: ISTIO_META_ADMIN_ENABLED: "false"/debug/) and ensure istiod RBAC limits xDS client registration| Security Test | Method | Risk |
|---|---|---|
| PeerAuthentication in PERMISSIVE mode | Check global PeerAuthentication; test from uninjected pod | High |
| No default deny-all AuthorizationPolicy | Missing deny-all means all service traffic allowed | High |
| Envoy admin port 15000 network-accessible | curl VICTIM-IP:15000/config_dump from another pod | High |
| istiod debug endpoint accessible | curl istiod:15014/debug/config_dump | Medium |
| Egress not controlled | curl from pod to external IP — should be blocked | Medium |
| JWT RequestAuthentication with missing paths | Access paths not covered by RequestAuthentication rules | Medium |
Ironimo tests Istio deployments for PeerAuthentication PERMISSIVE mode bypass, AuthorizationPolicy coverage gaps, Envoy admin interface exposure, istiod control plane attack surface, JWT validation bypass, egress gateway escape, and ambient mesh security — covering the complete Istio attack surface.
Start free scan