Identity-based authz
AuthorizationPolicy, default-deny, least privilege.
mTLS proves who is calling; authorization decides whether they may. Identity-based authorization is where zero trust turns "every service has an identity" into "every service may reach only what it needs" — the control that actually bounds lateral movement.
Allow by identity, default-deny
An Istio AuthorizationPolicy (or Linkerd’s authorization resources) makes access decisions based on the verified source identity — the SPIFFE principal in the caller’s certificate — plus namespace and L7 attributes, never the source IP. The zero-trust posture is default-deny: no service-to-service call is permitted until a policy explicitly allows it, so a workload can reach only the specific services and methods it was granted. Because the policy matches on stable identity rather than ephemeral IPs, it stays correct as pods reschedule — the rules follow the workload, not its address.
# Default-deny for the namespace: nothing is allowed unless a policy permits it.apiVersion: security.istio.io/v1kind: AuthorizationPolicymetadata: { name: deny-all, namespace: prod }spec: {} # empty spec with no rules = deny all# Least-privilege allow: ONLY the frontend identity may POST /checkout to api.apiVersion: security.istio.io/v1kind: AuthorizationPolicymetadata: { name: allow-checkout, namespace: prod }spec:selector: { matchLabels: { app: api } }rules:- from: [{ source: { principals: ["cluster.local/ns/prod/sa/frontend"] } }]to: [{ operation: { methods: ["POST"], paths: ["/checkout"] } }]
Two authorization planes
Mesh authorization governs the data plane — service-to-service calls — and is distinct from Kubernetes RBAC, which governs the control plane (who can act on the API server). You want both: RBAC decides who can deploy or read secrets; mesh authz decides which running service may call which. A compromised workload under default-deny identity authz is contained to exactly the destinations its policy allows, so the blast radius of a foothold is what you explicitly granted and nothing more. Manage these policies as version-controlled code and roll them out audit-then-enforce, learning the real call graph before flipping default-deny on.