Mutual TLS in Istio
Istio provides mutual TLS (mTLS) between all services in the mesh. Unlike standard TLS where only the server proves identity, mTLS requires both client and server to present certificates — enabling cryptographic verification of service identity.
SPIFFE Identity
Istio issues each workload a SPIFFE (Secure Production Identity Framework For Everyone) certificate. The identity format is:
# SPIFFE identity format in Istio
spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>
# Example: a pod running as "reviews" service account in "bookinfo" namespace
spiffe://cluster.local/ns/bookinfo/sa/reviews
# Verify the certificate issued to a sidecar
istioctl proxy-config secret deploy/reviews -n bookinfo
# Check mTLS status between services
istioctl x describe pod reviews-v1-xxxxx.bookinfo
mTLS Modes
Istio supports three mTLS modes via PeerAuthentication:
- STRICT — Only mTLS traffic accepted. Non-mTLS connections are rejected.
- PERMISSIVE — Accepts both mTLS and plaintext. Used during migration.
- DISABLE — mTLS disabled for the workload (not recommended).
istioctl x describe to verify mTLS status before switching.
PeerAuthentication CRD
The PeerAuthentication resource controls mTLS enforcement at mesh, namespace, or workload level:
# peer-auth-namespace.yaml — enforce STRICT mTLS for entire namespace
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: bookinfo
spec:
mtls:
mode: STRICT
Workload-Specific mTLS
Override the namespace policy for specific workloads using a selector:
# peer-auth-workload.yaml — PERMISSIVE for a specific service
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: ratings-permissive
namespace: bookinfo
spec:
selector:
matchLabels:
app: ratings
mtls:
mode: PERMISSIVE
portLevelMtls:
9080:
mode: STRICT
8080:
mode: DISABLE
# Apply namespace-wide STRICT mTLS
kubectl apply -f peer-auth-namespace.yaml
# Verify mTLS is enforced — plaintext request should fail
kubectl exec deploy/sleep -n legacy -- \
curl -s http://reviews.bookinfo:9080/reviews/1
# Expected: connection reset (no sidecar in "legacy" namespace)
# Verify mTLS works from within the mesh
kubectl exec deploy/sleep -n bookinfo -c sleep -- \
curl -s http://reviews:9080/reviews/1
# Expected: 200 OK (both have sidecars, mTLS negotiated automatically)
AuthorizationPolicy
AuthorizationPolicy provides fine-grained access control within the mesh. It operates on the principle of least privilege — defining exactly which services can communicate with each other.
ALLOW & DENY Actions
# authz-allow.yaml — only frontend can access reviews
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: reviews-allow
namespace: bookinfo
spec:
selector:
matchLabels:
app: reviews
action: ALLOW
rules:
- from:
- source:
principals:
- "cluster.local/ns/bookinfo/sa/frontend"
to:
- operation:
methods: ["GET"]
paths: ["/reviews/*"]
# authz-deny.yaml — block all traffic from external namespace
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-external
namespace: bookinfo
spec:
selector:
matchLabels:
app: reviews
action: DENY
rules:
- from:
- source:
namespaces: ["external", "legacy"]
Source Principals & Conditions
AuthorizationPolicy rules support multiple conditions that are AND-ed together within a rule, while multiple rules are OR-ed:
# authz-combined.yaml — complex access control
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ratings-policy
namespace: bookinfo
spec:
selector:
matchLabels:
app: ratings
action: ALLOW
rules:
# Rule 1: reviews service can GET ratings
- from:
- source:
principals: ["cluster.local/ns/bookinfo/sa/reviews"]
to:
- operation:
methods: ["GET"]
paths: ["/ratings/*"]
# Rule 2: admin service has full access
- from:
- source:
principals: ["cluster.local/ns/bookinfo/sa/admin"]
to:
- operation:
methods: ["GET", "POST", "PUT", "DELETE"]
RequestAuthentication
RequestAuthentication validates JWT tokens at the mesh edge. It verifies the token signature, issuer, and audience before allowing the request to proceed:
# request-auth.yaml — validate JWTs from an OIDC provider
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: jwt-auth
namespace: bookinfo
spec:
selector:
matchLabels:
app: frontend
jwtRules:
- issuer: "https://accounts.google.com"
jwksUri: "https://www.googleapis.com/oauth2/v3/certs"
audiences:
- "my-app.example.com"
forwardOriginalToken: true
---
# Require valid JWT — reject requests without token
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: require-jwt
namespace: bookinfo
spec:
selector:
matchLabels:
app: frontend
action: DENY
rules:
- from:
- source:
notRequestPrincipals: ["*"]
notRequestPrincipals to enforce mandatory authentication.
Zero Trust Architecture
Implementing zero trust with Istio follows a deny-by-default pattern. Apply a blanket deny policy, then explicitly allow only required communication paths:
# zero-trust-deny-all.yaml — deny all traffic by default
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: bookinfo
spec:
# Empty spec = deny all traffic to all workloads in namespace
{}
---
# zero-trust-allow-frontend.yaml — explicitly allow frontend → reviews
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-frontend-to-reviews
namespace: bookinfo
spec:
selector:
matchLabels:
app: reviews
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/bookinfo/sa/frontend"]
to:
- operation:
methods: ["GET"]
---
# zero-trust-allow-reviews-ratings.yaml — reviews → ratings
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-reviews-to-ratings
namespace: bookinfo
spec:
selector:
matchLabels:
app: ratings
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/bookinfo/sa/reviews"]
to:
- operation:
methods: ["GET"]
# Apply zero-trust policies
kubectl apply -f zero-trust-deny-all.yaml
kubectl apply -f zero-trust-allow-frontend.yaml
kubectl apply -f zero-trust-allow-reviews-ratings.yaml
# Verify: frontend → reviews works
kubectl exec deploy/frontend -c frontend -- curl -s http://reviews:9080/reviews/1
# Expected: 200 OK
# Verify: direct access to ratings is denied
kubectl exec deploy/frontend -c frontend -- curl -s http://ratings:9080/ratings/1
# Expected: RBAC: access denied (frontend not authorized to access ratings directly)
Exercises
sidecar.istio.io/inject: "false" annotation) and verify it cannot communicate with mesh services. Then switch to PERMISSIVE and confirm connectivity is restored.
Key Takeaways
- mTLS provides cryptographic service identity verification using SPIFFE certificates issued by istiod
- PeerAuthentication controls mTLS enforcement at mesh, namespace, and workload levels with STRICT/PERMISSIVE/DISABLE modes
- AuthorizationPolicy enables fine-grained access control based on service identity (principal), namespace, HTTP method, and path
- RequestAuthentication validates JWT tokens but must be paired with AuthorizationPolicy to enforce mandatory authentication
- Zero trust is implemented with deny-all + explicit ALLOW policies — least privilege by default