Overview

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

  1. 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.
  2. Add your apps to the ambient mesh.
  3. 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.

  1. Create the istio-egress namespace and add the following labels:

    • istio.io/dataplane-mode: ambient to add all pods in that namespace to the ambient mesh.
    • istio.io/use-waypoint: waypoint to 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-waypoint
      
  2. Create a waypoint proxy that serves as the shared egress gateway. To configure your gateway as a waypoint proxy, you use the istio-waypoint GatewayClass. 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
    EOF
      
  3. Verify that your egress gateway pod is running.

      kubectl get pods -n istio-egress
      

    Example output:

      NAME                               READY   STATUS    RESTARTS   AGE
    egress-waypoint-5cb48fc696-mq99j   1/1     Running   0          87m
      
  4. Configure 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%%"
    EOF
      
  5. Create 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 the istio.io/use-waypoint label, 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
    EOF
      
  6. Verify that the ServiceEntry is attached to the waypoint by looking at its status.

      kubectl get serviceentry nginx-mesh-external-se -n istio-egress -o yaml
      

    Example 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.

  1. 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.invalid for app1. 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
      
  2. Create a DestinationRule to set an mTLS traffic policy on the synthetic hostname. Reference your mTLS secret in the namespace/name format 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
    EOF
      
  3. Create 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
      

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.