Route all egress traffic for an external service through an egress gateway and ensure that only certain services in the mesh can send requests to the external endpoint that it serves. All other services in the mesh are blocked from sending requests to the external endpoint. You can also use the egress traffic to perform TLS origination and secure the connection to the external endpoint. For more information, see Perform TLS origination with egress gateways.

Before you begin

  1. If you have not already, set the following environment variables.

    1. Save the patch version and tag of the Solo distribution of Istio that you previously installed.
        export ISTIO_VERSION=1.25.2
      # Change the tags as needed
      export ISTIO_IMAGE=${ISTIO_VERSION}-solo
        
    2. Save the repo key for the minor version of the Solo distribution of Istio that you installed. This is the 12-character hash at the end of the repo URL us-docker.pkg.dev/gloo-mesh/istio-<repo-key>, which you can find in the Istio images built by Solo.io support article.
        # 12-character hash at the end of the minor version repo URL
      export REPO_KEY=<repo_key>
      export REPO=us-docker.pkg.dev/gloo-mesh/istio-${REPO_KEY}
      export HELM_REPO=us-docker.pkg.dev/gloo-mesh/istio-helm-${REPO_KEY}
        
    3. Get the revision that you used for your installation. Typically, this is main for a Helm installation, or gloo for a Gloo Operator installation.
        export REVISION=$(kubectl get pod -l app=istiod -n istio-system -o jsonpath='{.items[0].metadata.labels.istio\.io/rev}')      
      echo ${REVISION}
        
    4. Save the name and kubeconfig context of a cluster where you want to install the egress gateway. In a multicluster setup, this can be any workload cluster where you installed a service mesh and the Bookinfo sample app, such as $REMOTE_CLUSTER1.
        export CLUSTER_NAME=<cluster-name>
      export CLUSTER_CONTEXT=<cluster-context>
        
  2. Decide on the outbound traffic policy for your service mesh. In minimal Istio installations, the outbound traffic policy for your service mesh is typically set to ALLOW_ANY by default and allows your services in the mesh to reach any external endpoint. You can change this setting and instead block all egress traffic to hosts that are not part of your service mesh by changing the outbound traffic policy to REGISTRY_ONLY as shown in the following steps.

Create an egress gateway

  1. Prepare a Helm values file for the Istio egress gateway. This sample command downloads an example file, egress-gateway.yaml, and updates the environment variables with the values that you previously set. You can further edit the file to provide your own details for production-level settings.

      curl -0L https://raw.githubusercontent.com/solo-io/gloo-mesh-use-cases/main/gloo-mesh-enterprise/istio-install/manual-helm/egress-gateway.yaml > egress-gateway.yaml
    envsubst < egress-gateway.yaml > egress-gateway-values.yaml
      
  2. Create the egress gateway.

      helm upgrade --install istio-egressgateway oci://${HELM_REPO}/gateway \
      --version ${ISTIO_VERSION} \
      --namespace istio-egress \
      --create-namespace \
      --kube-context ${CLUSTER_CONTEXT} \
      --wait \
      -f egress-gateway-values.yaml
      
  3. Verify that your egress gateway pod is running.

      kubectl get pods -n istio-egress --context ${CLUSTER_CONTEXT}
      

    Example output:

      NAME                                        READY   STATUS    RESTARTS   AGE
    istio-egressgateway-gloo-55fcbddd96-bwntr   1/1     Running   0          25s
      

Route through the gateway

  1. Create the global namespace in your workload cluster.

      kubectl create namespace global --context ${CLUSTER_CONTEXT}
      
  2. Create a virtual gateway resource to configure your egress gateway.

    SettingDescription
    spec.listeners.exposedExternalServices.hostSelect the external services that you want to serve on this egress gateway. To select the external services, you define the hostname that must be configured in the external service. To use a wildcard, enter the wildcard as one host, such as - host: "*.com". Then, enter the Kubernetes service FQDN of the egress gateways that route to the wildcard host, such as istio-egressgateway.istio-egress.svc.cluster.local.
    spec.listeners.portEnter the port number that services in the mesh use in combination with the host to send a request to the egress gateway.
    spec.listeners.tlsSelect the TLS mode that you want to use to connect to the egress gateway from within the mesh. In this example, requests for the httpbin.org host are rerouted to the egress gateway first by using mutual TLS. The incoming TLS connection is terminated at the egress gateway.
  3. Create an external service for httpbin.org.

      kubectl apply --context ${CLUSTER_CONTEXT} -f- <<EOF
    apiVersion: networking.gloo.solo.io/v2
    kind: ExternalService
    metadata:
      name: httpbin.org
      namespace: global
    spec:
      hosts:
      - "httpbin.org"
      ports:
        - clientsideTls: {}
          egressGatewayRoutes: 
            portMatch: 80
            virtualGatewayRefs:
              - cluster: ${CLUSTER_NAME}
                name: egress-vg
                namespace: istio-egress
          name: https
          number: 443
          protocol: HTTPS
    EOF
      
    SettingDescription
    spec.hostsEnter the external hostname that you want to allow access to for services in the mesh.
    spec.ports.clientsideTlsAllow TLS origination to the httpbin.org endpoint.
    spec.ports.egressGatewayRoutes.virtualGatewayRefsEnter the details for your egress gateway. Make sure to enter the egress gateway details for both of your workload clusters.
    spec.ports.egressGatewayRoutes.portMatchSelect the port that clients use when sending a request to the external endpoint. Note that this port does not represent the port that the client’s sidecar proxy uses to connect to the egress gateway. Instead, make sure to enter the source port that is used in the original client request.
    spec.ports.nameEnter the port number that you want to use to connect to the external endpoint. When a client request is sent to the gateway, the client’s sidecar proxy intercepts this traffic and securely connects to the egress gateway via mutual TLS. The egress gateway then terminates the incoming TLS connection. Because this example defines an HTTPS port, a new TLS connection is originated at the egress gateway to securely connect to the httpbin.org external endpoint.
  4. Verify that you can access httpbin.org from the productpage app.

      kubectl --context ${CLUSTER_CONTEXT} -n bookinfo exec deploy/productpage-v1 -c curl -- curl -vik "httpbin.org/get"
      

    Example output:

      ...
    < HTTP/1.1 200 OK
    < date: Thu, 05 Jun 2025 20:30:30 GMT
    < content-type: application/json
    < content-length: 2596
    < server: envoy
    < access-control-allow-origin: *
    < access-control-allow-credentials: true
    < x-envoy-upstream-service-time: 567
    ...
      
  5. Optional: If you enabled Istio access logs, you can review the logs of the egress gateway and verify that you can see a log entry for the request from productpage to httpbin.org through the egress gateway.

      kubectl logs $(kubectl get pod --context ${CLUSTER_CONTEXT} -l app=istio-egressgateway -A -o jsonpath='{.items[0].metadata.name}') -n istio-egress --context ${CLUSTER_CONTEXT}
      

    Example output:

      {"authority":"httpbin.org","bytes_received":0,"bytes_sent":1045,"connection_termination_details":null,"downstream_local_address":"10.0.XX.XX:80","downstream_remote_address":"10.0.XX.XX:34760","duration":3226,"method":"GET","path":"/get","protocol":"HTTP/2","request_id":"c75481d6-a2df-4815-9a7b-7b94deba2097","requested_server_name":null,"response_code":200,"response_code_details":"via_upstream","response_flags":"-","route_name":null,"start_time":"2025-06-05T21:02:20.806Z","upstream_cluster":"outbound|80||httpbin.org","upstream_host":"52.202.28.30:80","upstream_local_address":"10.0.XX.XX:57306","upstream_service_time":"3226","upstream_transport_failure_reason":null,"user_agent":"curl/7.83.1-DEV","x_forwarded_for":"10.0.XX.XX"}
      
  6. Get the virtual service that was created for your external service and make sure that it lists port 443 for the httpbin.org destination. This setting ensures the egress gateway originates a new TLS connection before connecting to httpbin.org.

      kubectl --context ${CLUSTER_CONTEXT} describe vs -l gloo.solo.io/parent_name=httpbin.org -n istio-egress
      

    Example output:

      ...
    spec:
      exportTo:
      - bookinfo
      - default
      - global
      - gloo-mesh
      - helloworld
      - httpbin
      - istio-egress
      - istio-system
      gateways:
      - virtualgateway-egress-vg-gloo-m-ef7dba3d044465c79ef179255be34f5
      - mesh
      hosts:
      - httpbin.org
      http:
      - match:
        - gateways:
          - virtualgateway-egress-vg-gloo-m-ef7dba3d044465c79ef179255be34f5
          port: 443
        route:
        - destination:
            host: httpbin.org
            port:
              number: 443
    ...
      

Block traffic with an access policy

  1. Create an access policy for your external service to allow only the product page app to access the httpbin.org external endpoint.

      kubectl apply --context ${CLUSTER_CONTEXT} -f- <<EOF
    apiVersion: security.policy.gloo.solo.io/v2
    kind: AccessPolicy
    metadata:
      name: egress-access-policy
      namespace: istio-egress
    spec:
      applyToDestinations:
      - kind: EXTERNAL_SERVICE
        port:
          number: 443
        selector:
          cluster: ${CLUSTER_NAME}
          name: httpbin.org
          namespace: global
      config:
        authz:
          allowedClients:
          - serviceAccountSelector:
              cluster: ${CLUSTER_NAME}
              labels:
                account: productpage
              namespace: bookinfo
    EOF
      
  2. Send a request to httpbin.org from the reviews app and verify that this request is denied with a 403 HTTP response code.

      kubectl --context ${CLUSTER_CONTEXT} -n bookinfo debug -i pods/$(kubectl get pod --context ${CLUSTER_CONTEXT} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -vik httpbin.org
      

    Example output:

      * Connected to httpbin.org (240.240.59.19) port 80 (#0)
    > GET / HTTP/1.1
    > Host: httpbin.org
    > User-Agent: curl/8.1.1-DEV
    > Accept: */*
    > 
    < HTTP/1.1 403 Forbidden
    < content-length: 19
    < content-type: text/plain
    < date: Wed, 24 May 2023 20:58:55 GMT
    < server: envoy
    < x-envoy-upstream-service-time: 13
    < 
    { [19 bytes data]
    HTTP/1.1 403 Forbidden
    * Connection #0 to host httpbin.org left intact
    content-length: 19
    content-type: text/plain
    date: Wed, 24 May 2023 20:58:55 GMT
    server: envoy
    x-envoy-upstream-service-time: 13
    RBAC: access denied%       
      
  3. Send another request to httpbin.org from the product page app and verify that this request is accepted.

      kubectl --context ${CLUSTER_CONTEXT} -n bookinfo exec deploy/productpage-v1 -c curl -- curl -vik "httpbin.org/get"
      

    Example output:

      * Connected to httpbin.org (240.240.59.19) port 80 (#0)
    > GET / HTTP/1.1
    > Host: httpbin.org
    > User-Agent: curl/8.1.1-DEV
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < date: Wed, 24 May 2023 21:01:44 GMT
    < expires: -1
    < cache-control: private, max-age=0
    < content-type: text/html; charset=ISO-8859-1
    HTTP/1.1 200 OK
      

Cleanup

You can optionally remove the resources that you set up as part of this guide.
  • Policy:
      kubectl delete accesspolicy egress-access-policy -n istio-egress --context ${CLUSTER_CONTEXT}
      
  • Gateway and routing resources:
      kubectl delete externalservice httpbin.org -n global --context ${CLUSTER_CONTEXT}
    kubectl delete virtualgateway egress-vg -n istio-egress --context ${CLUSTER_CONTEXT}
    kubectl delete ns global --context ${CLUSTER_CONTEXT}
      
  • Istio egress gateway:
      helm uninstall istio-egressgateway -n istio-egress --kube-context ${CLUSTER_CONTEXT}
    kubectl delete namespace istio-egress --context ${CLUSTER_CONTEXT}