Module 8: Authentication and Authorization
In this module we focus on Authentication and Authorization: making sure that only trusted workloads can communicate, and that they are only allowed to do what they are supposed to do. In a microservices architecture, security is no longer just about protecting the edge of the system.
| Every service-to-service call is a potential attack surface and must be secured. |
Authentication answers the question: Who are you? Authorization answers the question: What are you allowed to do?
They are important because they:
-
Protect services from unauthorized access
-
Enable zero-trust networking inside the cluster
-
Reduce the blast radius of compromised services
-
Make security consistent and centrally managed
-
Simplify application code by offloading security to the platform
Istio provides strong, built-in security capabilities at the service mesh level:
-
mTLS (Mutual TLS): Automatically encrypts traffic and authenticates workloads using identities
-
Workload identity: Each service gets a cryptographic identity based on its service account
-
Peer authentication: Control how services authenticate each other (strict, permissive, disabled)
-
Request authentication: Validate JWT tokens for end-user or external identity providers
-
Authorization policies: Fine-grained access control based on identity, namespace, paths, methods, headers, and claims
-
Default deny policies: Implement zero-trust by blocking all traffic unless explicitly allowed
-
Centralized enforcement: Security rules are enforced consistently without changing application code
With Istio, authentication and authorization become infrastructure concerns rather than application concerns. This module will show how to secure service-to-service communication and APIs in a uniform, declarative, and production-ready way.
Navigate to the subdirectory: 080-authorization
|
Task 1: Enforcing mTLS with Peer Authentication
PeerAuthentication is how you tell Istio how strictly services must use mTLS when talking to each other.
When you set it to STRICT, you are saying:
“Only accept traffic that is encrypted and mutually authenticated with mTLS.”
|
Sidecar mTLS and ztunnel HBONE handle mTLS handshakes etc. in a different way!! When you set mTLS mode to STRICT, the services in a namespace have to negotiate and agree on ONE specific mTLS mode, therefore STRICT mode won’t work if you have sidecar workloads and ambient workloads in the SAME namespace. In this case you should use PERMISSIVE mode. |
Now let’s enforce mTLS for the whole mesh by applying:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: mtls-default-strict-policy
namespace: istio-system
spec:
mtls:
mode: STRICT
oc apply -f 01-mtls-default-strict-policy.yaml
Validate that the travel application is still working by looking at:
-
Kiali - Traffic Graph
-
Grafana - Service Mesh Overview Dashboard
-
Travel Demo Business Dashboard - The application UI
But notice, your Travel Demo Business Dashboard in the travel-control-sidecar namespace isn’t working anymore.
Why is that?? Do you have an idea?
Task 2: Validate mTLS in Ambient Mode
Ambient mode provides strong workload-to-workload authentication by default using mTLS, without sidecars. In Ambient, authentication is handled by the ztunnel layer:
-
Every workload gets a cryptographic identity (based on its Kubernetes ServiceAccount)
-
All traffic between workloads is automatically: Encrypted, Authenticated, Verified using mutual TLS (mTLS)
You can validate that by:
-
In the OpenShift console, you can look at the metrics for your traffic (Click Observe → Metrics):
istio_tcp_connections_opened_total,istio_tcp_connections_closed_total,istio_tcp_received_bytes_total,istio_tcp_sent_bytes_total -
Use Kiali Dashboard (Traffic Graph → Enable Security in the display options)

-
Validate
HBONEprotocol is being used with the commandistioctl ztunnel-config workloads -n ztunnel -
Looking at the
ztunnellogs, making sure identities are being used for source and destination
Task 3: Create a Level 4 Authorization Policy
The following policy allows only the travel-control service to call the viaggi service.
In simple terms:
-
It applies to pods labeled
app: viaggiin the travel-portal namespace. -
It allows requests coming from the service account:
cluster.local/ns/travel-control/sa/default -
Once this ALLOW policy exists, all other sources are implicitly denied.
The viaggi service only accepts traffic from the travel-control namespace’s default service account. Everyone else is blocked.
This is a Level 4 policy that is enforced by ztunnel.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: travel-api-control-allow
namespace: travel-portal
spec:
selector:
matchLabels:
app: viaggi
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/travel-control/sa/default
oc apply -f 02-auth-policy.yaml
Check if the policy was attached to ztunnel:
oc get ap travel-api-control-allow -n travel-portal -o yaml
status:
conditions:
- lastTransitionTime: "2026-01-09T12:04:50.952399869Z"
message: attached to ztunnel
observedGeneration: "1"
reason: Accepted
status: "True"
type: ZtunnelAccepted
You can also verify that it is not possible to access the viaggi service from another pod with the command:
oc exec $(oc get pod -l app=cars -n travel-agency -o jsonpath='{.items[0].metadata.name}') -n travel-agency -- curl -sSv http://viaggi.travel-portal.svc.cluster.local:8000/status
* Trying 172.30.125.155...
* Connected to viaggi.travel-portal.svc.cluster.local (172.30.125.155) port 8000 (#0)
> GET /status HTTP/1.1
> User-Agent: curl/7.29.0
> Host: viaggi.travel-portal.svc.cluster.local:8000
> Accept: */*
>
upstream connect error or disconnect/reset before headers. reset reason: connection termination< HTTP/1.1 503 Service Unavailable
Task 4: Create a Level 7 Authorization policy
The following policy is an Istio AuthorizationPolicy that defines that the travels service only accepts HTTP GET requests and only if they come from the
travel-portal namespace using the default service account.
Everything else is denied.
So it enforces two conditions at the same time:
-
Correct identity (who is calling)
-
Correct operation (what they are allowed to do)
This is a Level 7 policy that is enforced by the waypoint proxy.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-get-from-travel-portals
namespace: travel-agency
spec:
targetRefs:
- kind: Service
group: ""
name: travels
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/travel-portal/sa/default
to:
- operation:
methods: ["GET"]
oc apply -f 03-travel-api-get-policy.yaml
The traffic flow looks like this:
caller pod
↓
ztunnel (mTLS + identity)
↓
waypoint-travel-agency ← AuthorizationPolicy enforced here (L7)
↓
travels service
Check if the policy was attached to the waypoint proxy:
oc get ap allow-get-from-travel-portals -n travel-agency -o yaml
status:
conditions:
- lastTransitionTime: "2026-01-19T08:46:06.875184153Z"
message: bound to travel-agency/waypoint-travel-agency
observedGeneration: "1"
reason: Accepted
status: "True"
type: WaypointAccepted
Verify that it is not possible to access the travels service from another pod with the command:
oc exec $(oc get pod -l app=cars -n travel-agency -o jsonpath='{.items[0].metadata.name}') -n travel-agency -- curl -sSv travels.travel-agency.svc.cluster.local:8000/travels/Madrid
* Trying 172.30.150.117...
* Connected to travels.travel-agency.svc.cluster.local (172.30.150.117) port 8000 (#0)
> GET /travels/Madrid HTTP/1.1
> User-Agent: curl/7.29.0
> Host: travels.travel-agency.svc.cluster.local:8000
> Accept: */*
>
RBAC: access denied< HTTP/1.1 403 Forbidden
Task 5: DENY ALL policy (Optional Challenge)
In this step we are going to apply a deny all policy for the travel-portal namespace, and you need to figure out the necessary ALLOW policies to make everything work again.
In this case, “deny all traffic in a namespace” means denying everything that goes through the namespace’s waypoint.
So effectively:
-
No service in travel-portal can be called
-
Services in travel-portal cannot call anything else
-
Until you add explicit ALLOW policies, the namespace is completely locked down at L7
anything → ztunnel → waypoint-travel-portal → ❌ DENIED
Now, let’s apply the policy:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: travel-portal-deny-all
namespace: travel-portal
spec:
targetRefs:
- kind: Gateway
group: gateway.networking.k8s.io
name: waypoint-travel-portal
action: DENY
rules:
- {}
oc apply -f 04-deny-all-L7-policy.yaml

Important to remember:
-
This is enforced at the waypoint proxy (L7)
-
mTLS and identity still exist at ztunnel (L4)
-
Only blocks traffic that is routed through that waypoint. It does not block direct ztunnel → workload traffic.
Now figure out the right ALLOW policies to make the travel application work again. And have fun!!
|