Skip to content

Egress Gateway Setup and Policies

This guide covers setting up an Istio egress gateway with Kuadrant and applying rate limiting, workload identity, and authentication policies to outbound traffic.

Prerequisites

  • Kubernetes cluster with Kuadrant operator and Istio installed. See the Getting Started guide.

Set up the egress gateway infrastructure:

curl -sL https://raw.githubusercontent.com/Kuadrant/kuadrant-operator/refs/heads/main/hack/setup-egress.sh | bash

This deploys a Gateway (kuadrant-egressgateway), ServiceEntry and DestinationRule for httpbin.org with TLS origination, an HTTPRoute, a test-client pod, and a dev Vault instance with Kubernetes auth. The examples in this guide use:

Resource Value
Gateway namespace gateway-system
Gateway name kuadrant-egressgateway
External service httpbin.org

Export the gateway address for use in examples:

export EGRESS_IP=$(kubectl get gtw kuadrant-egressgateway -n gateway-system \
    -o jsonpath='{.status.addresses[0].value}')

Egress Gateway Resources

The setup script deployed the following resources.

Gateway

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: kuadrant-egressgateway
  namespace: gateway-system
  annotations:
    networking.istio.io/service-type: ClusterIP
spec:
  gatewayClassName: istio
  listeners:

    - name: http
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: All

ServiceEntry

Registers the external service in Istio's service registry, making the hostname routable:

apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: httpbin-external
  namespace: gateway-system
spec:
  hosts:

    - httpbin.org
  ports:
    - number: 443
      name: https
      protocol: HTTPS
  location: MESH_EXTERNAL
  resolution: DNS

DestinationRule

Configures TLS origination - the gateway establishes TLS to the external service:

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: httpbin-external
  namespace: gateway-system
spec:
  host: httpbin.org
  trafficPolicy:
    tls:
      mode: SIMPLE
      sni: httpbin.org

HTTPRoute

Routes traffic matching the external hostname through the gateway to the external service:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httpbin-external
  namespace: gateway-system
spec:
  parentRefs:

    - name: kuadrant-egressgateway
      namespace: gateway-system
  hostnames:
    - httpbin.org
  rules:
    - filters:
        - type: URLRewrite
          urlRewrite:
            hostname: httpbin.org
      backendRefs:
        - group: networking.istio.io
          kind: Hostname
          name: httpbin.org
          port: 443

The Hostname backend is provided by the ServiceEntry. The URLRewrite filter ensures the correct Host header reaches the external service.

Rate Limiting

RateLimitPolicy works on egress using the same attachment model as ingress - the same targetRef, the same Limitador limits, the same WasmPlugin enforcement. One caveat: source.address cannot be used as a counter expression because it includes the ephemeral port, giving each connection its own bucket. Use workload identity with auth.identity.username for per-workload limiting instead.

Basic Egress Rate Limiting

A simple rate limit on all egress traffic through a route:

kubectl apply -f - <<'EOF'
apiVersion: kuadrant.io/v1
kind: RateLimitPolicy
metadata:
  name: egress-ratelimit
  namespace: gateway-system
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: httpbin-external
  limits:
    global:
      rates:

        - limit: 100
          window: 1m
EOF

This limits all traffic through the httpbin-external route to 100 requests per minute, regardless of which workload is sending.

Verify:

# Send requests until rate limited
kubectl exec test-client -n egress-test -- \
    curl -s -o /dev/null -w "%{http_code}" -H "Host: httpbin.org" http://${EGRESS_IP}/get
# 200 (until limit reached, then 429)

Other Rate Limiting Patterns

Standard RateLimitPolicy patterns (per-route, gateway-level, conditional with when predicates, defaults/overrides) all work on egress. For other configurations, see:

Pattern Guide
Basic per-route rate limiting Simple RL for App Developers
Gateway-level rate limiting Gateway RL for Cluster Operators
Per-identity with API keys Authenticated RL for App Developers
Per-identity with JWT + K8s RBAC Authenticated RL with JWTs and K8s AuthNZ

Workload Identity

In egress, the clients are internal workloads. To identify which workload is making a request, use Kubernetes TokenReview via AuthPolicy. By default, every pod has a ServiceAccount token mounted automatically - no API keys to distribute.

The workload sends its SA token in the Authorization header:

curl -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
     -H "Host: httpbin.org" http://${EGRESS_IP}/get

AuthPolicy validates the token and exposes the workload's identity (namespace, service account name) for use by other policies. Requests without a valid SA token are rejected with 401.

The following AuthPolicy authenticates workloads and restricts egress access to specific namespaces:

kubectl apply -f - <<'EOF'
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
  name: workload-identity
  namespace: gateway-system
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: httpbin-external
  rules:
    authentication:
      "workload-sa":
        kubernetesTokenReview:
          audiences:

            - "https://kubernetes.default.svc.cluster.local"
    authorization:
      "allowed-namespaces":
        patternMatching:
          patterns:
            - predicate: auth.identity.user.username.startsWith('system:serviceaccount:egress-test:')
EOF
  • authentication.workload-sa - validates the SA token via TokenReview. Unauthenticated requests are rejected (401).
  • authorization.allowed-namespaces - restricts access to workloads from the egress-test namespace. Workloads from other namespaces are rejected (403). Add additional patterns with || to allow more namespaces.

Verify access control:

# Authorized workload - 200
kubectl exec test-client -n egress-test -- sh -c '
curl -s -o /dev/null -w "%{http_code}" \
    -H "Host: httpbin.org" \
    -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
    http://'"${EGRESS_IP}"'/get
'

# No token - 401
kubectl exec test-client -n egress-test -- \
    curl -s -o /dev/null -w "%{http_code}" -H "Host: httpbin.org" http://${EGRESS_IP}/get

# Unauthorized namespace - 403
kubectl run bad-client --image=curlimages/curl:latest -n default --restart=Never \
    --command -- sleep infinity
kubectl wait --for=condition=Ready pod/bad-client -n default --timeout=30s
kubectl exec bad-client -n default -- sh -c '
curl -s -o /dev/null -w "%{http_code}" -H "Host: httpbin.org" \
    -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
    http://'"${EGRESS_IP}"'/get
'
kubectl delete pod bad-client -n default

Per-Workload Rate Limiting

With workload identity established, you can give each workload its own rate limit bucket by adding an RLP that uses the SA username as a counter:

kubectl apply -f - <<'EOF'
apiVersion: kuadrant.io/v1
kind: RateLimitPolicy
metadata:
  name: egress-per-workload
  namespace: gateway-system
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: httpbin-external
  limits:
    per-workload:
      rates:

        - limit: 10
          window: 1m
      counters:
        - expression: "auth.identity.username"
EOF

Each ServiceAccount gets an independent 10 req/min bucket. system:serviceaccount:team-a:default and system:serviceaccount:team-b:default are tracked separately. The identity is stable across pod restarts and reschedules.

Workloads must include their SA token in requests:

kubectl exec test-client -n egress-test -- sh -c '
curl -s -o /dev/null -w "%{http_code}" \
    -H "Host: httpbin.org" \
    -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
    http://'"${EGRESS_IP}"'/get
'

The resolved identity contains:

Field Example value Access path (AuthPolicy selectors) Access path (RLP expressions)
Username system:serviceaccount:egress-test:default auth.identity.user.username auth.identity.username
UID 425214f1-cf45-... auth.identity.user.uid auth.identity.uid
Groups [system:serviceaccounts, system:serviceaccounts:egress-test] auth.identity.user.groups auth.identity.groups

Note: AuthPolicy selectors and RateLimitPolicy expressions resolve identity paths differently. AuthPolicy uses the full TokenReview structure (auth.identity.user.username), while RLP counter expressions use a flattened representation (auth.identity.username). This is by design - the two are evaluated by different engines (Authorino vs. wasm-shim).

Credential Injection

Beyond the access control pattern above, the primary egress use case for AuthPolicy is credential injection - fetching external API credentials from a secret store (e.g., Vault) and injecting them into outbound requests.

See the Credential Injection guide for a full walkthrough covering:

  • Vault integration with Kubernetes auth method
  • Per-identity credential paths (secret/egress/<namespace>/<sa-name>)
  • Two-layer security model (TokenReview + Vault authorization)

Considerations

ClusterIP vs LoadBalancer

The Gateway example above uses the networking.istio.io/service-type: ClusterIP annotation because egress traffic originates inside the cluster - no external IP is needed. If you need workloads in other clusters to reach this egress gateway, use LoadBalancer instead.

Application Must Send HTTP

For Kuadrant policies to inspect request headers and apply rate limiting, the application must send plain HTTP to the egress gateway. The DestinationRule handles TLS origination to the external service. If the application sends HTTPS, the gateway cannot inspect the encrypted traffic.

Resource Ownership

Resource Managed by Purpose
Gateway User Egress gateway infrastructure
ServiceEntry User Registers external service in Istio
DestinationRule User TLS origination configuration
HTTPRoute User Routes traffic to external service
AuthPolicy, RateLimitPolicy User Policy definitions
WasmPlugin, EnvoyFilter Kuadrant (auto-generated) Policy enforcement in the data plane
AuthConfig, Limitador Limits Kuadrant (auto-generated) Policy decisions consumed by Authorino and Limitador

Limitations

  • Istio only - Egress gateway support targets Istio as the Gateway API provider. Envoy Gateway is not supported for egress at this time.
  • ServiceEntry and DestinationRule are user-managed - Kuadrant does not create or manage these Istio resources.

Next Steps

  • DNS Routing - configure how pods reach the egress gateway (internal hostname, CoreDNS)
  • Credential Injection - inject external API credentials (e.g., from Vault) into outbound requests

References