Istio Security#
Istio provides three security capabilities that are difficult to implement without a service mesh: automatic mutual TLS between services, fine-grained authorization policies, and egress traffic control. These features work at the infrastructure layer, meaning applications do not need any code changes.
Automatic mTLS with PeerAuthentication#
Istio’s sidecar proxies can automatically encrypt all pod-to-pod traffic with mutual TLS. The key resource is PeerAuthentication. There are three modes:
- PERMISSIVE – Accepts both plaintext and mTLS traffic. This is the default and exists for migration. Do not leave it in production.
- STRICT – Requires mTLS for all traffic. Plaintext connections are rejected.
- DISABLE – Turns off mTLS entirely.
Enable strict mTLS across the entire mesh:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICTPlacing this in istio-system applies it mesh-wide. You can override per-namespace or per-workload:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: db-strict
namespace: database
spec:
selector:
matchLabels:
app: postgres
mtls:
mode: STRICT
portLevelMtls:
5432:
mode: STRICTCommon mistake: Leaving the mesh-wide PeerAuthentication in PERMISSIVE mode after the initial rollout. This means any service can communicate in plaintext, defeating the purpose of mTLS. Always switch to STRICT once all services have sidecars injected.
Verify mTLS is active:
istioctl x describe pod <pod-name> -n <namespace>Authorization Policies#
AuthorizationPolicy controls which services can talk to which services, on which paths, using which methods. Without any policy, all traffic is allowed (in the absence of a mesh-wide deny policy).
Deny-by-Default#
Apply a deny-all policy per namespace, then add allow rules:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: production
spec:
{}An empty spec with no rules denies all traffic to workloads in the namespace.
Allow Specific Traffic#
Allow the frontend service to call the api service on GET and POST for specific paths:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-frontend-to-api
namespace: production
spec:
selector:
matchLabels:
app: api
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/production/sa/frontend"]
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/v1/*", "/healthz"]The principals field uses the SPIFFE identity that Istio assigns based on the Kubernetes service account. This ties authorization to workload identity, not network location.
Deny Rules#
Block a specific source from accessing sensitive endpoints:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: deny-external-to-admin
namespace: production
spec:
selector:
matchLabels:
app: api
action: DENY
rules:
- from:
- source:
notNamespaces: ["production"]
to:
- operation:
paths: ["/admin/*"]Policy ordering matters: DENY policies are evaluated before ALLOW policies. If a request matches any DENY rule, it is rejected regardless of ALLOW rules. Design your policies with this in mind.
RequestAuthentication with JWT#
Istio can validate JWT tokens at the proxy layer before they reach your application:
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: jwt-auth
namespace: production
spec:
selector:
matchLabels:
app: api
jwtRules:
- issuer: "https://auth.example.com"
jwksUri: "https://auth.example.com/.well-known/jwks.json"
forwardOriginalToken: true
outputPayloadToHeader: "x-jwt-payload"RequestAuthentication only validates tokens that are present – it does not require them. To require a valid JWT, pair it with an AuthorizationPolicy:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: require-jwt
namespace: production
spec:
selector:
matchLabels:
app: api
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["https://auth.example.com/*"]Requests without a valid JWT will have no requestPrincipal set and will be denied by this policy.
Istio Ingress Gateway Security#
The Istio ingress gateway replaces traditional ingress controllers. Secure it with TLS:
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: app-gateway
namespace: production
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: app-tls-cert
hosts:
- "app.example.com"
- port:
number: 80
name: http
protocol: HTTP
tls:
httpsRedirect: true
hosts:
- "app.example.com"The HTTP server block redirects all plaintext traffic to HTTPS. The credentialName references a Kubernetes secret in the istio-system namespace containing the TLS certificate and key.
Egress Control#
By default, Istio allows all outbound traffic. Lock this down with meshConfig.outboundTrafficPolicy.mode: REGISTRY_ONLY, then explicitly allow external services:
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: allow-external-api
namespace: production
spec:
hosts:
- "api.stripe.com"
ports:
- number: 443
name: https
protocol: TLS
resolution: DNS
location: MESH_EXTERNALCombine with AuthorizationPolicy to restrict which workloads can reach external services:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: restrict-egress-stripe
namespace: production
spec:
selector:
matchLabels:
app: payment-service
action: ALLOW
rules:
- to:
- operation:
hosts: ["api.stripe.com"]
ports: ["443"]Audit Logging#
Enable access logging in the mesh to capture all traffic decisions:
istioctl install --set meshConfig.accessLogFile=/dev/stdoutFor production, send logs to a centralized system. Istio’s Envoy access logs include source and destination identity, authorization decision, and response code – everything needed to investigate security incidents.
Verification Checklist#
- Run
istioctl analyze -Ato detect misconfigurations across the mesh. - Verify mTLS status with
istioctl x describe pod. - Test authorization policies by sending requests from different service accounts and verifying the expected 403 responses.
- Confirm egress restrictions by attempting outbound connections from pods to hosts not in ServiceEntry resources.
- Check that PERMISSIVE mode is not present in any production PeerAuthentication resource.