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:

    Zip
    $ 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=ambient

Deploy 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       True

Define 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
EOF

Verify 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
200

To 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_total

A 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"]
EOF

After applying the policy, verify that allowed requests succeed:

$ kubectl exec deploy/curl -- curl -s -o /dev/null -w "%{http_code}" http://httpbin.org/get
200

Confirm that a disallowed request is rejected:

$ kubectl exec deploy/curl -- curl -s -o /dev/null -w "%{http_code}" -X POST http://httpbin.org/post
403

Originate 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
EOF

Verify 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 -5

Add 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
EOF

Verify 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
200

Each ServiceEntry in the namespace is automatically routed through the waypoint and can carry its own AuthorizationPolicy.

Cleanup

Zip
$ kubectl delete namespace istio-egress
$ kubectl delete -f @samples/curl/curl.yaml@
$ kubectl label namespace default istio.io/dataplane-mode-

See also

Was this information useful?
Do you have any suggestions for improvement?

Thanks for your feedback!