---
title: "Istio authorization stops at identity, not at what a workload may do"
description: "Istio proves which workload is calling with mTLS and SPIFFE, and stops there. This guide covers where AuthorizationPolicy runs out, handing the decision to an external authorizer through the CUSTOM action, running one policy set across north south and east west traffic, and what the extra network hops cost."
author: "Emre Baran"
date: "2026-08-24T12:02:09.445Z"
canonical: "https://www.cerbos.dev/blog/istio-authorization-stops-at-identity"
image: "https://stylish-appliance-1c1cc1c30d.media.strapiapp.com/Istio_authorization_stops_at_identity_not_at_what_a_workload_may_do_91a68248fe.png"
tags: ["guide","engineering"]
source: "https://www.cerbos.dev/blog/istio-authorization-stops-at-identity"
---

# Istio authorization stops at identity, not at what a workload may do

Install Istio, turn on strict mTLS, and every pod in the mesh gains a cryptographic identity it did not have before. istiod issues each workload a SPIFFE ID derived from its Kubernetes service account, rotates the certificate for it, and the sidecar presents it on every connection. `AuthorizationPolicy` then matches on that identity, along with namespace, path, method and port. For a lot of platform teams, that is where mesh security work stops.

NIST wrote guidance for this exact architecture, and it separates two jobs the mesh tempts you to merge. [SP 800-204B](https://csrc.nist.gov/pubs/sp/800/204/b/final) treats the sidecar proxy as the enforcement point, deployable and controllable independently of the application, while the decision itself can either stay in the proxy or be handed to an external service. The [Zero Trust architecture](https://csrc.nist.gov/pubs/sp/800/207/final) it builds on puts the policy engine at the centre, which is worth noting because a mesh is usually installed with Zero Trust written on the ticket.

The distinction matters more than it sounds. mTLS proves a workload is who it claims to be. It says nothing about what that workload should be allowed to do. A compromised service holding a valid SPIFFE identity can reach any endpoint it can route to, and the mesh will keep proving its identity on every hop while it does. Identity is the hard half of the problem and Istio solves it properly. Deciding whether this caller should touch this resource right now is still open.

That gap is not a defect in Istio. `AuthorizationPolicy` is a transport level access control mechanism and it does that job. It was never designed to answer questions about ownership, tenancy or time, and it has no view of the person whose request started the chain.

## Where Istio AuthorizationPolicy runs out, and why mTLS is not authorization

North south traffic reaches the mesh carrying a JWT, and is usually handled by claim matching at the gateway or by code inside each service. East west traffic carries no JWT at all, only an mTLS peer identity, and is handled by `AuthorizationPolicy` on principals and namespaces. Two mechanisms, two policy languages, often two sets of owners. The same underlying rule gets expressed twice and stays correct in one place. [Service to service](https://www.cerbos.dev/blog/service-to-service-authorization) rules are the ones that go stale, because nobody sees them fail.

That split has a name in current guidance. The Coalition for Secure AI states in its [agentic IAM paper](https://www.coalitionforsecureai.org/wp-content/uploads/2026/04/agentic-identity-and-access-control.pdf) that authentication and authorization must be enforced at each hop in a chain and at the final enforcement point, the tool, API or data system, and not only at the initial gateway, to prevent confused deputy and privilege escalation. Written for agent chains, it describes any service chain. The gateway decides that a support user may cancel an order, and three hops later the service acting on it is trusted for being inside the mesh, not because anything re-evaluated the question. Agents make this acute rather than new.

The ceiling is also lower than it first appears. Istio matches on identity, namespace, path, method and port. It has no way to express that a caller owns the record it is asking for, that a request stays inside one tenant boundary, or that refunds are only issued during business hours. Anything sourced from an external system is out of reach entirely. Those [attribute conditions](https://www.cerbos.dev/blog/rbac-vs-abac) end up back in application middleware, which is generally the code the mesh was brought in to thin out.

Decisions then land in two different shapes. Gateway logs record what the edge allowed, sidecar logs record what each hop allowed, and reconstructing one user request means correlating those streams by timestamp after the fact. An [audit trail](https://www.cerbos.dev/blog/how-does-cerbos-help-with-compliance-audits-and-certifications) that cannot explain why a call was permitted does not carry much weight in a review. Meanwhile the rules spread across `AuthorizationPolicy` resources, middleware libraries and the custom Envoy filter someone wrote two years ago, and nobody can say what a given identity can reach across a [microservices](https://www.cerbos.dev/blog/tips-for-microservices-authorization) estate.

## How Istio external authorization works with the CUSTOM action

An `AuthorizationPolicy` with the `CUSTOM` action hands the decision to an external authorizer registered as an extension provider in the mesh configuration. Envoy calls that authorizer over the ext\_authz protocol before the request reaches the application container, and honours the allow or deny it gets back. The mechanism sits in the data path already. What is missing is something on the far end that speaks the protocol and can evaluate a real policy.

[Cerbos Synapse](https://www.cerbos.dev/product-cerbos-synapse) is the data and integration layer that sits in front of the Cerbos PDP, and it [implements that protocol](https://docs.cerbos.dev/synapse/latest/extensions/envoy-extension.html) natively. It registers as an ext\_authz provider like any other. There is no Lua script in the filter chain, no WASM module to compile and ship with every proxy, and no adapter binary to keep in step between Envoy and the policy engine. Turning an Envoy `Check` request into a Cerbos check is CEL configuration.

The shape that covers both planes is two Synapse instances running against one shared set of policies. The external instance sits behind the ingress gateway and builds the principal from JWT claims, `sub` becoming the principal ID and the `roles` claim becoming its roles. The internal instance is the provider for every workload in the application namespace and builds the principal from the SPIFFE identity on the mTLS connection, with a fixed `service` role. The only real difference between the two planes is how identity is derived. Neither mapping carries a hardcoded service list, and both fail closed, since a path resolving to a policy name with nothing behind it is denied by default.

## The interface between enforcement and decision is settling

This is not a workaround. SP 800-204B lists two ways to mediate a request in a proxy, a native authorization structure supplied by the mesh, or a call to an external authorization server using any access control model. Istio and Envoy's external authorization service are named as the example of the second, so the `CUSTOM` provider is the documented path rather than a gap someone found.

What is converging now is the interface itself. The [Authorization API](https://openid.net/specs/authorization-api-1_0.html) reached Final Specification through the OpenID Foundation in January 2026, standardizing how an enforcement point asks a decision point a question, with a subject, an action, a resource and context going in and a decision coming back. It deliberately does not define a policy language, and says nothing about where the decision point gets its facts. Enforcement is heading toward one interface rather than one mechanism per proxy, and how the decision point learns what it needs to know is the part this integration has to solve.

Germany is building the same split across a federation. The security requirements for its federal API authorization infrastructure were [approved](https://www.it-planungsrat.de/beschluss/b-2026-16-it) by the country's IT Planning Council on 17 June 2026, with twenty-three architecture decision records published in the open. The [published account](https://apievangelist.com/2026/07/16/germany-built-the-api-authorization-blueprint-the-rest-of-government-needs/) of the design describes a founding thesis of profile, don't invent, composing OAuth 2.1, FAPI 2.0 and DPoP, with OpenID AuthZEN between its enforcement and decision points. Coarse grained permissions are governed centrally and fine grained decisions stay with the domains that own the data, the same division a mesh needs between namespace level segmentation and per resource rules.

## One policy language for north south and east west traffic

The inventory policy is where this becomes visible. It carries the user facing rules and the service to service rules in one file. Support and admin roles can adjust stock. The orders service, matched by its SPIFFE ID using the [SPIFFE functions](https://docs.cerbos.dev/cerbos/latest/policies/conditions.html) available in Cerbos conditions, can read and update. Reading that one file tells you everything that can touch inventory, whether it arrived as a person or as a process.

That reframes what workload identity is for. Instead of the rule being that orders can call inventory, the rule becomes that orders can call inventory with these methods, on these paths, within this tenant. The SPIFFE ID is one attribute inside the condition rather than the whole condition, and non human callers get evaluated against the same resource definitions as human ones.

Both planes then write decisions in one format, carrying the `x-request-id` Envoy generated at the edge into the decision record. An external request that fans out to two internal services produces three decisions sharing one identifier. Correlation becomes a group by rather than a reconstruction.

## The latency cost of external authorization in a service mesh

Every check is a network hop, and a mesh multiplies hops. A north south request is checked twice by design, once at the gateway against the JWT and once at the destination sidecar against the gateway's own identity. Each internal call adds another. A request that enters through the gateway and triggers three onward calls is evaluated five times rather than once. At the edge that cost is easy to absorb. Per call inside the mesh, it is a different conversation.

Two things bring it down. Synapse can run the PDP [in process](https://docs.cerbos.dev/synapse/latest/) rather than connecting to a separate cluster, so evaluation is a function call rather than another network round trip. And [data sources](https://docs.cerbos.dev/synapse/latest/extensions/data-sources.html) that enrich a request from an identity provider or a database are cached behind an in memory or Redis backend. That cache is a correctness decision as much as a latency one, since an attribute held for sixty seconds means a revoked role stays usable for sixty seconds, and centralizing every attribute moves the bottleneck from authorization to the data layer.

An enforcement point only covers traffic that routes through it, the caveat behind every claim of gateway level enforcement. In a mesh that means every workload genuinely has a sidecar injected and nothing reaches a service on a path the proxy cannot see. It also means the `CUSTOM` authorizer is consulted only for requests Istio routes, so an unrouted path is rejected before any policy runs, which looks different in the logs from a policy deny. Coarse segmentation can stay in `AuthorizationPolicy`, with the external check scoped to the paths that need attribute decisions.

Availability is the objection a mesh reviewer raises first, and the field has a stated position on the hardest case. The same CoSAI guidance requires enforcement gateways fronting high capability agents to be configured fail closed, with a clearly defined safe degradation path such as reverting to human review or halting execution. That is scoped to the highest risk traffic rather than every call in a mesh, so it is a benchmark rather than a rule to copy. The same account describes the German design as one where a decision point keeps working when the central plane is down. Running the PDP in process against a local policy bundle is that property, and the control plane does not have to be reachable for a decision to be made.

Topology is a choice on top of that. A shared deployment keeps the instance count low and the caches warm at the cost of one more hop, while running one [per workload](https://www.cerbos.dev/blog/whats-so-bad-about-sidecars-anyway) removes the hop and multiplies the instances. Both instances also have to see the same policies, and distributing [signed bundles](https://docs.cerbos.dev/synapse/latest/configuration/cerbos-hub.html) from Cerbos Hub makes a rollout across two authorizer fleets one operation rather than two that can disagree.

## Closing the other half

The mesh already solved the difficult part. Workload identity in Istio is cryptographic, rotated without anyone thinking about it, and hard to forge. What it does not carry is intent. Sending a decision the sidecar was going to make anyway to something that can weigh ownership, tenancy and role in one language puts both planes on the same footing, and leaves the application code out of it.

None of it is specific to a mesh either. The same authorizer configuration works against [plain Envoy](https://www.cerbos.dev/blog/envoy-ext-authz-enforcing-one-authorization-policy-at-gateway-in-service) outside a mesh, and the same policy model turns up again in [admission control](https://www.cerbos.dev/blog/kubernetes-admission-policies-without-rego) for Kubernetes. If the attributes your rules depend on live in an identity provider rather than in the token, those are better resolved at [decision time](https://www.cerbos.dev/blog/how-to-look-up-identity-attributes-at-decision-time) than baked into a claim.

[**Try Cerbos**](https://hub.cerbos.cloud) to see how this works in practice, or [**book a call**](https://www.cerbos.dev/workshop) to talk through your architecture with the team.

## FAQ

### Does Istio mTLS provide authorization?

Istio mTLS does not provide authorization, it provides authentication for workloads. istiod issues each workload a SPIFFE ID derived from its Kubernetes service account, rotates the certificate and has the sidecar present it on every connection, which proves a workload is who it claims to be. It says nothing about what that workload should be allowed to do, so a compromised service holding a valid SPIFFE identity can reach any endpoint it can route to while the mesh keeps proving its identity on every hop.

### What are the limits of Istio AuthorizationPolicy?

The limits of Istio AuthorizationPolicy come from it being a transport level access control mechanism. It matches on identity, namespace, path, method and port, which is the job it was designed for. It has no way to express that a caller owns the record it is asking for, that a request stays inside one [tenant boundary](https://www.cerbos.dev/blog/authorization-challenges-in-a-multitenant-system), or that refunds are only issued during business hours, and anything sourced from an external system is out of reach entirely. Those [attribute conditions](https://www.cerbos.dev/blog/rbac-vs-abac) end up back in application middleware, which is usually the code the mesh was brought in to thin out.

### How do you add external authorization to Istio?

You add external authorization to Istio with an AuthorizationPolicy using the CUSTOM action, which hands the decision to an external authorizer registered as an extension provider in the mesh configuration. Envoy calls that authorizer over the ext\_authz protocol before the request reaches the application container and honors the allow or deny it gets back, so the mechanism is already in the data path. [Cerbos Synapse](https://www.cerbos.dev/product-cerbos-synapse) [implements that protocol natively](https://docs.cerbos.dev/synapse/latest/extensions/envoy-extension.html) and registers as an ext\_authz provider, so there is no Lua script in the filter chain, no WASM module to compile and ship with every proxy, and no adapter binary to keep in step.

### What is the difference between north south and east west authorization in a service mesh?

The difference between north south and east west authorization in a service mesh is what the request carries and therefore how the caller is identified. North south traffic reaches the mesh with a JWT and is usually handled by claim matching at the gateway or by code inside each service. East west traffic carries no JWT at all, only an mTLS peer identity, and is handled by AuthorizationPolicy on principals and namespaces. Two mechanisms and two policy languages means the same underlying rule gets expressed twice and stays correct in one place, which is how [service to service](https://www.cerbos.dev/blog/service-to-service-authorization) rules go stale.

### Does external authorization add latency in a service mesh?

External authorization does add latency in a service mesh, because every check is a network hop and a mesh multiplies hops. A north south request is checked twice by design, once at the gateway against the JWT and once at the destination sidecar, so a request that enters through the gateway and triggers three onward calls is evaluated five times rather than once. Two things bring it down. The Cerbos PDP can run [in process](https://docs.cerbos.dev/synapse/latest/) inside Synapse so evaluation is a function call rather than another round trip, and data sources that enrich a request are cached behind an in memory or Redis backend. That cache is a correctness decision as much as a latency one, since an attribute held for sixty seconds means a revoked role stays usable for sixty seconds.

### What traffic does an Istio authorization policy not cover? 

An Istio authorization policy does not cover traffic that does not route through the proxy, which is the caveat behind every claim of gateway level enforcement. Every workload has to genuinely have a sidecar injected, and nothing can reach a service on a path the proxy cannot see. The external authorizer is also consulted only for requests Istio routes, so an unrouted path is rejected before any policy runs, which looks different in the logs from a policy deny and is worth knowing before you read a clean [audit trail](https://www.cerbos.dev/blog/how-does-cerbos-help-with-compliance-audits-and-certifications) as proof that everything was checked.
