mTLS Handoff Between Gateway and Mesh
Running a gateway in front of a mesh means a request is encrypted, decrypted and re-encrypted at least twice before it reaches an application. That is normal and correct — but only if each hop’s identity is the right one and the receiving side knows what it is trusting. This page covers the handoff at the gateway-to-sidecar boundary, the double-encryption trap, and how to make an upstream’s authorisation decision meaningful rather than decorative.
Prerequisite concepts
This assumes implementing mTLS at the gateway edge for the client-facing handshake and API gateway vs service mesh for the layer split.
Three identities on one request
# Envoy 1.32+ gateway — present a workload certificate to the mesh
clusters:
- name: orders_service
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
tls_certificate_sds_secret_configs:
- name: "spiffe://cluster.local/ns/gateway/sa/edge" # the gateway's own identity
sds_config: { api_config_source: { api_type: GRPC } }
combined_validation_context:
default_validation_context:
match_typed_subject_alt_names:
- san_type: URI
matcher: { exact: "spiffe://cluster.local/ns/orders/sa/orders" }
The match_typed_subject_alt_names block is the part that is usually missing. Without it the gateway accepts any certificate the mesh certificate authority issued — which is every workload in that Kubernetes cluster, including one an attacker got a foothold in.
The double-encryption trap
The practical rule is that each hop terminates and re-originates. A gateway that opens a TLS session to the application port rather than to the mesh-managed port has built a tunnel through the mesh, and the mesh will faithfully carry bytes it cannot see.
Making the assertion trustworthy
Once the gateway is a known workload, the user claim it forwards can be trusted — provided the same header cannot be set by anyone else. That requires two things: the gateway strips the header from inbound requests, and the receiving service accepts it only from the gateway’s identity.
Decision matrix
| Situation | Choice |
|---|---|
| Gateway inside the mesh | run it with a sidecar, or as a mesh-native gateway |
| Gateway outside the mesh | issue it a workload identity from the mesh CA explicitly |
| Upstream not yet meshed | terminate at the gateway, plan the sidecar as the next step |
| Compliance requires end-to-end encryption | per-hop mTLS satisfies it; nested tunnels usually do not |
| Multiple clusters | federate trust domains rather than sharing a CA key |
Gotchas and failure signals
Mesh dashboards showing no traffic for a service that is clearly serving requests is the double-encryption signature.
Certificate rotation is on a different clock at each layer. A gateway holding a long-lived certificate against a mesh rotating hourly will fail at a boundary nobody scheduled.
Trust-domain federation is not certificate sharing. Copying a CA key between clusters creates one trust domain with two operators and no way to revoke half of it.
A gateway that accepts any mesh-issued certificate has authenticated the trust domain, not the service. Always match the expected subject alternative name.
Rotation, and the failure it causes at 03:00
Certificate lifetimes are the part of this design most likely to be inherited rather than chosen. A mesh typically issues workload certificates with lifetimes measured in hours and rotates them automatically; a gateway provisioned outside the mesh often holds a certificate measured in months, issued by a different process, renewed by a different team.
That asymmetry is survivable while both sides are healthy and becomes an outage the first time one of them is not. The specific failure is a gateway whose certificate expires between deploys: nothing in the deployment pipeline touched it, no alert fired because the monitoring watched the public-facing certificate rather than the internal one, and every upstream call fails at once with a TLS error that reads like a network fault.
Two habits prevent it. Issue the gateway’s workload identity from the same authority and on the same rotation schedule as everything else in the mesh, even when the gateway itself runs outside the mesh — the point is a single expiry regime, not a single deployment model. And alert on remaining certificate lifetime for every certificate in the path, not merely the one clients see, with a threshold well above one rotation interval so a failed renewal has time to be noticed by a person.
Validation
- Gateway presents a workload identity and the upstream matches its exact SAN
- Sidecar reports the gateway as the source principal in its telemetry
- Forwarded user headers stripped from inbound requests at the edge
- Authorisation policy names the gateway identity as the only permitted asserter
- Certificate lifetimes and rotation intervals aligned across both layers
FAQ
Should the gateway terminate TLS and re-originate to the mesh?
Yes. Each hop terminates and re-originates, so the gateway decrypts the client session, applies policy, and opens a new mutual TLS session to the sidecar using its own workload identity. A gateway that instead opens a session straight through to the application port has tunnelled through the mesh, and the sidecar can no longer apply policy, emit telemetry or route.
How do I know if I have accidentally double-encrypted?
The mesh dashboards show little or no traffic for a service that is plainly serving requests, because the sidecar sees opaque bytes rather than HTTP. Check that the gateway addresses the Service rather than a pod IP, since sidecar interception depends on the destination the connection targets.
Is validating that the certificate came from the mesh CA enough?
No. Every workload in that Kubernetes cluster holds a certificate from that CA, so accepting any of them authenticates the trust domain rather than the peer. Match the exact subject alternative name of the workload you expect, so a foothold in an unrelated service does not become a valid caller.
How does the upstream know it can trust a forwarded user header?
Because the sidecar proved which workload sent it. Strip the header from inbound requests at the edge so no client can set it, then write an authorisation policy naming the gateway identity as the only principal permitted to assert a user. Without that policy the header is an assertion any workload in the mesh can make.
Parent: API Gateway vs Service Mesh
Related
- Choosing Gateway or Mesh for East-West Traffic — which calls should reach this boundary in the first place.
- Implementing mTLS at the Gateway Edge — the client-facing half of the same handshake.
- Security Boundaries & Zero Trust — header sanitisation and claim-based authorisation in context.