Advanced mTLS egress ENTERPRISE ALPHA
Enable mTLS traffic routing through an egress gateway based on source principal.
Overview
This feature requires your mesh to be installed with the Solo distribution of Istio and an Enterprise-level license for Gloo Mesh (OSS APIs). Contact your account representative to obtain a valid license.mTLS egress based on client identity is an advanced feature that is in the alpha state. Alpha features are likely to change, are not fully tested, and are not supported for production. For more information, see Solo feature maturity.
Deploy a waypoint proxy that serves as a shared egress gateway for Istio workloads in your ambient mesh. This gateway originates mTLS egress traffic in which each Istio workload identity uses a per client-app service account as its own unique, external identity. This egress waypoint configuration essentially allows you to map a mesh identity onto an external identity, and originate mTLS connections with the mapped external identity based on the identity of the client that initiates the connection.
For example, consider two apps in the same namespace that might send egress requests to an external hostname. When you use this feature, the external server returns the subject that is defined in the client certificate to illustrate that the correct external identity is being used for each app identity.
When app1 calls the external server, the response returns the team-namespace.app1 source in the subject response, because the egress gateway correctly maps the internal identity of app1 to its external service identity:
kubectl exec -n team-namespace deployments/app1 -it -- curl -s http://my-nginx.foo.bar/subject
{
"subject": "CN=team-namespace.app1"
}%
In turn, when app2 calls the external server, the response then returns the team-namespace.app2 source in the subject response, because the egress gateway uses the mapped identities of app2 this time:
kubectl exec -n team-namespace deployments/app2 -it -- curl -s http://my-nginx.foo.bar/subject
{
"subject": "CN=team-namespace.app2"
}%
Without this feature, the subject field would be empty, because the server would be unable to identify the source identity that originated the mTLS connection.
Before you begin
- Use the Solo distribution of Istio version 1.24.3 or later and your Enterprise-level license to set up an ambient mesh. You can set up the mesh by using the Gloo Operator or Helm.If you have not yet set up an ambient mesh, be sure to include the
PERMIT_CROSS_NAMESPACE_RESOURCE_ACCESS: istio-egress/egress-waypointenvironment variable in istiod when you follow either of these guides to install an ambient mesh. For details, see the first step in the next section. - Add your apps to the ambient mesh.
- Be sure that your external server is set up for mTLS connections, and generate a client certificate and a private key that your client app uses when making egress requests to the external server. When you store the certificate information in a Kubernetes secret, be sure to create the secret in the same namespace as the client app. If you want to deploy a test server, you can follow the Istio docs to deploy an nginx server to your cluster and locally generate the certificate and key.
Step 1: Upgrade your ambient mesh
Update your istiod installation to set the PERMIT_CROSS_NAMESPACE_RESOURCE_ACCESS environment variable to the egress waypoint namespace and service account name, in the namespace/name format. This example enables cross-namespace resource access for the waypoint that you create in the next section, egress-waypoint in the istio-egress namespace, which serves as the shared egress gateway for your mesh workloads. Specify multiple waypoints as necessary in a comma-separated list.
Step 2: Create a shared egress gateway
Create a waypoint that serves as the shared egress gateway. Then, configure the gateway to route traffic based on the client workload identity and create a ServiceEntry so that the gateway can route to the external service.
Create the
istio-egressnamespace and add the following labels:istio.io/dataplane-mode: ambientto add all pods in that namespace to the ambient mesh.istio.io/use-waypoint: waypointto redirect all traffic through the egress waypoint proxy that you create in the next step.
kubectl create namespace istio-egress kubectl label ns istio-egress istio.io/dataplane-mode=ambient kubectl label ns istio-egress istio.io/use-waypoint=egress-waypointCreate a waypoint proxy that serves as the shared egress gateway. To configure your gateway as a waypoint proxy, you use the
istio-waypointGatewayClass. The Gateway serves routes from all namespaces.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: egress-waypoint namespace: istio-egress spec: gatewayClassName: istio-waypoint listeners: - name: mesh port: 15008 protocol: HBONE allowedRoutes: namespaces: from: All EOFVerify that your egress gateway pod is running.
kubectl get pods -n istio-egressExample output:
NAME READY STATUS RESTARTS AGE egress-waypoint-5cb48fc696-mq99j 1/1 Running 0 87mConfigure the egress gateway with the following EnvoyFilter, which adds an early header that enables routing based on the client workload identity. This header is cleaned up to ensure that the internal workload identity is not leaked with the request.
kubectl apply -f- <<EOF apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: principal-routing-header-envoyfilter namespace: istio-egress spec: targetRefs: - name: egress-waypoint kind: Gateway group: gateway.networking.k8s.io configPatches: # Apply header to HttpConnectionManager at the a network filter level - applyTo: NETWORK_FILTER match: context: GATEWAY listener: filterChain: filter: name: "envoy.filters.network.http_connection_manager" patch: # Merge into the HCM operation: MERGE value: typed_config: # HCM level "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" # Extension to merge into the HCM early_header_mutation_extensions: - name: src-principal-header-mutation typed_config: "@type": type.googleapis.com/envoy.extensions.http.early_header_mutation.header_mutation.v3.HeaderMutation mutations: - append: header: key: "%%source.principal%%" value: "%FILTER_STATE(io.istio.peer_principal:PLAIN)%" # Overwrite the client identity append_action: OVERWRITE_IF_EXISTS_OR_ADD - applyTo: ROUTE_CONFIGURATION match: context: GATEWAY patch: operation: MERGE value: # Clean up identity so that it # is not leaked with the request request_headers_to_remove: - "%%source.principal%%" EOFCreate a ServiceEntry that represents the real hostname for the external service that your apps send egress requests to, such as
my-nginx.foo.bar. Because you label the ServiceEntry with theistio.io/use-waypointlabel, all traffic to the external hostname is automatically routed through your egress gateway.kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1 kind: ServiceEntry metadata: name: nginx-mesh-external-se namespace: istio-egress labels: istio.io/use-waypoint: egress-waypoint spec: hosts: - my-nginx.foo.bar location: MESH_EXTERNAL ports: - number: 80 name: http protocol: HTTP resolution: DNS EOFVerify that the ServiceEntry is attached to the waypoint by looking at its status.
kubectl get serviceentry nginx-mesh-external-se -n istio-egress -o yamlExample output:
... status: addresses: - host: nginx-mesh-external-se value: 240.240.0.2 - host: nginx-mesh-external-se value: 2001:2::2 conditions: - lastTransitionTime: "2025-01-31T19:59:45.435933727Z" message: Successfully attached to waypoint egress/egress-waypoint reason: WaypointAccepted status: "True" type: istio.io/WaypointBound
Step 3: Map client and external service identities
Create per-app resources in the same namespace as your client app. The following ServiceEntry, DestinationRule, and HTTPRoute resources set up the mapping between the client app’s internal Istio workload identity, and an mTLS connection based on that identity to the external service.
Create a ServiceEntry to define a synthetic, unroutable hostname for your client app, which points to the external service’s ServiceEntry address.
- The following example defines the hostname
app1.team-namespace.my-nginx.foo.bar.invalidforapp1. The synthetic hostname for the app must be globally unique and is ideally unroutable, because in later steps you use it as a backendRef in an HTTPRoute only to bind it to the external service. - Additionally, this resource defines the external service’s real hostname,
my-nginx.foo.bar, as the endpoint.
kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1 kind: ServiceEntry metadata: name: app1-nginx-mesh-external-se namespace: team-namespace spec: ports: - number: 443 name: https protocol: HTTPS # Synthentic, unique, unroutable hostname hosts: - app1.team-namespace.my-nginx.foo.bar.invalid location: MESH_EXTERNAL resolution: DNS # External service's real hostname endpoints: - address: my-nginx.foo.bar EOF- The following example defines the hostname
Create a DestinationRule to set an mTLS traffic policy on the synthetic hostname. Reference your mTLS secret in the
namespace/nameformat to ensure that the gateway can access the secret for each specific app in the app namespace, rather than in the gateway namespace.kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: app1-nginx-mesh-external-dr namespace: team-namespace spec: # Synthetic hostname for the app host: app1.team-namespace.my-nginx.foo.bar.invalid trafficPolicy: tls: subjectAltNames: - app1.team-namespace.my-nginx.foo.bar.invalid - my-nginx.foo.bar mode: MUTUAL # mTLS secret in namespace/name format credentialName: team-namespace/app1-client-mtls-secret EOFCreate an HTTPRoute for the global ServiceEntry that routes to the real hostname for the external service.
- The following example defines a route for the external service’s real hostname,
my-nginx.foo.bar. - The route matches on the
%%source.principal%%header for the client app’s SPIFFE identity, which serves as the client app’s internal identity. Matched requests are routed to the synthetic hostname for the client app as the backend.
kubectl apply -f - <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: app1-api-partner-com-httproute namespace: team-namespace spec: hostnames: - my-nginx.foo.bar parentRefs: - name: nginx-mesh-external-se kind: ServiceEntry group: networking.istio.io namespace: istio-egress rules: - matches: - headers: - type: Exact name: "%%source.principal%%" value: "spiffe://cluster.local/ns/team-namespace/sa/app1" backendRefs: - group: networking.istio.io kind: Hostname name: app1.team-namespace.my-nginx.foo.bar.invalid port: 443 EOF- The following example defines a route for the external service’s real hostname,
Overall, this configuration ensures that when an egress request to my-nginx.foo.bar is sent by any app with the %%source.principal%% header value of spiffe://cluster.local/ns/team-namespace/sa/app1, it is routed through the synthetic hostname to ensure that an mTLS connection is originated for that specific app identity. If needed, you can now repeat the same steps in Step 3: Configure per-app resources for other apps that must send requests to the external server.