Back to Technology

Istio Track Part 3: mTLS & AuthorizationPolicy

June 6, 2026 Wasil Zafar 36 min read

Mutual TLS for zero-trust service communication and AuthorizationPolicy for fine-grained access control by service identity, namespace, and HTTP method.

Table of Contents

  1. Mutual TLS in Istio
  2. PeerAuthentication CRD
  3. AuthorizationPolicy
  4. RequestAuthentication
  5. Zero Trust Architecture
  6. Exercises
  7. Key Takeaways
Istio Track (3 Parts): Part 1: Install & SidecarsPart 2: VirtualService & RoutingPart 3: mTLS & AuthorizationPolicy (You are here)

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).
Migration Strategy: Start with PERMISSIVE mode to allow non-mesh services to communicate. Once all services have sidecars, switch to STRICT. Use 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: ["*"]
Important: RequestAuthentication alone only validates tokens that are present — it does NOT reject requests without tokens. Pair it with an AuthorizationPolicy DENY rule using 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

Exercise 1: Enable STRICT mTLS namespace-wide. Deploy a pod without a sidecar (using the sidecar.istio.io/inject: "false" annotation) and verify it cannot communicate with mesh services. Then switch to PERMISSIVE and confirm connectivity is restored.
Exercise 2: Create a deny-all AuthorizationPolicy for a namespace. Then add ALLOW policies for exactly two communication paths (A→B and B→C). Verify that A cannot reach C directly, but the full chain A→B→C works.
Exercise 3: Configure RequestAuthentication with a test JWT issuer. Generate a valid JWT token (use jwt.io debugger) and verify requests with valid tokens succeed while requests without tokens or with expired tokens are rejected.

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
Series Complete! You've covered Istio installation, sidecar injection, traffic management with VirtualService, advanced routing patterns, and security with mTLS and AuthorizationPolicy. These three parts give you a production-ready foundation for operating Istio service meshes.