Egress gateways
An egress gateway is a dedicated proxy that all outbound traffic to external services must pass through. It provides a single, verifiable exit point for traffic leaving the mesh, where you can apply authorization policies, enable observability, and originate TLS.
In sidecar mode, configuring an egress gateway for a single host requires coordinating five separate objects: a ServiceEntry, a Gateway, two HTTPRoute resources (one to steer mesh traffic into the gateway, one to forward traffic from the gateway to the destination), and a DestinationRule. Each new external host repeats most of that work.
In ambient mode, a waypoint proxy naturally acts as an egress gateway. Ztunnel automatically routes traffic to a service’s waypoint before forwarding it to the destination. If you place a ServiceEntry in a namespace enrolled to use a waypoint, all mesh traffic to that external host passes through the waypoint automatically, with no extra routing rules required.
Before you begin
Install Istio with ambient mode enabled.
Deploy a workload to use as a traffic source. The curl sample works well:
$ kubectl apply -f @samples/curl/curl.yaml@Label the workload’s namespace for ambient mode so ztunnel intercepts its traffic:
$ kubectl label namespace default istio.io/dataplane-mode=ambient
Set up the egress namespace
Create a dedicated namespace for egress resources. Isolating external service definitions and their policies from application namespaces simplifies administration and reduces the blast radius of misconfigurations.
$ kubectl create namespace istio-egress
$ kubectl label namespace istio-egress istio.io/dataplane-mode=ambientDeploy the egress waypoint
Deploy a waypoint proxy in the egress namespace and enroll the namespace to use it. The --enroll-namespace flag adds the istio.io/use-waypoint label to the namespace, so every service defined there, including those backed by ServiceEntry, will be routed through the waypoint.
Note that the Kubernetes Gateway API CRDs do not come installed by default on most Kubernetes clusters, so make sure they are installed before using the Gateway API:
$ kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.1/experimental-install.yaml$ istioctl waypoint apply --for service --enroll-namespace --namespace istio-egress
✅ waypoint istio-egress/waypoint applied
✅ namespace istio-egress labeled with "istio.io/use-waypoint: waypoint"Confirm the waypoint is ready:
$ istioctl waypoint list -n istio-egress
NAME REVISION TRAFFIC TYPE PROGRAMMED
waypoint default service TrueDefine an external service
Create a ServiceEntry in the egress namespace to represent the external host. Because the namespace is enrolled to use the waypoint, ztunnel routes all mesh traffic to this host through the waypoint automatically. The ServiceEntry is visible cluster-wide by default (exportTo: *), so ztunnel on every node resolves httpbin.org to the istio-egress waypoint without any additional configuration.
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: httpbin-org
namespace: istio-egress
spec:
hosts:
- httpbin.org
ports:
- number: 80
name: http
protocol: HTTP
resolution: DNS
EOFVerify traffic routes through the egress waypoint
Send a request from the curl pod to the external host and confirm it reaches the destination:
$ kubectl exec deploy/curl -- curl -s -o /dev/null -w "%{http_code}" http://httpbin.org/get
200To confirm traffic traversed the waypoint, check the waypoint’s Envoy stats:
$ kubectl exec -n istio-egress deploy/waypoint -c istio-proxy -- pilot-agent request GET stats | grep upstream_rq_totalA non-zero upstream_rq_total count (the number of requests the waypoint forwarded upstream) confirms the waypoint is acting as the egress gateway.
Enforce access policies
Because traffic passes through the waypoint, you can attach Layer 7 authorization policies directly to the ServiceEntry. The policy below allows any source to issue GET requests to /get only:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-org
namespace: istio-egress
spec:
targetRefs:
- kind: ServiceEntry
group: networking.istio.io
name: httpbin-org
action: ALLOW
rules:
- to:
- operation:
methods: ["GET"]
paths: ["/get"]
EOFAfter applying the policy, verify that allowed requests succeed:
$ kubectl exec deploy/curl -- curl -s -o /dev/null -w "%{http_code}" http://httpbin.org/get
200Confirm that a disallowed request is rejected:
$ kubectl exec deploy/curl -- curl -s -o /dev/null -w "%{http_code}" -X POST http://httpbin.org/post
403Originate TLS at the egress gateway
Application pods can send plaintext HTTP to the egress gateway; the gateway upgrades to HTTPS before forwarding to the external host. This concentrates TLS credential management at the gateway and avoids distributing certificates to each application pod.
Update the ServiceEntry to map the plaintext port to the TLS port, and add a DestinationRule to originate the TLS connection:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: httpbin-org
namespace: istio-egress
spec:
hosts:
- httpbin.org
ports:
- number: 80
name: http
protocol: HTTP
targetPort: 443
resolution: DNS
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: httpbin-org-tls
namespace: istio-egress
spec:
host: httpbin.org
trafficPolicy:
tls:
mode: SIMPLE
EOFVerify that the application still receives a response, now sent over HTTPS by the gateway:
$ kubectl exec deploy/curl -- curl -s http://httpbin.org/get | head -5Add an external service without TLS origination
To expose additional external hosts through the same egress waypoint, create another ServiceEntry in the same namespace. No additional waypoint configuration is needed because the namespace is already enrolled:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: example-com
namespace: istio-egress
spec:
hosts:
- example.com
ports:
- number: 80
name: http
protocol: HTTP
resolution: DNS
EOFVerify that traffic to the new host also routes through the waypoint:
$ kubectl exec deploy/curl -- curl -s -o /dev/null -w "%{http_code}" http://example.com
200Each ServiceEntry in the namespace is automatically routed through the waypoint and can carry its own AuthorizationPolicy.
Cleanup
$ kubectl delete namespace istio-egress
$ kubectl delete -f @samples/curl/curl.yaml@
$ kubectl label namespace default istio.io/dataplane-mode-See also
- Configure waypoint proxies: general waypoint deployment and enrollment
- Require traffic to traverse the waypoint: enforce that egress traffic cannot bypass the waypoint
- ServiceEntry visibility: control which namespaces can discover each
ServiceEntry - Use Layer 7 features: full list of L7 policies and routes available at a waypoint
- Egress gateways (sidecar mode): the equivalent sidecar-mode configuration for comparison