Exercise 4: Verify and Enforce mTLS

In this exercise, you will confirm that Istio is encrypting traffic between your meshed workloads with mutual TLS (mTLS), then move the namespace from the default permissive behaviour to strict mTLS so that only encrypted, identity-verified traffic is allowed.

When you added the istio-demo namespace to the mesh, every pod received an Envoy sidecar. By default the AKS Istio add-on runs mTLS in PERMISSIVE mode: sidecars will use mTLS when both ends are in the mesh, but will still accept plaintext. This makes onboarding easy, but it means plaintext traffic is not yet blocked.

Task 1: Confirm Traffic is Using mTLS

  1. Make sure the application has received some traffic, then check the proxy configuration to confirm mTLS is in use between the gateway and the web workloads.

    The Istio sidecars expose their effective configuration on the local admin port. Confirm that the web service has sidecars and is therefore eligible for mTLS.

    kubectl get pods -n istio-demo `
      -o "custom-columns=POD:.metadata.name,INIT-CONTAINERS:.spec.initContainers[*].name,CONTAINERS:.spec.containers[*].name"
    kubectl get pods -n istio-demo \
      -o 'custom-columns=POD:.metadata.name,INIT-CONTAINERS:.spec.initContainers[*].name,CONTAINERS:.spec.containers[*].name'

    Each web pod should list istio-proxy under INIT-CONTAINERS (the AKS Istio add-on uses Kubernetes native sidecar containers, so the Envoy proxy is an initContainer with restartPolicy: Always rather than a regular sibling of web). The presence of the proxy on both the caller (the ingress gateway) and the callee (web) is what allows mTLS to be negotiated automatically.

Tip

You will also be able to see mTLS visually in the next exercise. In Kiali, enable the Security display badge on the graph and look for the padlock icon on the edges between workloads - that padlock means the connection is mTLS encrypted.

Task 2: Prove Permissive Mode Still Allows Plaintext

  1. Send a plaintext request to the web service from a pod that is not in the mesh. Because the namespace is still in permissive mode, this request succeeds.

    kubectl run mtls-test --image=ghcr.io/microsoft/k8s-on-azure-workshop/curl:latest --restart=Never -i --rm `
      -- curl -s -o /dev/null -w "%{http_code}`n" http://web.istio-demo.svc.cluster.local
    kubectl run mtls-test --image=ghcr.io/microsoft/k8s-on-azure-workshop/curl:latest --restart=Never -i --rm \
      -- curl -s -o /dev/null -w "%{http_code}\n" http://web.istio-demo.svc.cluster.local

    The mtls-test pod runs in the default namespace, which is not part of the mesh, so it has no sidecar and sends plaintext. In permissive mode the web sidecar accepts it and you get an HTTP 200.

Task 3: Enforce Strict mTLS

  1. Apply a PeerAuthentication resource to require mTLS for all workloads in the istio-demo namespace.

    @"
    apiVersion: security.istio.io/v1beta1
    kind: PeerAuthentication
    metadata:
      name: default
      namespace: istio-demo
    spec:
      mtls:
        mode: STRICT
    "@ | kubectl apply -f -
    cat <<EOF | kubectl apply -f -
    apiVersion: security.istio.io/v1beta1
    kind: PeerAuthentication
    metadata:
      name: default
      namespace: istio-demo
    spec:
      mtls:
        mode: STRICT
    EOF
  2. Repeat the plaintext request from the non-meshed pod. It should now fail, because the web sidecar rejects any connection that is not mTLS.

    kubectl run mtls-test --image=ghcr.io/microsoft/k8s-on-azure-workshop/curl:latest --restart=Never -i --rm `
      -- curl -s -o /dev/null -w "%{http_code}`n" --max-time 5 http://web.istio-demo.svc.cluster.local
    kubectl run mtls-test --image=ghcr.io/microsoft/k8s-on-azure-workshop/curl:latest --restart=Never -i --rm \
      -- curl -s -o /dev/null -w "%{http_code}\n" --max-time 5 http://web.istio-demo.svc.cluster.local

    The plaintext request now fails (the connection is reset rather than returning a 200). This confirms strict mTLS is being enforced.

  3. Confirm that legitimate traffic still works. Requests through the Istio ingress gateway continue to succeed, because the gateway is in the mesh and uses mTLS to reach web.

    Invoke-WebRequest -UseBasicParsing "http://$INGRESS_IP" | Select-Object -ExpandProperty StatusCode
    curl -s -o /dev/null -w "%{http_code}\n" "http://$INGRESS_IP"

    This should still return 200. Traffic from inside the mesh is encrypted and allowed; plaintext traffic from outside the mesh is blocked.

Info

STRICT mode rejects all non-mTLS traffic to the affected workloads. Only enable it once you are confident every legitimate caller is in the mesh - otherwise you can unintentionally block traffic from workloads that have not been onboarded yet. The PERMISSIVE default exists precisely to make that migration safe.