Customize request and response headers using an EnvoyFilter resource

Updated at:

You can use an EnvoyFilter resource to customize request and response headers. An EnvoyFilter resource lets you directly modify the configuration of the Istio proxy (Envoy). This lets you add, delete, or modify headers as requests or responses stream through the proxy.

Prerequisites

The httpbin application has been deployed.

Step 1: Define an Envoy filter template

ASM lets you create Envoy filters from Envoy filter templates. A single Envoy filter template can be used to create multiple Envoy filters and apply them to different workloads and namespaces. This improves configuration reusability and management efficiency.

The following is a sample YAML file for an Envoy filter template. For more information, see Envoy Filter CRD documentation.

View EnvoyFilter YAML

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: custom-header-filter
  namespace: my-namespace
spec:
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: SIDECAR_INBOUND
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
              subFilter:
                name: envoy.filters.http.router
        proxy:
          proxyVersion: ^1\.20.*
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.lua
          typed_config:
            '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
            inlineCode: |
              function envoy_on_request(request_handle)
                -- Get a header key and value from the request header.
                local header_key = "x-custom-request-header" -- The key of the request header you want to get.
                local header_value = request_handle:headers():get(header_key)
                if header_value then
                  -- Write to Otel Baggage.
                  local baggage = header_key .. "=" .. header_value
                  request_handle:headers():add("baggage", baggage)
                  request_handle:streamInfo():dynamicMetadata():set("envoy.filters.http.lua", "otel.baggage", baggage)
                end
              end

              function envoy_on_response(response_handle)
                -- Get Otel Baggage from dynamic metadata.
                local metadata = response_handle:streamInfo():dynamicMetadata():get("envoy.filters.http.lua") or {}
                local baggage = metadata["otel.baggage"]
                if baggage then
                  -- Write the Otel Baggage to another header.
                  local new_header_key = "x-custom-response-header" -- The key for the new response header.
                  response_handle:headers():add(new_header_key, baggage)
                end
              end
  workloadSelector:
    labels:
      app: httpbin
      version: v1

  • If you use Istio 1.9 or later, replace the value of the proxyVersion field to match your version.

  • If you use Istio 1.8 or earlier, replace the value of the proxyVersion field to match your version. In the EnvoyFilter resource, also replace envoy.filters.network.http_connection_manager with envoy.http_connection_manager, envoy.filters.http.router with envoy.router, and type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua with type.googleapis.com/envoy.config.filter.http.lua.v2.Lua.

Step 2: Create an EnvoyFilter resource from the Envoy filter template

After you create an Envoy filter template, attach it to a specific workload or namespace. This causes the Envoy filter to take effect only on the specified workload or namespace. When you attach the template, an EnvoyFilter resource is automatically created.

The following example creates an EnvoyFilter resource named custom-header-filter. This resource configures a Lua filter to add a custom request header to incoming requests and a custom response header to outgoing responses. The filter applies to all workloads that have the app: my-app label.

View EnvoyFilter YAML

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: custom-header-filter
  namespace: my-namespace
spec:
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: SIDECAR_INBOUND
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
              subFilter:
                name: envoy.filters.http.router
        proxy:
          proxyVersion: ^1\.20.*
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.lua
          typed_config:
            '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
            inlineCode: |
              function envoy_on_request(request_handle)
                -- Get a header key and value from the request header.
                local header_key = "x-custom-request-header" -- The key of the request header you want to get.
                local header_value = request_handle:headers():get(header_key)
                if header_value then
                  -- Write to Otel Baggage.
                  local baggage = header_key .. "=" .. header_value
                  request_handle:headers():add("baggage", baggage)
                  request_handle:streamInfo():dynamicMetadata():set("envoy.filters.http.lua", "otel.baggage", baggage)
                end
              end

              function envoy_on_response(response_handle)
                -- Get Otel Baggage from dynamic metadata.
                local metadata = response_handle:streamInfo():dynamicMetadata():get("envoy.filters.http.lua") or {}
                local baggage = metadata["otel.baggage"]
                if baggage then
                  -- Write the Otel Baggage to another header.
                  local new_header_key = "x-custom-response-header" -- The key for the new response header.
                  response_handle:headers():add(new_header_key, baggage)
                end
              end
  workloadSelector:
    labels:
      app: httpbin
      version: v1

Important

An EnvoyFilter resource is powerful but complex because it directly modifies the underlying Envoy configuration. Before you use an EnvoyFilter resource, familiarize yourself with the Envoy configuration model and make changes carefully to prevent side effects. Note that EnvoyFilter configurations may change with Istio upgrades, which can cause compatibility issues.

Step 3: View custom request and response content in logs

ASM lets you customize log formats to retrieve values from request headers, response headers, and built-in Envoy values. For more information, see Observability configuration.

Add the following three fields to display the content in the logs:

Variable name

Type

Variable value

my-x-custom-request-header

Request property

%REQ(x-custom-request-header)%

baggage-from-request

Request property

%REQ(baggage)%

my-x-custom-response-header

Response property

%RESP(x-custom-response-header)%

View the logs of the httpbin pod. The output is similar to the following:

{
    "bytes_received": "0",
    "bytes_sent": "490",
    "duration": "1",
    "istio_policy_status": "-",
    "method": "GET",
    "path": "/headers",
    "protocol": "HTTP/1.1",
    "response_code": "200",
    "response_flags": "-",
    "my-x-custom-request-header": "xxx",
    "baggage-from-request": "x-custom-request-header=xxx",
    "my-x-custom-response-header": "x-custom-request-header=xxx",
}