Here’s some notes I took while learning about Istio mTLS Some sources from cloudflare and partially from linux foundation training.

Once workloads have a strong identity, we can use them at runtime to do mutual TLS authentication (mTLS).

Traditionally, TLS is done one way. Let’s take the example of going to https://google.com. If you navigate to the page, you will notice the lock icon, and you can click on it to get the certificate details. However, we did not give any proof of identity to google.com, we just opened the website. This is where mTLS is fundamentally different.

When two services try to communicate using mTLS, it’s required that both of them provide certificates to each other. That way, both parties know the identity of who they are talking to.

Using mTLS both client and server verify each others’ identities

Using mTLS both client and server verify each others’ identities

As we already learned, all communication between workloads goes through the Envoy proxies. When a workload sends a request to another, Istio re-routes the traffic to the sidecar Envoy proxy, regardless of whether mTLS or plain text communication is used.

In the case of mTLS, once the mTLS connection is established, the request is forwarded from the client Envoy proxy to the server-side Envoy proxy. Then, the sidecar Envoy starts an mTLS handshake with the server-side Envoy. The workloads themselves aren’t performing the mTLS handshake - it’s the Envoy proxies doing the work.

During the handshake, the caller does a secure naming check to verify the service account in the server certificate is authorized to run the target service. After the authorization on the server-side, the sidecar forwards the traffic to the workload.

The sidecars are involved in intercepting the incoming inbound traffic and facilitating or sending the outbound traffic. For that reason, two distinct resources control the inbound and outbound traffic. The PeerAuthentication for inbound traffic and the DestinationRule for outbound traffic.

PeerAuthentication is used to configure mTLS settings for inbound traffic, and DestinationRule for configuring TLS settings for outbound traffic

PeerAuthentication is used to configure mTLS settings for inbound traffic, and DestinationRule for configuring TLS settings for outbound traffic

so lets start

first a gateway

apiVersion: networking.istio.io/v1beta1  
kind: Gateway  
metadata:  
  name: gateway  
spec:  
  selector:  
    istio: ingressgateway  
  servers:  
    - port:  
        number: 80  
        name: http  
        protocol: HTTP  
      hosts:  
        - '*'

second disable the istio injection by changing the namespace label. adding a -

kubectl label namespace default istio-injection-
namespace/default labeled

third a frontend service (this means this one has no istio-injection)

apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: web-frontend  
  labels:  
    app: web-frontend  
spec:  
  replicas: 1  
  selector:  
    matchLabels:  
      app: web-frontend  
  template:  
    metadata:  
      labels:  
        app: web-frontend  
        version: v1  
    spec:  
      containers:  
        - image: gcr.io/tetratelabs/web-frontend:1.0.0  
          imagePullPolicy: Always  
          name: web  
          ports:  
            - containerPort: 8080  
          env:  
            - name: CUSTOMER_SERVICE_URL  
              value: 'ht‌tp://customers.default.svc.cluster.local'  
---  
kind: Service  
apiVersion: v1  
metadata:  
  name: web-frontend  
  labels:  
    app: web-frontend  
spec:  
  selector:  
    app: web-frontend  
  ports:  
    - port: 80  
      name: http  
      targetPort: 8080  
---  
apiVersion: networking.istio.io/v1beta1  
kind: VirtualService  
metadata:  
  name: web-frontend  
spec:  
  hosts:  
    - '*'  
  gateways:
    - gateway
  http:  
    - route:
        - destination:  
            host: web-frontend.default.svc.cluster.local  
            port: 
              number: 80

we should get the pods up and running

kubectl get po 
NAME                          READY  STATUS   RESTARTS  AGE  
web-frontend-659f65f49-cbhvl  1/1    Running  0         7m31s

fourth reenable the injection

kubectl label namespace default istio-injection=enabled  
namespace/default labeled

injection happens during pod creation so this we should be aware

fifth a customer-v1 deployment (this means this one has istio-injection)

apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: customers-v1  
  labels:  
    app: customers  
    version: v1  
spec:  
  replicas: 1  
  selector:  
    matchLabels:  
      app: customers  
      version: v1  
  template:  
    metadata:  
      labels:  
        app: customers  
        version: v1  
    spec:  
     containers:  
      - image: gcr.io/tetratelabs/customers:1.0.0  
        imagePullPolicy: Always  
        name: svc  
        ports:  
          - containerPort: 3000  
---  
kind: Service  
apiVersion: v1  
metadata:  
  name: customers  
  labels:  
    app: customers  
spec:  
  selector:  
    app: customers  
  ports:  
    - port: 80  
      name: http  
      targetPort: 3000  
---  
apiVersion: networking.istio.io/v1beta1  
kind: VirtualService  
metadata:  
  name: customers  
spec:  
  hosts:  
    - 'customers.default.svc.cluster.local'  
  http:  
    - route:  
        - destination:  
            host: customers.default.svc.cluster.local  
            port:  
             number: 80

so when we get po

kubectl get po
NAME                           READY  STATUS   RESTARTS  AGE  
customers-v1-7857944975-qrqsz  2/2    Running  0         4m1s  
web-frontend-659f65f49-cbhvl   1/1    Running  0         13m

MTLS Modes

permission mode

export GATEWAY_IP=$(kubectl get svc -n istio-system istio-ingressgateway -ojsonpath='{.status.loadBalancer.ingress[0].ip}')

if we navigate to $GATEWAY_IP now, we’ll get through with plain text traffic permission allows it if the service has no proxy. The proxy here being the istio-proxy, we didn’t set for the customer-v1

kiali will show this kiali no proxy injected so it says “unknown”

then connect the customer to gateway

apiVersion: networking.istio.io/v1beta1  
kind: VirtualService  
metadata:  
  name: customers  
spec:  
  hosts:  
    - 'customers.default.svc.cluster.local'  
  gateways:  
    - gateway  
  http:  
    - route:  
        - destination:  
            host: customers.default.svc.cluster.local  
            port:  
              number: 80

curl the host should work

curl -H "Host: customers.default.svc.cluster.local" http://$GATEWAY_IP

generate traffic towards the host

while true; do curl -H "Host: customers.default.svc.cluster.local" http://$GATEWAY_IP; done

and towards the service

while true; do curl http://$GATEWAY_IP; done

kiali will show kiali padlocks MTLS (notice padlock) for the traffic towards customers-v1 not for web-frontend (without MTLS/proxy)

Lets use STRICT mode with peerAuthentication

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

we’ll get ECONNRESET for the web-frontend because web-frontend internal connect is closed by customer-v1 which expects MTLS

The contrast:

  • PERMISSIVE → accepts plaintext and mTLS (default, good for migration).
  • STRICT → accepts mTLS only, rejects all plaintext (good once fully migrated).
  • DISABLE → no mTLS, plaintext only (rarely used, e.g. when something else handles encryption).

we can use JWT instead of [[SPIFFE]] security