When your applications send plaintext HTTP to external services, traffic leaving the mesh is unencrypted. The Service Mesh (ASM) egress gateway acts as a single exit point for all outbound mesh traffic. It intercepts outgoing requests and initiates mTLS connections to external services on behalf of your workloads, providing end-to-end encryption without code changes.
This guide walks you through routing outbound HTTP traffic through an egress gateway and upgrading it to mTLS.
Use cases
Route traffic through an egress gateway with mTLS origination when:
Your security policy requires encrypted egress. Applications send plaintext HTTP, but all traffic leaving the mesh must be encrypted. The egress gateway handles TLS origination with no application changes.
You need centralized egress control. All outbound traffic must flow through dedicated gateway nodes for auditing, monitoring, and policy enforcement.
The external service requires client certificate authentication. The mesh must present a specific client certificate (mutual TLS) on behalf of your workloads.
How it works
The following diagram shows the complete traffic path after you finish all configuration steps in this guide:
sleep pod ──mesh mTLS──> egress gateway ──external mTLS──> external ingress gateway ──mTLS──> httpbinThe
sleeppod sends a plaintext HTTP request totest.com.The sidecar proxy intercepts the request and forwards it to the egress gateway over mesh-internal mTLS (certificates managed by ASM).
The egress gateway initiates a new mTLS connection to the external service using a user-provided client certificate.
The external ingress gateway terminates mTLS and routes the request to the upstream
httpbinservice.
The guide builds this path incrementally. After Step 3, the egress-to-external segment is still plaintext. Step 4 upgrades that segment to mTLS, closing the security gap.
Prerequisites
Before you begin, make sure that you have:
Completed all steps in Configure mTLS service on ASM ingress gateway and restrict specific client access. That tutorial deploys the mTLS server used as the external service in this guide
A separate ASM instance and Container Service for Kubernetes (ACK) cluster as the primary environment for this guide
Automatic sidecar injection enabled. For more information, see Configure sidecar proxy injection policies
In the steps below, $INGRESS_GATEWAY_IP refers to the ingress gateway IP from the prerequisite tutorial. "ACK cluster" and "ASM instance" refer to the primary environment resources.
Set the shell variable used throughout this guide. With the kubeconfig of the ACK cluster, run:
# Replace with the actual ingress gateway IP from the prerequisite tutorial
export INGRESS_GATEWAY_IP=<ingress-gateway-ip>Step 1: Deploy the sleep test application
Deploy the sleep application as a test client. For deployment instructions, see Related operations.
Verify the deployment
With the kubeconfig of the ACK cluster, verify that the sleep pod can reach the external HTTPBin service:
kubectl exec deploy/sleep -- curl --header "host:test.com" $INGRESS_GATEWAY_IP/status/418Expected output:
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`The HTTP 418 teapot response confirms connectivity to the external HTTPBin service.
Step 2: Enable REGISTRY_ONLY and create a ServiceEntry
Restrict outbound access (optional)
Set the outbound traffic policy to REGISTRY_ONLY to block pods from reaching any service not explicitly registered through a ServiceEntry. For more information, see Step 2: Enable REGISTRY_ONLY.
After you enable REGISTRY_ONLY, requests to unregistered services return a 502 Bad Gateway error.
Register the external service
Create a ServiceEntry for test.com so that pods in the mesh can reach it. For more information, see Create an external service.
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: test-com
namespace: default
spec:
endpoints:
- address: <ingress-gateway-ip> # IP address of the external ingress gateway
hosts:
- test.com
location: MESH_EXTERNAL
ports:
- name: http
number: 80
protocol: HTTP
- name: https
number: 443
protocol: HTTPS
resolution: STATICReplace <ingress-gateway-ip> with the IP address of the ingress gateway from the prerequisite tutorial.
Verify
After you apply the ServiceEntry, run the same curl command from Step 1 to confirm that the sleep pod can still reach the HTTPBin service at test.com.
Step 3: Create the egress gateway and route HTTP traffic through it
3a. Create the egress gateway
Create an egress gateway for HTTP traffic on port 80 with mutual TLS authentication enabled. Workloads in the mesh automatically encrypt traffic to the gateway using ASM-managed certificates. For more information, see Create an egress gateway.
3b. Create gateway rules
Create a Gateway resource that declares the egress gateway listener. The gateway listens on port 80 with ISTIO_MUTUAL TLS mode (ASM-managed certificates). For more information, see Create gateway rules.
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: egress-gateway
namespace: default
spec:
selector:
istio: egressgateway
servers:
- hosts:
- '*'
port:
name: http
number: 80
protocol: HTTPS
tls:
mode: ISTIO_MUTUAL3c. Create a VirtualService
Create a VirtualService that routes test.com traffic in two stages:
Mesh to egress gateway: The sidecar forwards traffic on port 80 to the egress gateway.
Egress gateway to external service: The egress gateway forwards traffic to the
test.comServiceEntry on port 80.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: egressgateway-vs
spec:
hosts:
- test.com
gateways:
- egress-gateway # Gateway created in step 3b
- mesh
http:
- match:
- gateways:
- mesh
port: 80
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
port:
number: 80
weight: 100
- match:
- gateways:
- egress-gateway
port: 80
route:
- destination:
host: test.com
port:
number: 80
weight: 100Verify that traffic flows through the egress gateway
Access
test.comfrom thesleeppod: Expected output: the HTTP 418 teapot ASCII art (same as Step 1).kubectl exec deploy/sleep -- curl --header "host:test.com" $INGRESS_GATEWAY_IP/status/418Check the egress gateway access log. With the kubeconfig of the ASM instance, run: The log entry should show
"upstream_cluster":"outbound|80||test.com", which confirms that the request passed through the egress gateway.export EGRESS_POD=$(kubectl get pod -l istio=egressgateway -n istio-system -o jsonpath='{.items[0].metadata.name}') kubectl -n istio-system logs $EGRESS_POD | tail -1
Traffic flow at this stage
sleep pod ──mTLS──> egress gateway ──plaintext──> external ingress gateway ──mTLS──> httpbinThe egress-to-external segment is still plaintext. Step 4 upgrades this segment to mTLS.
Step 4: Upgrade egress traffic to mTLS
4a. Update the VirtualService
Update the VirtualService to route egress gateway traffic to port 443 instead of port 80. The only change is the destination port for the egress-gateway match:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: egressgateway-vs
spec:
hosts:
- test.com
gateways:
- egress-gateway
- mesh
http:
- match:
- gateways:
- mesh
port: 80
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
port:
number: 80
weight: 100
- match:
- gateways:
- egress-gateway
port: 80
route:
- destination:
host: test.com
port:
number: 443 # Changed from 80 to 443
weight: 1004b. Import the mTLS client certificate
Import the mTLS certificate from Configure mTLS service on ASM ingress gateway and restrict specific client access. Name the certificate test.client. For more information, see Use the certificate management feature of ASM.
Alternatively, create the secret directly with kubectl. With the kubeconfig of the ACK cluster, run:
kubectl create -n istio-system secret generic test.client \
--from-file=tls.key=client.key.pem \
--from-file=tls.crt=clientcert.pem \
--from-file=ca.crt=cacert.pem4c. Create a DestinationRule for mTLS origination
Create a DestinationRule that configures the egress gateway to initiate mTLS when connecting to test.com on port 443:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: originate-mtls-for-test-com
spec:
host: test.com
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
portLevelSettings:
- port:
number: 443
tls:
mode: MUTUAL
credentialName: test.client
sni: test.com| Field | Description |
|---|---|
mode: MUTUAL | Enables mutual TLS. The egress gateway presents a client certificate and validates the server certificate. |
credentialName | References the test.client secret containing the client certificate, key, and CA certificate. |
sni | Sets the Server Name Indication (SNI) extension to test.com for the TLS handshake. |
Verify mTLS origination
Test a path that the client certificate is allowed to access: Expected output:
kubectl exec deploy/sleep -it -- curl --header "host:test.com" $INGRESS_GATEWAY_IP/status/200 -IHTTP/1.1 200 OK server: envoy date: Mon, 29 Jul 2024 03:33:50 GMT content-type: text/html; charset=utf-8 access-control-allow-origin: * access-control-allow-credentials: true content-length: 0 x-envoy-upstream-service-time: 5Test a path that the client certificate is denied from accessing: Expected output: The request is denied because the
test.clientcertificate is configured in the prerequisite tutorial to block access to the/status/418path.kubectl exec deploy/sleep -it -- curl --header "host:test.com" $INGRESS_GATEWAY_IP/status/418RBAC: access denied%Confirm the egress gateway connects over port 443. With the kubeconfig of the ASM instance, run: The log entry should show
"upstream_cluster":"outbound|443||test.com"and"upstream_host":"...:443", which confirms the egress gateway connects to the external service over the mTLS port.kubectl -n istio-system logs $EGRESS_POD | tail -1
Traffic flow after mTLS upgrade
sleep pod ──mTLS──> egress gateway ──mTLS──> external ingress gateway ──mTLS──> httpbinAll segments are now encrypted with mTLS. The plaintext gap from Step 3 is eliminated.
Step 5: Configure authorization policies
With end-to-end mTLS in place, the mesh authenticates client identities at every hop. Authorization policies enforce fine-grained access control at two points.
Restrict access at the egress gateway
Apply an AuthorizationPolicy on the egress gateway to control which in-mesh services can reach test.com. The following example denies the sleep service account access to the /headers path:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
labels:
gateway: egressgateway
name: test
namespace: istio-system
spec:
action: DENY
rules:
- from:
- source:
principals:
- cluster.local/ns/default/sa/sleep
to:
- operation:
hosts:
- test.com
paths:
- /headers
selector:
matchLabels:
istio: egressgatewayAfter you apply this policy, requests from the sleep pod to test.com/headers through the egress gateway are denied.
Restrict access at the external ingress gateway
The AuthorizationPolicy on the external ingress gateway (from Configure mTLS service on ASM ingress gateway and restrict specific client access) restricts access based on client certificate identity. The prerequisite tutorial policy prevents the test.client certificate from accessing /status/418.
Together, these two authorization points provide layered access control:
| Authorization point | Scope |
|---|---|
| Egress gateway policies | Control which in-mesh services can reach which external hosts and paths. |
| External ingress gateway policies | Control which client certificates can access which backend paths. |