Skip to main content

Documentation Index

Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide shows how to configure authentication in Istio using the sample helloworld app. You’ll:
  • Deploy the sample app with Istio sidecar injection.
  • Enforce cluster-wide mTLS with a global PeerAuthentication.
  • Demonstrate how namespace- and workload-level PeerAuthentication override the global policy.
  • Show how to limit permissive mode to specific workloads.
Prerequisites and links

1 — Confirm sidecar injection on the default namespace

Verify Istio sidecar injection label on default:
kubectl get ns --show-labels
Expected (example) output:
NAME                 STATUS   AGE     LABELS
default              Active   3m4s    istio-injection=enabled,kubernetes.io/metadata.name=default
istio-system         Active   91s     kubernetes.io/metadata.name=istio-system
kube-node-lease      Active   3m4s    kubernetes.io/metadata.name=kube-node-lease
kube-public          Active   3m4s    kubernetes.io/metadata.name=kube-public
kube-system          Active   3m4s    kubernetes.io/metadata.name=kube-system

2 — Deploy the helloworld sample

Apply the helloworld sample in the default namespace:
kubectl apply -f https://raw.githubusercontent.com/istio/istio/refs/heads/master/samples/helloworld/helloworld.yaml
Expected creation output:
service/helloworld created
deployment.apps/helloworld-v1 created
deployment.apps/helloworld-v2 created
Verify pods:
kubectl get pods
Example output:
NAME                             READY   STATUS    RESTARTS   AGE
helloworld-v1-7459d7b54b-f7cxb   2/2     Running   0          24s
helloworld-v2-654d97458-r84kp    2/2     Running   0          24s

3 — Create a separate namespace and run a test pod

Create a new namespace test and run a pod inside it. Important: include -n test when creating the pod.
kubectl create ns test
# Wrong (runs in default):
# Correct: run the pod in the test namespace
kubectl run test --image=nginx -n test
kubectl get pods -n test
Example pod output:
NAME   READY   STATUS    RESTARTS   AGE
test   1/1     Running   0          4s
Remember: enabling or disabling sidecar injection is a namespace-level label. Pods must be (re)created after changing the label to pick up injection behavior. If you label a namespace for injection, delete and recreate pods to get an Envoy sidecar.

4 — Verify connectivity from the non-injected pod

Check the helloworld service and curl it from the test pod:
kubectl get svc
Example service:
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)     AGE
helloworld   ClusterIP   10.108.92.244   <none>        5000/TCP    62s
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP     4m15s
From the test pod (non-injected), call helloworld:
kubectl exec -ti -n test test -- curl helloworld.default.svc:5000/hello
Expected response (plaintext allowed by default):
Hello version: v2, instance: helloworld-v2-654d97458-r84kp
Explanation: By default Istio does not enforce mTLS, so plaintext traffic from a non-injected pod works.

5 — Enforce cluster-wide mTLS with a global PeerAuthentication

Create peer_auth_global.yaml:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
Apply it and verify:
kubectl apply -f peer_auth_global.yaml
kubectl get peerauthentications.security.istio.io -A
Expected:
peerauthentication.security.istio.io/default created

NAMESPACE      NAME      MODE    AGE
istio-system   default   STRICT  10s

6 — Observe failure from non-injected pods after STRICT mTLS

With the global STRICT policy in place, a non-injected pod (plaintext) cannot talk to services that require mTLS:
kubectl exec -ti -n test test -- curl --head helloworld.default.svc:5000/hello
Example failure:
curl: (56) Recv failure: Connection reset by peer
command terminated with exit code 56
Reason: The destination expects mTLS; the source pod lacks Envoy and sends plaintext.

7 — Enable injection and re-create the test pod to succeed with mTLS

Enable automatic sidecar injection for the test namespace and re-create the pod:
istioctl analyze -n test

kubectl label namespace test istio-injection=enabled
kubectl delete pod test -n test
kubectl run test --image=nginx -n test
Test again:
kubectl exec -ti -n test test -- curl --head helloworld.default.svc:5000/hello
Expected success headers:
HTTP/1.1 200 OK
server: envoy
date: Tue, 15 Apr 2025 18:12:06 GMT
content-type: text/html; charset=utf-8
content-length: 60
x-envoy-upstream-service-time: 122
Now traffic is routed through Envoy sidecars and mTLS succeeds.

8 — Override global STRICT with a namespace-level PERMISSIVE policy

Create peer_auth_default.yaml to set PERMISSIVE for the default namespace:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: default
spec:
  mtls:
    mode: PERMISSIVE
Apply and confirm:
kubectl apply -f peer_auth_default.yaml
kubectl get peerauthentications.security.istio.io -A
Example output:
peerauthentication.security.istio.io/default created

NAMESPACE       NAME      MODE        AGE
default         default   PERMISSIVE  6s
istio-system    default   STRICT      3m7s
Create a new non-injected namespace app and run a pod there to demonstrate reachability:
kubectl create ns app
kubectl get ns --show-labels
kubectl run test --image=nginx -n app
kubectl get pods -n app
Example get ns output:
NAME             STATUS   AGE     LABELS
app              Active   10s     kubernetes.io/metadata.name=app
default          Active   9m49s   istio-injection=enabled,kubernetes.io/metadata.name=default
istio-system     Active   8m16s   kubernetes.io/metadata.name=istio-system
test             Active   6m22s   istio-injection=enabled,kubernetes.io/metadata.name=test
From the app pod (non-injected), curl helloworld in default:
kubectl exec -ti -n app test -- curl --head helloworld.default.svc:5000/hello
Expected success (PERMISSIVE accepts plaintext and mTLS):
HTTP/1.1 200 OK
server: istio-envoy
date: Tue, 15 Apr 2025 18:14:44 GMT
content-type: text/html; charset=utf-8
content-length: 60
x-envoy-upstream-service-time: 136
x-envoy-decorator-operation: helloworld.default.svc.cluster.local:5000/*
Explanation: A namespace-level PeerAuthentication overrides the global STRICT policy for that namespace, allowing plaintext traffic when PERMISSIVE.

9 — Limit permissive mode to only the helloworld workload

Instead of allowing all workloads in default to accept plaintext, add a selector to the namespace PeerAuthentication so only workloads with app: helloworld are PERMISSIVE. Update peer_auth_default.yaml:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: default
spec:
  selector:
    matchLabels:
      app: helloworld
  mtls:
    mode: PERMISSIVE
Apply it:
kubectl apply -f peer_auth_default.yaml
Example:
peerauthentication.security.istio.io/default configured
Behavior now:
  • Global STRICT still applies cluster-wide except where namespace/workload-specific PeerAuthentication overrides it.
  • Only workloads in default labeled app=helloworld will accept plaintext (PERMISSIVE).
  • Other workloads in default (e.g., Bookinfo productpage) still require mTLS from non-injected clients.
Demonstration: From test (injected) to productpage.default.svc:9080:
kubectl exec -ti -n test test -- curl --head productpage.default.svc:9080
Expected success since test is injected:
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 1683
server: envoy
date: Tue, 15 Apr 2025 18:18:37 GMT
x-envoy-upstream-service-time: 23
From app (not injected) to productpage.default.svc:9080:
kubectl exec -ti -n app test -- curl --head productpage.default.svc:9080
Expected failure:
curl: (56) Recv failure: Connection reset by peer
command terminated with exit code 56
But app can still reach helloworld because the selector matches that workload:
kubectl exec -ti -n app test -- curl --head helloworld.default.svc:5000/hello
Expected success:
HTTP/1.1 200 OK
server: istio-envoy
date: Tue, 15 Apr 2025 18:20:09 GMT
content-type: text/html; charset=utf-8
content-length: 60
x-envoy-upstream-service-time: 103
x-envoy-decorator-operation: helloworld.default.svc.cluster.local:5000/*

Quick reference — PeerAuthentication modes and precedence

Term / ResourcePurpose
GLOBAL (namespace: istio-system)Applies to entire mesh unless overridden. Use for cluster-wide defaults.
NAMESPACE (namespace-level PeerAuthentication)Overrides global settings for all workloads in the namespace.
WORKLOAD (PeerAuthentication with selector)Overrides namespace/global for matching workloads only. Highest precedence.
PeerAuthentication modes:
ModeEffect
STRICTEnforces mTLS — only TLS-authenticated connections allowed.
PERMISSIVEAccepts both mTLS and plaintext connections. Useful during migration.
DISABLE or unsetmTLS is not used for the scope (see Istio docs).
Summary bullets
  • PeerAuthentication resources apply at different scopes: global (istio-system) → namespace → workload (via selector).
  • Namespace and workload PeerAuthentication override the global policy for their scope.
  • Use PERMISSIVE to support both plaintext and mTLS while migrating to full mTLS.
  • Use istioctl analyze to detect injection and configuration issues.
A screenshot of the Istio documentation webpage showing the PeerAuthentication / MutualTLS section, including a table describing fields like "mtls" and "portLevelMtls." The page layout shows a sidebar, header navigation, and a "MutualTLS" heading with a field/description table.
Exam tip: Be able to read the PeerAuthentication API (modes: STRICT, PERMISSIVE, DISABLE/UNSET) and explain scope precedence (global → namespace → workload selector). This concept is commonly tested.

Watch Video

Practice Lab