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.

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    <none>        8000/TCP   90s
kubernetes   ClusterIP   10.96.0.1       <none>        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

ConceptWhere to matchExamples
Source matchingfrom.sourcenamespaces, principals, requestPrincipals, ipBlocks
Operation matchingto.operationmethods, paths, ports
Workload scopingselector.matchLabelsTarget a specific workload or app
Actionsspec.actionALLOW, DENY, AUDIT, CUSTOM
PrecedencePolicy evaluation orderDENY rules evaluated before ALLOW rules
See the Istio docs for the full set of attributes and examples:
A screenshot of the Istio documentation page titled "Authorization Policy," showing explanatory text about CUSTOM, DENY, and ALLOW actions and a numbered rules list. The page has the Istio logo at the top, site navigation across the header, a left-side table of contents, and a "Try Istio" button.

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:

Watch Video

Practice Lab