Lesson  in  DevSecOps in Practice: Container & Kubernetes Security

Mutual TLS with Istio Service Mesh

With Istio, mTLS between services is automatic — no certificate files to manage, no nginx config to touch. Install Istio, label a namespace for sidecar injection, deploy two services, and enforce STRICT mTLS with a single PeerAuthentication resource. The Envoy proxy handles everything transparently.

Istio: The Service Mesh Approach

Manual mTLS vs Service Mesh

ApproachCertificate managementApp changes neededScales to 100 services?
cert-manager + nginxYou manage each certYes — nginx config, volume mountsNo
Istio service meshAutomatic, rotated every 24hNone — sidecar handles itYes

Istio injects an Envoy proxy sidecar into every pod. The sidecar intercepts all traffic and handles mTLS transparently. Your application code never changes.

Install Istio

Istio is being installed in the background as the playground starts. This takes 2-3 minutes.

Wait for istiod to be ready:

kubectl wait --for=condition=Ready pods --all -n istio-system --timeout=180s
kubectl get pods -n istio-system

Expected:

NAME                      READY   STATUS    RESTARTS   AGE
istiod-xxxxx-yyy          1/1     Running   0          2m

Enable Automatic Sidecar Injection

Label a namespace and Istio automatically injects the Envoy proxy into every pod deployed there.

kubectl create namespace istio-demo

kubectl label namespace istio-demo istio-injection=enabled

kubectl get namespace istio-demo --show-labels

Expected: istio-injection=enabled in the labels.

Deploy Two Services Into the Mesh

We'll use httpbin (an HTTP echo service) as the server and sleep (a minimal curl-capable pod) as the client. No TLS configuration needed anywhere.

# Server: httpbin
kubectl apply -n istio-demo -f \
  https://raw.githubusercontent.com/istio/istio/release-1.20/samples/httpbin/httpbin.yaml

# Client: sleep (a simple pod with curl)
kubectl apply -n istio-demo -f \
  https://raw.githubusercontent.com/istio/istio/release-1.20/samples/sleep/sleep.yaml

kubectl wait --for=condition=Ready pods --all -n istio-demo --timeout=120s
echo "Services deployed."

Verify the sidecars were injected

kubectl get pods -n istio-demo

Expected: Each pod shows 2/2 READY — that's the app container + the Envoy sidecar.

kubectl describe pod -n istio-demo -l app=httpbin | grep "istio-proxy"

Enforcing STRICT mTLS

mTLS is Permissive by Default

By default Istio runs in PERMISSIVE mode — it accepts both plaintext and mTLS connections. This allows gradual adoption. For production, switch to STRICT mode: no sidecar, no connection.

Apply PeerAuthentication STRICT

One resource, three lines of YAML:

cat > peer-auth.yaml << 'EOF'
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-demo
spec:
  mtls:
    mode: STRICT
EOF
kubectl apply -f peer-auth.yaml
echo "STRICT mTLS enforced."

Verify mTLS is Working (Transparently)

The sleep pod already has a sidecar. Communication to httpbin works — Istio handles the TLS handshake invisibly.

CLIENT=$(kubectl get pod -n istio-demo -l app=sleep -o jsonpath='{.items[0].metadata.name}')

kubectl exec -n istio-demo $CLIENT -c sleep -- \
  curl -s http://httpbin:8000/get | python3 -m json.tool | head -15

Expected: A JSON response — the sleep pod reached httpbin over mTLS. No --cert flags, no key files. Istio handled everything.

Show What Happens Without the Sidecar

In STRICT mode, Istio's Envoy proxy rejects plaintext connections. A pod outside the mesh — no sidecar — cannot connect.

# Run a curl pod in the 'default' namespace (no istio-injection label)
kubectl run non-mesh-test \
  --image=curlimages/curl:8.5.0 \
  --restart=Never \
  -i --rm \
  -n default \
  -- curl -v --max-time 5 http://httpbin.istio-demo.svc.cluster.local:8000/get \
  2>&1 | tail -5

Expected:

* Connection #0 to host httpbin.istio-demo.svc.cluster.local left intact
curl: (56) Recv failure: Connection reset by peer

Or a timeout — the Envoy sidecar on httpbin drops the plaintext connection because it has no client certificate.

mTLS at Scale

FeatureManual cert-managerIstio
Cert issuancePer service, manualAutomatic for all pods
Cert rotationManual or via cert-managerEvery 24h, zero downtime
App changesnginx config, volume mountsNone
EnforcementPer-service configNamespace or mesh-wide
Observabilitynginx logsKiali, Jaeger, Prometheus built-in

Cleanup

kubectl delete namespace istio-demo
helm uninstall istiod -n istio-system
helm uninstall istio-base -n istio-system
kubectl delete namespace istio-system
Previous lesson
Kubernetes Network Policy