Istio Hands-on Part 7 - Security with mTLS and AuthorizationPolicies
Objective
Lock down service-to-service traffic. You will:
- Enforce mutual TLS (mTLS) for the whole mesh
- Give
frontendandbackendseparate identities - Deny all traffic, then allow only
frontendto callbackend - Test an allowed call and a denied call
At the end every request to the backend is encrypted and comes from a known identity.
Step 1: Confirm the setup
You need:
- Istio 1.27 or later
- The
frontendtobackendapplication from Part 3 - Sidecar injection enabled
Check that the backend pod has a sidecar:
kubectl get pods -l app=backend -o jsonpath='{.items[*].spec.initContainers[*].name} {.items[*].spec.containers[*].name}'The output includes istio-proxy.
Step 2: Enforce mTLS
A PeerAuthentication in istio-system applies to the whole mesh. STRICT mode rejects plain-text connections.
cat <<EOF | kubectl apply -f -apiVersion: security.istio.io/v1kind: PeerAuthenticationmetadata: name: default namespace: istio-systemspec: mtls: mode: STRICTEOFCheck:
kubectl get peerauthentication -AExpected output:
NAMESPACE NAME MODE AGEistio-system default STRICT 71mCheck one pod:
istioctl x describe pod backend-xxxxxx-wntdpExpected output:
Pod: backend-684d96759f-wntdp Pod Revision: default Pod Ports: 5678 (backend)--------------------Service: backend Port: 80/auto-detect targets pod port 5678DestinationRule: backend for "backend" WARNING POD DOES NOT MATCH ANY SUBSETS. (Non matching subsets v1,v2) Matching subsets: (Non-matching subsets v1,v2) No Traffic Policy--------------------Effective PeerAuthentication: Workload mTLS mode: STRICTApplied PeerAuthentication: default.istio-systemThe subset warning comes from the Part 4 DestinationRule. It does not affect this part.
Step 3: Identities
With mTLS on, Istio gives each workload a SPIFFE identity. The identity comes from the Kubernetes ServiceAccount of the pod:
spiffe://cluster.local/ns/default/sa/frontend-saAccess policies match on this identity.
By default all pods use the default ServiceAccount. Then frontend and backend have the same identity and Istio cannot tell them apart. Step 4 fixes that.
Step 4: One ServiceAccount per workload
kubectl create sa frontend-sakubectl create sa backend-saPatch the deployments:
kubectl patch deploy frontend -p '{"spec":{"template":{"spec":{"serviceAccountName":"frontend-sa"}}}}'kubectl patch deploy backend -p '{"spec":{"template":{"spec":{"serviceAccountName":"backend-sa"}}}}'Wait for the pods to restart:
kubectl get podsEach workload now has its own identity.
Step 5: Deny all
Start from a closed state. An AuthorizationPolicy with an empty spec denies all traffic in the namespace.
cat <<EOF | kubectl apply -f -apiVersion: security.istio.io/v1kind: AuthorizationPolicymetadata: name: deny-all namespace: defaultspec: {}EOFStep 6: Allow frontend to backend
Allow only the frontend-sa identity to reach pods with label app=backend.
cat <<EOF | kubectl apply -f -apiVersion: security.istio.io/v1kind: AuthorizationPolicymetadata: name: backend-allow-frontend namespace: defaultspec: selector: matchLabels: app: backend action: ALLOW rules: - from: - source: principals: - cluster.local/ns/default/sa/frontend-saEOFStep 7: Test
From the frontend, allowed:
kubectl exec deploy/frontend -- curl -s -o /dev/null -w "%{http_code}\n" http://backendExpected output:
200From a new pod with the default ServiceAccount, denied:
kubectl run test --image=curlimages/curl -it --rm -- /bin/shcurl -s -o /dev/null -w "%{http_code}\n" http://backendExpected output:
403If the test pod gets 200, check two things:
frontendandbackenduse different ServiceAccounts- The PeerAuthentication mode is
STRICT, notPERMISSIVE
Step 8: Policy precedence
A common surprise: the namespace-wide deny-all does not block the frontend. This is why:
| Policy scope | Example | Behaviour |
|---|---|---|
Namespace-wide, spec: {} |
deny-all |
Applies only to workloads with no workload-specific policy |
Workload-specific, selector: app=backend |
backend-allow-frontend |
Takes precedence for that workload |
When both exist, the workload-specific policy decides for the backend.
Step 9: See it in Kiali
kubectl port-forward svc/kiali -n istio-system 20001:20001Open http://localhost:20001, select Graph, namespace default.
- Lock icons on the edges show mTLS
- Hover an edge to see allowed and denied requests
- The Istio Config tab lists both AuthorizationPolicies

Summary
- Enforced mTLS for the mesh
- Gave each workload its own ServiceAccount and identity
- Applied a deny-all policy, then one allow rule
- Learned how namespace and workload policies interact
The mesh now follows zero-trust rules: encryption by default, identity-based access, explicit allow rules. Part 8 adds end-user identity with JWT.
Comments