For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Block egress traffic with an egress gateway
Use an egress gateway to allow egress traffic to external endpoints for only certain services in the mesh.
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.
Warning
Blocking egress traffic with an egress gateway and access policies works only if clients send traffic to the egress gateway through the client’s sidecar proxy. If the sidecar proxy is bypassed, requests to an external endpoint might still be allowed, which can be a risk for malicious attacks. To make sure that no egress traffic is allowed, even if the client’s sidecar proxy is bypassed, create a network policy that blocks all egress traffic in addition to setting up an egress gateway. You can further reduce the surface for malicious attacks by deploying the egress gateway to a dedicated node. Also note that you cannot apply other policies, such as outlier detection or active healthchecks, to the egress gateway.
Before you begin
If you have not already, set the following environment variables.
- Save the name of your cluster in an environment variable.
export CLUSTER_NAME=<custer-name> - Save the patch version and tag of the Solo distribution of Istio that you previously installed.
export ISTIO_VERSION=1.31.1 # Change the tags as needed export ISTIO_IMAGE=${ISTIO_VERSION}-solo - 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} - Get the revision that you used for your installation. Typically, this is
mainfor a Helm installation, orgloofor 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} - 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>
- Save the name of your cluster in an environment variable.
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_ANYby 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 toREGISTRY_ONLYas shown in the following steps.If you use the Gloo Operator to manage Istio installations, create the following ConfigMap. For the Gloo Operator to pick up the ConfigMap, the name must be
gloo-extensions-configand the namespace must be the same as the Gloo Operator, such asgloo-mesh.kubectl apply -f- <<EOF apiVersion: v1 kind: ConfigMap metadata: name: gloo-extensions-config namespace: gloo-mesh data: values.istiod: | meshConfig: outboundTrafficPolicy: REGISTRY_ONLY EOF- Get the current values for the istiod Helm release in your cluster. Your release might have a different name.
helm get values istiod -n istio-system --kube-context ${CLUSTER_CONTEXT} -o yaml > istiod.yaml open istiod.yaml - Set
meshConfig.outboundTrafficPolicytoREGISTRY_ONLY, and save the file. - Upgrade your Helm release with the updated values.
helm upgrade istiod oci://${HELM_REPO}/istiod \ -n istio-system \ --kube-context ${CLUSTER_CONTEXT} \ -f istiod.yaml
- Get the current values for the istiod Helm release in your cluster. Your release might have a different name.
Create an egress gateway
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.yamlCreate 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.yamlVerify 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
Create the
globalnamespace in your workload cluster.kubectl create namespace global --context ${CLUSTER_CONTEXT}Create a virtual gateway resource to configure your egress gateway.
kubectl apply --context ${CLUSTER_CONTEXT} -f- <<EOF apiVersion: networking.gloo.solo.io/v2 kind: VirtualGateway metadata: labels: app: istio-egressgateway istio: egressgateway name: egress-vg namespace: istio-egress spec: listeners: - http: {} appProtocol: HTTPS exposedExternalServices: - host: httpbin.org port: number: 443 tls: mode: ISTIO_MUTUAL workloads: - selector: labels: app: istio-egressgateway istio: egressgateway EOFkubectl apply --context ${CLUSTER_CONTEXT} -f- <<EOF apiVersion: networking.gloo.solo.io/v2 kind: VirtualGateway metadata: labels: app: istio-egressgateway istio: egressgateway name: egress-vg namespace: istio-egress spec: listeners: - http: {} appProtocol: HTTPS exposedExternalServices: - host: "*.com" - host: istio-egressgateway.istio-egress.svc.cluster.local port: number: 443 tls: mode: ISTIO_MUTUAL workloads: - selector: labels: app: istio-egressgateway istio: egressgateway EOFSetting Description 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 asistio-egressgateway.istio-egress.svc.cluster.local. Do not expose a wildcard host and an exact host that the wildcard covers on the same listener, such as*.comalongsidehttpbin.org. Istio merges the two into one virtual host. Which external service serves the exact host is not guaranteed, so the exact host might be served with the TLS settings of the wildcard external service. The virtual gateway reports a warning for this overlap.spec.listeners.portEnter the port number that services in the mesh use in combination with the hostto 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.orghost are rerouted to the egress gateway first by using mutual TLS. The incoming TLS connection is terminated at the egress gateway.Create an external service for the external endpoint. Use the tab that matches the virtual gateway that you created.
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 EOFkubectl apply --context ${CLUSTER_CONTEXT} -f- <<EOF apiVersion: networking.gloo.solo.io/v2 kind: ExternalService metadata: name: wildcard-com namespace: global spec: hosts: - "*.com" ports: - clientsideTls: {} egressGatewayRoutes: portMatch: 80 virtualGatewayRefs: - cluster: ${CLUSTER_NAME} name: egress-vg namespace: istio-egress name: https number: 443 protocol: HTTPS EOFA wildcard host cannot use a
DestinationRule, so the egress gateway originates TLS through an Envoy dynamic forward proxy instead. The upstream port is always 443, and the upstream certificate must include a subject alternative name (SAN) that matches the requested host. To present a client certificate to the upstream, see Originate mutual TLS to a wildcard host.Setting Description spec.hostsEnter the external hostname that you want to allow access to for services in the mesh. To allow a set of hostnames, enter a wildcard host, such as "*.com", that the virtual gateway also exposes.spec.ports.clientsideTlsAllow TLS origination to the external endpoint. Leave empty to originate one-way TLS. 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.orgexternal endpoint.Verify that you can access
httpbin.orgfrom 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 ...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.orgthrough 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"}Get the virtual service that was created for your external service and make sure that it lists port 443 for the
httpbin.orgdestination. This setting ensures the egress gateway originates a new TLS connection before connecting tohttpbin.org.kubectl --context ${CLUSTER_CONTEXT} describe vs -l gloo.solo.io/parent_name=httpbin.org -n istio-egressExample 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 ...
Originate mutual TLS to a wildcard host
Some external endpoints require the client to present a certificate. An exact host presents one through a DestinationRule, which a wildcard host cannot use. Instead, the client certificate is configured on the dynamic forward proxy that serves the wildcard host.
These steps change the wildcard external service that you created in Route through the gateway. Apply them only for an upstream that requires a client certificate. The caCertificates field replaces the trust anchor for every host that the wildcard matches, so a public endpoint such as httpbin.org then fails verification.
Mount the client certificate, the private key, and the CA certificates onto the egress gateway. The egress gateway is the workload that presents the certificate, so the files must be on its own filesystem. A path that exists only on a client workload has no effect.
- Create a secret in the egress gateway namespace that holds the three files.
kubectl create secret generic egress-client-certs \ --from-file=client.crt=<path-to>/client.crt \ --from-file=client.key=<path-to>/client.key \ --from-file=upstream-ca.crt=<path-to>/upstream-ca.crt \ -n istio-egress --context ${CLUSTER_CONTEXT} - Add the secret to
egress-gateway-values.yaml, the values file that you created in Create an egress gateway. Do not mount to/etc/certs, which Istio uses for its own certificates.volumes: - name: egress-client-certs secret: secretName: egress-client-certs volumeMounts: - name: egress-client-certs mountPath: /etc/egress-certs readOnly: true - Upgrade the egress gateway with the updated values.
helm upgrade --install istio-egressgateway oci://${HELM_REPO}/gateway \ --version ${ISTIO_VERSION} \ --namespace istio-egress \ --kube-context ${CLUSTER_CONTEXT} \ --wait \ -f egress-gateway-values.yaml
- Create a secret in the egress gateway namespace that holds the three files.
Verify that the egress gateway can read the certificate files.
export EGRESS_POD=$(kubectl get pod --context ${CLUSTER_CONTEXT} -n istio-egress -l app=istio-egressgateway -o jsonpath='{.items[0].metadata.name}') kubectl exec ${EGRESS_POD} -n istio-egress --context ${CLUSTER_CONTEXT} -- ls -l /etc/egress-certsAll three files must be listed. A secret volume presents each one as a symbolic link into a
..datadirectory, which is expected. If the command reportsNo such file or directory, the volume is not mounted, so check the values file and confirm that the upgrade replaced the pod.Update your wildcard external service to originate mutual TLS.
kubectl apply --context ${CLUSTER_CONTEXT} -f- <<EOF apiVersion: networking.gloo.solo.io/v2 kind: ExternalService metadata: name: wildcard-com namespace: global spec: hosts: - "*.com" ports: - clientsideTls: mode: MUTUAL caCertificates: /etc/egress-certs/upstream-ca.crt clientCertificate: /etc/egress-certs/client.crt privateKey: /etc/egress-certs/client.key sni: api.example.com egressGatewayRoutes: portMatch: 80 virtualGatewayRefs: - cluster: ${CLUSTER_NAME} name: egress-vg namespace: istio-egress name: https number: 443 protocol: HTTPS EOFSetting Description spec.ports.clientsideTls.modeThe TLS mode for the connection to the upstream. MUTUALpresents a client certificate to the upstream.spec.ports.clientsideTls.caCertificatesThe file path to the CA certificates that verify the upstream certificate. Omit to verify the upstream against the default trust bundle of the proxy. spec.ports.clientsideTls.clientCertificateandspec.ports.clientsideTls.privateKeyThe file paths to the client certificate and the private key that the egress gateway presents. Both fields are required when modeisMUTUAL.spec.ports.clientsideTls.sniThe SNI string to present during the TLS handshake. On a wildcard host, this one value is presented for every host that the wildcard matches, so set it only when a single upstream serves all of them. Omit to present the requested host. spec.ports.egressGatewayRoutesThe egress gateway that the port routes through. Without this field, the certificate files are ignored, the external service reports an error, and the connection goes out with no client certificate. Verify that the origination cluster on the egress gateway carries the client certificate.
istioctl --context ${CLUSTER_CONTEXT} proxy-config cluster ${EGRESS_POD}.istio-egress -o json | grep -A 15 tls-origination-sni-proxyLook for a cluster named
gloo-mesh-tls-origination-sni-proxy-<hash>. Check that its transport socket lists the certificate and key file paths that you configured. Each set of TLS settings gets its own cluster, named for those settings. The cluster namedgloo-mesh-tls-origination-sni-proxy, with no hash, is the one-way default and carries no client certificate.Verify that the egress gateway loaded the certificate as a secret.
istioctl --context ${CLUSTER_CONTEXT} -n istio-egress proxy-config secret ${EGRESS_POD} | grep file-certIstio names a file-based certificate
file-cert:<certificate path>~<private key path>, so the row must name the two paths that you configured. If no row is returned, the egress gateway did not load the files. Check the proxy log for a repeating error that names the path.kubectl logs ${EGRESS_POD} -n istio-egress --context ${CLUSTER_CONTEXT} | grep -i "failed to generate secret"A path that the gateway cannot read produces
failed to generate secret ... no such file or directoryon a loop, and the gateway then originates TLS with no client certificate.
The following settings are not supported on a wildcard host. The external service reports a warning for each one.
| Setting | Behavior on a wildcard host |
|---|---|
credentialName | Not supported. Use the certificate file paths instead. |
mode: ISTIO_MUTUAL | Not supported. The egress gateway originates one-way TLS instead. |
A targetPort other than 443 | Ignored. The upstream port is always 443. |
Block traffic with an access policy
Create an access policy for your external service to allow only the product page app to access the
httpbin.orgexternal 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 EOFSend a request to
httpbin.orgfrom 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.orgExample 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%Send another request to
httpbin.orgfrom 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}