Service-Mesh mTLS and Authorization Policy as the Zero-Trust Data Plane

Zero trust becomes real the moment every service-to-service call has to prove who it is and be explicitly permitted — not before. Until then it is a slide.

Most zero-trust programmes stop at the north-south edge. They put an identity provider in front of the application, harden the ingress, and declare the network untrusted. Then, behind that edge, services call each other over plaintext HTTP on a flat pod network, authenticating with a shared secret in an environment variable, trusting any caller that can reach the port. The perimeter moved inward by one hop and nothing else changed. The east-west traffic — the calls between your own services, which is where an attacker moves once they are inside — is still governed by network reachability, which is to say by nothing.

A service mesh is the layer where you can fix that without rewriting every service. It gives you two enforced properties that turn the never-trust-always-verify principle into a fact about the network fabric: a cryptographic identity for every workload, and a policy that decides which identity may call which service, on which method, on which path. This is the data plane where zero trust is either enforced or merely asserted.

Free · 4 minutes

Would you survive contact with a determined attacker — or an auditor?

Fourteen questions on access, patching, detection, and recovery — the basics that prevent most real incidents, and the ones most often assumed rather than verified. Banded finding on screen, full sheet by email.

Identity is the whole game

Everything downstream depends on each workload having an identity that is not its IP address, not its DNS name, and not a bearer token it read from a config map. In a mesh built on the SPIFFE model — Istio, Linkerd and others implement it — each workload is issued a short-lived X.509 certificate whose subject is a verifiable identity string derived from its Kubernetes service account. In Istio that identity takes the form cluster.local/ns/<namespace>/sa/<service-account>. The certificate is minted by the mesh control plane, rotated automatically on a cycle measured in hours, and presented by the sidecar proxy on every connection.

This matters because it decouples authorisation from topology. You no longer reason about “traffic from the 10.4.0.0/16 range” — a statement that tells you nothing about who is actually calling. You reason about “the payments service account in the ledger namespace”, which is a statement about identity that survives rescheduling, scaling and IP churn. The practical discipline this forces is that service accounts stop being an afterthought. One service account per workload, scoped deliberately, becomes the unit of authorisation — the same principle that makes scoped, rotated credentials safe at the application edge, applied inside the mesh.

STRICT mTLS is the floor, not the achievement

Mutual TLS between sidecars gives you two things: the traffic is encrypted in transit, and each end of the connection has verified the other’s certificate. That second property is the one people forget. mTLS is not primarily about encryption — it is about the server refusing connections from any client that cannot present a valid mesh-issued certificate. Without it, an authorisation policy that names a caller identity is unenforceable, because there is nothing stopping a rogue pod from claiming to be that caller.

The trap is running the mesh in PERMISSIVE mode, which accepts both mTLS and plaintext on the same port. Teams enable it during migration — sensibly, because it lets meshed and un-meshed workloads coexist — and then never turn it off. A permissive mesh gives you the dashboards of zero trust with none of the enforcement: the plaintext path is still open, so any attacker on the pod network bypasses identity entirely. STRICT mode closes that path. It is the floor you build on, and the migration goal you have to actually reach, not the destination.

Default-deny, then allow by identity

Encryption and identity establish who is calling. Authorisation decides whether that caller is allowed to. The posture that makes this worth the effort is default-deny: absent an explicit allow rule, the call is refused. In Istio, an empty ALLOW policy attached to a workload denies everything not otherwise permitted, and you then grant specific caller identities access to specific methods and paths. The alternative — allow-by-default with a few deny rules — fails the moment someone adds a service and forgets the rule. Default-deny fails safe; allow-by-default fails open.

The following applies a mesh-wide STRICT mTLS floor, then a per-service policy that lets only the checkout workload’s identity call the payments API, and only on the two methods it legitimately uses. Everything else — every other identity, every other method — is denied by omission.

# 1. Mesh-wide STRICT mTLS. Placed in the root namespace with no
# selector, so it applies to every workload in the mesh.
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
---
# 2. Authorisation for the payments service. An ALLOW policy with a
# selector means: deny anything this policy does not explicitly permit.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: payments-allow-checkout
  namespace: ledger
spec:
  selector:
    matchLabels:
      app: payments
  action: ALLOW
  rules:
    - from:
        - source:
            # SPIFFE identity of the calling workload, not its IP.
            principals:
              - "cluster.local/ns/storefront/sa/checkout"
      to:
        - operation:
            methods: ["POST", "GET"]
            paths: ["/v1/charges*"]

Read that policy as a supervisor or an auditor would: the payments service accepts charge requests from exactly one named identity, on two methods, on one path prefix. A compromised reporting service in another namespace cannot reach it, because its identity is not on the list and there is no plaintext path to fall back to. That is a control you can point at, not a principle you assert.

Where to place the policy, and what to watch

The mesh enforces caller identity and coarse method and path rules. It does not know that this user is allowed to refund that specific transaction — end-user authorisation and token validation still live in the application, which is why validating JWTs correctly remains the service’s own job. The right mental model is layered: the mesh answers “may this workload talk to that workload at all,” and the application answers “may this principal perform this action on this resource.” Collapsing the two into one layer is how you end up with either a mesh that tries to parse business rules or an application that trusts its callers blindly.

Two failure modes are worth naming. First, policy ordering: Istio evaluates DENY rules before ALLOW, so a broad DENY can silently override the allow you thought was in force — test the combination, not each policy alone. Second, observability. A default-deny mesh will reject traffic you did not anticipate, and if you cannot see those rejections you will debug blind during an incident. Ship the sidecar access logs and the mesh authorisation metrics to the same place your identity-first architecture already reports to, and alert on denial spikes. A denied call is the control working; a denied call you cannot explain is the control you do not yet trust.

The point of all this is not the mesh. It is that “never trust, always verify” stops being a strategy-deck slogan and becomes a property the network enforces on your behalf, on every hop, whether or not anyone remembered to write the rule. Turn it off in one namespace to hit a deadline and you have not weakened the posture — you have removed it there.

Build and rescue work

Hands-on delivery of this kind is handled by Sixteen Pillars Studio.

Free interactive tool

Website compliance checklist

What your site has to do, based on what it actually does

Answer as much or as little as you like — the list builds as you go. Nothing is stored against your name and no email is required.

Can you trust the architecture you have?

Architecture diagrams rarely show the reality of how systems actually operate. An independent review establishes what is really there.