Envoy Patch Policy

This task explains the usage of the EnvoyPatchPolicy API. Note: This API is meant for users extremely familiar with Envoy xDS semantics. Also before considering this API for production use cases, please be aware that this API is unstable and the outcome may change across versions. Use at your own risk.

Introduction

The EnvoyPatchPolicy API allows user to modify the output xDS configuration generated by Envoy Gateway intended for EnvoyProxy, using JSON Patch semantics.

Motivation

This API was introduced to allow advanced users to be able to leverage Envoy Proxy functionality not exposed by Envoy Gateway APIs today.

Quickstart

Prerequisites

Follow the steps below to install Envoy Gateway and the example manifest. Before proceeding, you should be able to query the example backend using HTTP.

Expand for instructions
  1. Install the Gateway API CRDs and Envoy Gateway using Helm:

    helm install eg oci://docker.io/envoyproxy/gateway-helm --version v0.0.0-latest -n envoy-gateway-system --create-namespace
  2. Install the GatewayClass, Gateway, HTTPRoute and example app:

    kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/latest/quickstart.yaml -n default
  3. Verify Connectivity:

    You can also test the same functionality by sending traffic to the External IP. To get the external IP of the Envoy service, run:

    export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')

    Note: In certain environments, the load balancer may be exposed using a hostname, instead of an IP address. If so, replace ip in the above command with hostname.

    Curl the example app through Envoy proxy:

    curl --verbose --header "Host: www.example.com" http://$GATEWAY_HOST/get

    Get the name of the Envoy service created by the example Gateway:

    export ENVOY_SERVICE=$(kubectl get svc -n envoy-gateway-system --selector=gateway.envoyproxy.io/owning-gateway-namespace=default,gateway.envoyproxy.io/owning-gateway-name=eg -o jsonpath='{.items[0].metadata.name}')

    Port forward to the Envoy service:

    kubectl -n envoy-gateway-system port-forward service/${ENVOY_SERVICE} 8888:80 &

    Curl the example app through Envoy proxy:

    curl --verbose --header "Host: www.example.com" http://localhost:8888/get

Enable EnvoyPatchPolicy

  • By default EnvoyPatchPolicy is disabled. Lets enable it in the EnvoyGateway startup configuration

  • The default installation of Envoy Gateway installs a default EnvoyGateway configuration and attaches it using a ConfigMap. In the next step, we will update this resource to enable EnvoyPatchPolicy.

cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: envoy-gateway-config namespace: envoy-gateway-system data: envoy-gateway.yaml: | apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyGateway provider: type: Kubernetes gateway: controllerName: gateway.envoyproxy.io/gatewayclass-controller extensionApis: enableEnvoyPatchPolicy: true EOF

Save and apply the following resource to your cluster:

--- apiVersion: v1 kind: ConfigMap metadata: name: envoy-gateway-config namespace: envoy-gateway-system data: envoy-gateway.yaml: | apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyGateway provider: type: Kubernetes gateway: controllerName: gateway.envoyproxy.io/gatewayclass-controller extensionApis: enableEnvoyPatchPolicy: true

After updating the ConfigMap, you will need to wait the configuration kicks in.
You can force the configuration to be reloaded by restarting the envoy-gateway deployment.

kubectl rollout restart deployment envoy-gateway -n envoy-gateway-system

Testing

Customize Response

  • Use EnvoyProxy’s Local Reply Modification feature to return a custom response back to the client when the status code is 404

  • Apply the configuration

cat <<EOF | kubectl apply -f - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyPatchPolicy metadata: name: custom-response-patch-policy namespace: default spec: targetRef: group: gateway.networking.k8s.io kind: Gateway name: eg type: JSONPatch jsonPatches: - type: "type.googleapis.com/envoy.config.listener.v3.Listener" # The listener name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName> name: default/eg/http operation: op: add path: "/default_filter_chain/filters/0/typed_config/local_reply_config" value: mappers: - filter: status_code_filter: comparison: op: EQ value: default_value: 404 runtime_key: key_b status_code: 406 body: inline_string: "could not find what you are looking for" EOF

Save and apply the following resource to your cluster:

--- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyPatchPolicy metadata: name: custom-response-patch-policy namespace: default spec: targetRef: group: gateway.networking.k8s.io kind: Gateway name: eg type: JSONPatch jsonPatches: - type: "type.googleapis.com/envoy.config.listener.v3.Listener" # The listener name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName> name: default/eg/http operation: op: add path: "/default_filter_chain/filters/0/typed_config/local_reply_config" value: mappers: - filter: status_code_filter: comparison: op: EQ value: default_value: 404 runtime_key: key_b status_code: 406 body: inline_string: "could not find what you are looking for"

When mergeGateways is enabled, there will be one Envoy deployment for all Gateways in the cluster. Then the EnvoyPatchPolicy should target a specific GatewayClass.

cat <<EOF | kubectl apply -f - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyPatchPolicy metadata: name: custom-response-patch-policy namespace: default spec: targetRef: group: gateway.networking.k8s.io kind: GatewayClass name: eg type: JSONPatch jsonPatches: - type: "type.googleapis.com/envoy.config.listener.v3.Listener" # The listener name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName> name: default/eg/http operation: op: add path: "/default_filter_chain/filters/0/typed_config/local_reply_config" value: mappers: - filter: status_code_filter: comparison: op: EQ value: default_value: 404 runtime_key: key_b status_code: 406 body: inline_string: "could not find what you are looking for" EOF

Save and apply the following resource to your cluster:

--- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyPatchPolicy metadata: name: custom-response-patch-policy namespace: default spec: targetRef: group: gateway.networking.k8s.io kind: GatewayClass name: eg type: JSONPatch jsonPatches: - type: "type.googleapis.com/envoy.config.listener.v3.Listener" # The listener name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName> name: default/eg/http operation: op: add path: "/default_filter_chain/filters/0/typed_config/local_reply_config" value: mappers: - filter: status_code_filter: comparison: op: EQ value: default_value: 404 runtime_key: key_b status_code: 406 body: inline_string: "could not find what you are looking for"
  • Edit the HTTPRoute resource from the Quickstart to only match on paths with value /get
kubectl patch httproute backend --type=json --patch ' - op: add path: /spec/rules/0/matches/0/path/value value: /get '
  • Test it out by specifying a path apart from /get
$ curl --header "Host: www.example.com" http://$GATEWAY_HOST/find Handling connection for 8888 could not find what you are looking for

Customize VirtualHost by name

  • Use EnvoyProxy’s include_attempt_count_in_response feature to include the attempt count as header in the downstream response.
  • Apply the configuration
cat <<EOF | kubectl apply -f - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyPatchPolicy metadata: name: include-attempts namespace: default spec: targetRef: group: gateway.networking.k8s.io kind: Gateway name: eg type: JSONPatch jsonPatches: - type: "type.googleapis.com/envoy.config.route.v3.RouteConfiguration" # The RouteConfiguration name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName> name: default/eg/http operation: op: add # Every virtual_host that ends with 'www_example_com' (using RegEx Filter) jsonPath: "..virtual_hosts[?match(@.name, '.*www_example_com')]" # If the property does not exists, it can not be selected with jsonPath # Therefore the new property must be set in path path: "include_attempt_count_in_response" value: true EOF

Save and apply the following resource to your cluster:

--- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyPatchPolicy metadata: name: include-attempts namespace: default spec: targetRef: group: gateway.networking.k8s.io kind: Gateway name: eg type: JSONPatch jsonPatches: - type: "type.googleapis.com/envoy.config.route.v3.RouteConfiguration" # The RouteConfiguration name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName> name: default/eg/http operation: op: add # Every virtual_host that ends with 'www_example_com' (using RegEx Filter) jsonPath: "..virtual_hosts[?match(@.name, '.*www_example_com')]" # If the property does not exists, it can not be selected with jsonPath # Therefore the new property must be set in path path: "include_attempt_count_in_response" value: true
  • Test it out by looking at the response headers
$ curl -v --header "Host: www.example.com" http://localhost:8888/
...
< x-envoy-attempt-count: 1
...

Debugging

Runtime

  • The Status subresource should have information about the status of the resource. Make sure Accepted=True and Programmed=True conditions are set to ensure that the policy has been applied to Envoy Proxy.
apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyPatchPolicy metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"gateway.envoyproxy.io/v1alpha1","kind":"EnvoyPatchPolicy","metadata":{"annotations":{},"name":"custom-response-patch-policy","namespace":"default"},"spec":{"jsonPatches":[{"name":"default/eg/http","operation":{"op":"add","path":"/default_filter_chain/filters/0/typed_config/local_reply_config","value":{"mappers":[{"body":{"inline_string":"could not find what you are looking for"},"filter":{"status_code_filter":{"comparison":{"op":"EQ","value":{"default_value":404}}}}}]}},"type":"type.googleapis.com/envoy.config.listener.v3.Listener"}],"priority":0,"targetRef":{"group":"gateway.networking.k8s.io","kind":"Gateway","name":"eg","namespace":"default"},"type":"JSONPatch"}} creationTimestamp: "2023-07-31T21:47:53Z" generation: 1 name: custom-response-patch-policy namespace: default resourceVersion: "10265" uid: a35bda6e-a0cc-46d7-a63a-cee765174bc3 spec: jsonPatches: - name: default/eg/http operation: op: add path: /default_filter_chain/filters/0/typed_config/local_reply_config value: mappers: - body: inline_string: could not find what you are looking for filter: status_code_filter: comparison: op: EQ value: default_value: 404 type: type.googleapis.com/envoy.config.listener.v3.Listener priority: 0 targetRef: group: gateway.networking.k8s.io kind: Gateway name: eg type: JSONPatch status: conditions: - lastTransitionTime: "2023-07-31T21:48:19Z" message: EnvoyPatchPolicy has been accepted. observedGeneration: 1 reason: Accepted status: "True" type: Accepted - lastTransitionTime: "2023-07-31T21:48:19Z" message: successfully applied patches. reason: Programmed status: "True" type: Programmed

Offline

Caveats

This API will always be an unstable API and the same outcome cannot be guaranteed across versions for these reasons

  • The Envoy Proxy API might deprecate and remove API fields
  • Envoy Gateway might alter the xDS translation creating a different xDS output such as changing the name field of resources.

Last modified March 26, 2025: docs: fix layout (#5604) (953ccc1)