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.
In this lesson we cover authorization in Istio. Authentication (mTLS) verifies workload identity and secures traffic between services. Authorization decides what an authenticated workload is allowed to do.
What you’ll learn:
Deploy a sample workload (httpbin)
Enforce cluster-wide strict mTLS
Create AuthorizationPolicy objects to ALLOW and DENY traffic
Understand how DENY and ALLOW interact (DENY has precedence)
Prerequisites:
An Istio control plane installed and running
kubectl and istioctl available on your control host
Injection enabled for namespaces you test
Deploy the httpbin sample
Apply the httpbin sample workload:
root@controlplane ~ ➜ kubectl apply -f https://raw.githubusercontent.com/istio/istio/refs/heads/master/samples/httpbin/httpbin.yaml
serviceaccount/httpbin created
service/httpbin created
deployment.apps/httpbin created
root@controlplane ~ ➜
Create and label a test namespace with sidecar injection:
root@controlplane ~ ➜ kubectl create ns test
namespace/test created
root@controlplane ~ ➜ kubectl label ns test istio-injection=enabled
namespace/test labeled
root@controlplane ~ ➜ istioctl analyze -n test
✔ No validation issues found when analyzing namespace: test.
root@controlplane ~ ➜
Create a test pod (nginx) in the test namespace so you can exec and curl from it:
root@controlplane ~ ➜ kubectl run test --image=nginx -n test
pod/test created
root@controlplane ~ ➜ kubectl get pods -n test
NAME READY STATUS RESTARTS AGE
test 2/2 Running 0 7s
Verify the httpbin service and curl it from the test pod:
root@controlplane ~ ➜ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT ( S ) AGE
httpbin ClusterIP 10.96.105.55 < non e > 8000/TCP 90s
kubernetes ClusterIP 10.96.0.1 < non e > 443/TCP 39m
root@controlplane ~ ➜ kubectl exec -ti test -n test -- curl --head http://httpbin.default.svc:8000
HTTP/1.1 200 OK
access-control-allow-credentials: true
access-control-allow-origin: *
content-security-policy: default-src 'self' ; style-src 'self' 'unsafe-inline' ; img-src 'self' camo.githubusercontent.com
content-type: text/html ; charset = utf-8
date: Tue, 15 Apr 2025 18:49:11 GMT
x-envoy-upstream-service-time: 11
server: envoy
transfer-encoding: chunked
root@controlplane ~ ➜
At this point everything is accessible because no AuthorizationPolicy objects are in place.
Enable strict mTLS globally
Create a cluster-wide PeerAuthentication in the istio-system namespace to enforce strict mTLS:
apiVersion : security.istio.io/v1
kind : PeerAuthentication
metadata :
name : default
namespace : istio-system
spec :
mtls :
mode : STRICT
Apply it:
root@controlplane ~ ➜ kubectl apply -f peer_auth_global.yaml
peerauthentication.security.istio.io/default created
root@controlplane ~ ➜ kubectl get peerauthentications.security.istio.io -A
NAMESPACE NAME MODE AGE
istio-system default STRICT 17s
root@controlplane ~ ➜
Because both default and test namespaces have sidecar injection enabled, TLS will be used transparently between injected proxies.
Allow traffic to httpbin (AuthorizationPolicy ALLOW)
Create an AuthorizationPolicy that permits GET and HEAD requests to httpbin from the test namespace:
apiVersion : security.istio.io/v1
kind : AuthorizationPolicy
metadata :
name : httpbin-auth-policy
spec :
action : ALLOW
rules :
- from :
- source :
namespaces : [ "test" ]
to :
- operation :
methods : [ "GET" , "HEAD" ]
Apply the policy:
root@controlplane ~ ➜ kubectl apply -f auth_policy.yaml
authorizationpolicy.security.istio.io/httpbin-auth-policy created
HTTP method matching in AuthorizationPolicy is exact. If your policy allows only GET and you send a HEAD request, it will be denied. Include every HTTP method you expect to use.
Allow another namespace (app)
Create an app namespace and enable injection so you can test from a different source namespace:
root@controlplane ~ ➜ kubectl create ns app
namespace/app created
root@controlplane ~ ➜ kubectl label ns app istio-injection=enabled
namespace/app labeled
root@controlplane ~ ➜ kubectl get ns --show-labels
NAME STATUS AGE LABELS
app Active 18s istio-injection=enabled,kubernetes.io/metadata.name=app
default Active 43m istio-injection=enabled,kubernetes.io/metadata.name=default
test Active 5m58s istio-injection=enabled,kubernetes.io/metadata.name=test
Create a pod in app and test access:
root@controlplane ~ ➜ kubectl run test --image=nginx -n app
pod/test created
root@controlplane ~ ➜ kubectl get pods -n app
NAME READY STATUS RESTARTS AGE
test 2/2 Running 0 7s
If you exec into the app pod and curl httpbin, you’ll get a 403 because your ALLOW policy currently permits only the test namespace:
root@controlplane ~ ➜ kubectl exec -ti test -n app -- curl --head http://httpbin.default.svc:8000
HTTP/1.1 403 Forbidden
content-length: 19
content-type: text/plain
date: Tue, 15 Apr 2025 18:53:47 GMT
server: envoy
x-envoy-upstream-service-time: 7
To allow traffic from both namespaces, update the ALLOW policy’s namespaces list to include ["test", "app"], then re-apply.
Deny specific paths (AuthorizationPolicy DENY)
You can DENY specific request paths. Example: deny any request to /delay:
apiVersion : security.istio.io/v1
kind : AuthorizationPolicy
metadata :
name : httpbin-auth-deny-policy
spec :
action : DENY
rules :
- to :
- operation :
paths : [ "/delay" ]
Apply the deny policy:
root@controlplane ~ ➜ kubectl apply -f auth_policy_deny.yaml
authorizationpolicy.security.istio.io/httpbin-auth-deny-policy created
DENY rules are evaluated before ALLOW rules. If a DENY matches a request, it will block the request even if an ALLOW policy would otherwise permit it. Order and scoping matter — scope DENY policies carefully.
Deny all and scoped denies
A common (but often risky) pattern is to create a namespace- or workload-scoped deny-all, then add explicit ALLOWs. Example namespace-scoped deny-all for default:
apiVersion : security.istio.io/v1
kind : AuthorizationPolicy
metadata :
name : deny-all
namespace : default
spec :
action : DENY
rules :
- {}
Apply it:
root@controlplane ~ ➜ kubectl apply -f auth_deny_all.yaml
authorizationpolicy.security.istio.io/deny-all created
root@controlplane ~ ➜ kubectl get authorizationpolicies.security.istio.io -A
NAMESPACE NAME ACTION AGE
default deny-all DENY 7s
default httpbin-auth-policy ALLOW 28m
Because the deny-all policy matches everything in its scope, it will override ALLOWs unless you scope DENY policies narrowly. To remove the deny-all:
root@controlplane ~ ➜ kubectl delete -f auth_deny_all.yaml
authorizationpolicy.security.istio.io "deny-all" deleted
Scoped workload deny
You can target specific workloads using selector.matchLabels. Example: deny all traffic to a workload with app: productpage:
apiVersion : security.istio.io/v1
kind : AuthorizationPolicy
metadata :
name : deny-all-product
spec :
selector :
matchLabels :
app : productpage
action : DENY
rules :
- {}
Apply it:
root@controlplane ~ ➜ kubectl apply -f auth_deny_product.yaml
authorizationpolicy.security.istio.io/deny-all-product created
Before applying the deny, productpage is reachable:
root@controlplane ~ ➜ kubectl exec -ti test -n app -- curl --head http://productpage.default.svc:9080/productpage
HTTP/1.1 200 OK
content-type: text/html ; charset = utf-8
content-length: 5179
server: envoy
date: Tue, 15 Apr 2025 19:30:56 GMT
x-envoy-upstream-service-time: 750
After applying the deny, requests are forbidden:
root@controlplane ~ ➜ kubectl exec -ti test -n app -- curl --head http://productpage.default.svc:9080/productpage
HTTP/1.1 403 Forbidden
content-length: 19
content-type: text/plain
date: Tue, 15 Apr 2025 19:30:56 GMT
server: envoy
x-envoy-upstream-service-time: 7
Quick reference table
Concept Where to match Examples Source matching from.sourcenamespaces, principals, requestPrincipals, ipBlocksOperation matching to.operationmethods, paths, portsWorkload scoping selector.matchLabelsTarget a specific workload or app Actions spec.actionALLOW, DENY, AUDIT, CUSTOMPrecedence Policy evaluation order DENY rules evaluated before ALLOW rules
See the Istio docs for the full set of attributes and examples:
Useful TCP example
AuthorizationPolicy can also be used for TCP by matching ports (no HTTP attributes):
apiVersion : security.istio.io/v1
kind : AuthorizationPolicy
metadata :
name : tcp-policy
namespace : foo
spec :
selector :
matchLabels :
app : tcp-echo
action : ALLOW
rules :
- to :
- operation :
ports : [ "9000" , "9001" ]
Test connecting to TCP ports (from the Istio docs):
$ kubectl exec "$( kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" \
-c curl -n foo -- sh -c \
'echo "port 9000" | nc tcp-echo 9000' | grep "hello" && echo 'connection succeeded' || echo
hello port 9000
connection succeeded
Practice checklist
Allow specific namespaces or service accounts when possible (least privilege).
Allow specific HTTP methods, paths, and ports — remember exact matching.
Use scoped DENY policies rather than broad deny-all when possible.
Test from pods in different namespaces to validate source-based rules.
For TCP services, use ports in AuthorizationPolicy and avoid HTTP attributes.
Summary
Authentication (mTLS) verifies identity; Authorization decides what that identity can do.
AuthorizationPolicy rules can match on source (namespace, principal, service account, labels), operation (methods, paths, ports), and workload selectors.
Actions include ALLOW, DENY, AUDIT, and CUSTOM. DENY takes precedence over ALLOW — scope DENY policies carefully.
Use selector.matchLabels to target specific workloads; use ports for TCP rules.
Further reading: