Building DevSecOps solutions using AWS, Terraform and Kubernetes

Enforce mTLS in Istio

  • 26th May 2025
Istio Diagram

Introduction

Istio uses mutual TLS (mTLS) to ensure all service-to-service communication within the mesh is encrypted.

This short article covers setting up a sample environment, checking the current mTLS settings, and exploring how to enforce mTLS for both inbound and outbound traffic.

Prerequisites

  • You have a local Kubernetes development cluster
  • You have kubectl installed and working
  • You have istioctl installed and working
  • You have istio configured

Note: This example code is intended for lightweight tutorial purposes only and is not suitable for production use.

Setup

Before we get started, let’s create a dummy environment containing:

  • 2x Deployment with Nginx with Istio sidecars
  • 2x ConfigMap containing Nginx config
  • 2x Service

This setup allows us to test traffic between two Nginx sidecars.

Create istio-mtls.yaml:

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc-1
spec:
  type: NodePort
  ports:
  - name: http-web
    port: 80
    targetPort: 80
    nodePort: 30003
  selector:
    app: nginx-1
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config-1
data:
  nginx.conf: '
events {
}
http {
   server {
       listen 80;
       location / {
           return 200 "Hello world - nginx-svc-1!";
       }
   }
}
'
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-1
spec:
  selector:
    matchLabels:
      app: nginx-1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nginx-1
    spec:
      containers:
      - image: nginx:latest
        name: nginx-1
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
          - name: config-vol
            mountPath: /etc/nginx/
      volumes:
        - name: config-vol
          configMap:
            name: nginx-config-1
            items:
              - key: nginx.conf
                path: nginx.conf
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc-2
spec:
  type: NodePort
  ports:
  - name: http-web
    port: 80
    targetPort: 80
    nodePort: 30002
  selector:
    app: nginx-2
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config-2
data:
  nginx.conf: '
events {
}
http {
   server {
       listen 80;
       location / {
           return 200 "Hello world - nginx-svc-2!";
       }
   }
}
'
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-2
spec:
  selector:
    matchLabels:
      app: nginx-2
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nginx-2
    spec:
      containers:
      - image: nginx:latest
        name: nginx-2
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
          - name: config-vol
            mountPath: /etc/nginx/
      volumes:
        - name: config-vol
          configMap:
            name: nginx-config-2
            items:
              - key: nginx.conf
                path: nginx.conf

Create the namespace, enable Istio sidecar injection, and apply istio-mtls.yaml:

kubectl create namespace mtls-test
kubectl label namespace mtls-test istio-injection=enabled --overwrite
kubectl apply -f istio-mtls.yaml -n mtls-test

Confirm setup

istioctl analyze -n mtls-test
kubectl get deployments -n mtls-test
kubectl get pods -n mtls-test

Check current mTLS settings

1) Check PeerAuthentication

Istio uses PeerAuthentication to configure mTLS settings for inbound traffic:

kubectl get peerauthentication -A

Look for the following mtls.mode settings:

  • STRICT - mTLS is enforced
  • PERMISSIVE - mTLS is optional
  • DISABLE - mTLS is off

If you see this result then it has not been enforced:

$ kubectl get peerauthentication -A
No resources found
2) Check DestinationRule

Istio uses DestinationRule to configure mTLS settings for outbound traffic:

kubectl get destinationrule -A

Look for rules with trafficPolicy.tls.mode:

  • ISTIO_MUTUAL - mTLS is enabled
  • DISABLE - mTLS is disabled
3) Describe Pod using istioctl

Now to check which of these rules actually apply to a pod we can use istioctl.

Get pod name:

kubectl get pod -n mtls-test

Use istioctl, replacing <pod-name> and <namespace> with your own values:

istioctl x describe pod <pod-name>.<namespace>
# e.g.: istioctl x describe pod nginx-1-6dfdc84d8c-8hw66.mtls-test

Example output:

Pod: nginx-1-6dfdc84d8c-8hw66.mtls-test
   Pod Revision: default
   Pod Ports: 80 (nginx-1), 15090 (istio-proxy)
--------------------
Service: nginx-svc-1.mtls-test
   Port: http-web 80/HTTP targets pod port 80
--------------------
Effective PeerAuthentication:
   Workload mTLS mode: PERMISSIVE

We can see that this pod is still using PERMISSIVE mode, which means mTLS is optional.

4) Check global settings

Finally, you can check if global mTLS is enabled on the cluster by running:

kubectl get configmap istio -n istio-system -o yaml | grep -i mtls

If enabled, you should see something similar to:

enableAutoMtls: true

Enforce Inbound mTLS for namespace

We can now enforce mTLS for our mtls-test namespace by deploying this peer-authentication.yaml:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: istio-mtls-test
  namespace: mtls-test
spec:
  mtls:
    mode: STRICT

Then apply:

kubectl apply -f peer-authentication.yaml -n mtls-test

If you set PeerAuthentication with mtls.mode: STRICT, the server side (the pod receiving traffic) requires mTLS.

Now we can confirm by running the commands from earlier. Remember to replace <pod-name>.<namespace> with your own values:

istioctl x describe pod <pod-name>.<namespace>

Expected output:

Pod: nginx-1-6dfdc84d8c-8hw66.mtls-test
   Pod Revision: default
   Pod Ports: 80 (nginx-1), 15090 (istio-proxy)
--------------------
Service: nginx-svc-1.mtls-test
   Port: http-web 80/HTTP targets pod port 80
--------------------
Effective PeerAuthentication:
   Workload mTLS mode: STRICT
Applied PeerAuthentication:
   istio-mtls-test.mtls-test

You can see that this PeerAuthentication is now set to STRICT and enforced for inbound traffic to this pod.

Confirm traffic still works between containers:

kubectl exec -it nginx-1-6dfdc84d8c-8hw66 -n mtls-test -c nginx-1 -- curl http://nginx-svc-2.mtls-test.svc.cluster.local

Enforce Outbound mTLS for namespace

We can now outbound mTLS for our mtls-test namespace by deploying this destination-rule.yaml:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: istio-mtls-test
  namespace: mtls-test
spec:
  host: "*.svc.cluster.local"
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL

Then apply:

kubectl apply -f destination-rule.yaml -n mtls-test

The DestinationRule tells the Envoy proxy to send outbound traffic using mTLS (using tls.mode: ISTIO_MUTUAL).

If no DestinationRule is present or it doesn’t specify mTLS, the client may send plaintext traffic.

Now we can confirm by running the commands from earlier. Remember to replace <pod-name>.<namespace> with your own values:

istioctl x describe pod <pod-name>.<namespace>

Expected output:

Pod: nginx-1-6dfdc84d8c-8hw66.mtls-test
   Pod Revision: default
   Pod Ports: 80 (nginx-1), 15090 (istio-proxy)
--------------------
Service: nginx-svc-1.mtls-test
   Port: http-web 80/HTTP targets pod port 80
DestinationRule: istio-mtls-test.mtls-test for "*.svc.cluster.local"
   Traffic Policy TLS Mode: ISTIO_MUTUAL
--------------------
Effective PeerAuthentication:
   Workload mTLS mode: STRICT
Applied PeerAuthentication:
   istio-mtls-test.mtls-test

You can see that the DestinationRule is now applied to outbound traffic from this pod to other services within the local mesh ("*.svc.cluster.local")

Again, confirm traffic still works between containers:

kubectl exec -it nginx-1-6dfdc84d8c-jt64r -n mtls-test -c nginx-1 -- curl http://nginx-svc-2.mtls-test.svc.cluster.local

Cleanup

Now to delete all our test resources:

kubectl delete -f istio-mtls.yaml -n mtls-test
kubectl delete -f peer-authentication.yaml -n mtls-test
kubectl delete -f destination-rule.yaml -n mtls-test
kubectl delete namespace mtls-test

Summary

Hopefully this gives you a good starting point for enforcing and debugging mTLS in Istio.

You should now be able to:

  • Check current mTLS settings
  • Enforce mTLS for Inbound traffic
  • Enforce mTLS for Outbound traffic

Rhuaridh

Please get in touch through my socials if you would like to ask any questions - I am always happy to speak tech!