Istio’s authorization policies don’t just control who can access what, they control how and when, often in ways that fundamentally change how you think about network security.
Let’s see this in action. Imagine we have a frontend service that needs to talk to a backend service. By default, Istio allows all traffic between services within the mesh. We want to restrict this.
First, let’s set up a simple scenario with two services, frontend and backend, deployed in the default namespace.
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: nginx # Replace with your actual frontend image
ports:
- containerPort: 80
---
# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: httpd # Replace with your actual backend image
ports:
- containerPort: 80
Apply these:
kubectl apply -f frontend-deployment.yaml
kubectl apply -f backend-deployment.yaml
Now, we need to ensure Istio’s sidecars are injected. If you’re running Istio, this usually happens automatically via namespace labeling:
kubectl label namespace default istio-injection=enabled
Wait a few moments for the pods to restart with the sidecars.
Without any authorization policy, frontend can talk to backend. We can verify this by exec-ing into the frontend pod and trying to curl the backend service:
kubectl exec -it $(kubectl get pods -l app=frontend -o jsonpath='{.items[0].metadata.name}') -- curl http://backend.default.svc.cluster.local
This should return a successful response from the backend.
Now, let’s introduce an AuthorizationPolicy to deny all traffic to the backend service by default.
# deny-all-backend.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all-backend
namespace: default
spec:
selector:
matchLabels:
app: backend
action: DENY
rules:
- {} # An empty rule denies everything
Apply this policy:
kubectl apply -f deny-all-backend.yaml
If you try the curl command again from the frontend pod, it will now fail with a 403 Forbidden error. This is Istio’s sidecar intercepting the request and enforcing the policy.
The mental model here is that Istio’s AuthorizationPolicy is applied to the destination workload (the backend service in this case), and the sidecar on the destination intercepts incoming requests. The policy then dictates whether to ALLOW, DENY, or AUDIT the request based on a set of rules.
The selector in the policy targets the workload(s) it applies to. action specifies what to do if a rule matches. rules are composed of from, to, and when clauses.
from: Specifies the source of the request (e.g., specific services, namespaces, principals).to: Specifies the operation being performed (e.g., HTTP methods, paths, gRPC methods).when: Adds more complex conditions, often based on request properties or JWT claims.
Crucially, if any AuthorizationPolicy selects a workload, then only requests that match a rule in an ALLOW policy (or are explicitly audited) will be permitted. If no ALLOW policy matches, and there’s a DENY policy, the request is denied. If there are multiple ALLOW policies, the request must match at least one. If there are multiple DENY policies, the request must not match any.
Let’s refine our policy to allow frontend to access backend only on GET requests to /api/v1/data.
# allow-frontend-get-data.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-frontend-get-data
namespace: default
spec:
selector:
matchLabels:
app: backend
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/default/sa/frontend"] # Assuming frontend pod has 'frontend' SA
to:
- operation:
methods: ["GET"]
paths: ["/api/v1/data"]
First, we need to ensure the frontend service account has a principal that Istio can recognize. If you created your frontend deployment with a specific serviceAccountName, make sure it’s frontend. If not, Istio uses the default service account for the namespace. For this example, let’s assume the frontend pod runs with the frontend service account. If you haven’t created one, you’ll need to:
kubectl create serviceaccount frontend -n default
# Then update your frontend deployment to use this SA
And then re-apply your frontend deployment.
Now, apply the ALLOW policy:
kubectl apply -f allow-frontend-get-data.yaml
With this policy in place, the deny-all-backend.yaml policy is still active. However, because we now have an ALLOW policy that matches specific conditions, Istio will evaluate it.
- A
GETrequest fromfrontendto/api/v1/dataonbackendwill be allowed. - A
POSTrequest fromfrontendto/api/v1/datawill be denied (because it doesn’t match theGETmethod in theALLOWrule). - A
GETrequest fromfrontendto/api/v1/otherwill be denied (because it doesn’t match/api/v1/datain theALLOWrule). - A request from any other service to
backendwill also be denied by thedeny-all-backend.yamlpolicy, as it doesn’t match thefromclause of ourallow-frontend-get-data.yamlpolicy.
The most surprising part is how principals work. They aren’t just strings; they are Istio’s representation of a workload’s identity, typically derived from its Kubernetes Service Account. The format cluster.local/ns/<namespace>/sa/<serviceaccountname> is the default. This means you’re not just authorizing based on IP addresses or network segments, but on the actual identity of the workload within the mesh, which is a much stronger security guarantee.
The next step is to explore how to use JWT validation within Istio authorization policies for even more robust authentication and authorization.