Transformations in kgateway
Eitan Suez
Apr 17, 2025
Transformations are a feature in kgateway that allows for the transformation of an incoming request or outgoing response. It offers the addition, removal, or replacement of HTTP headers and the manipulation of request or response body.
While the Kubernetes Gateway API provides filters for request and response header modifiers, those filters are scoped to the manipulation of headers only, and provide only rudimentary capabilities such as adding, removing or updating headers with static values supplied as strings.
Transformations are more powerful in that the value of the body or header is an inja template, which loosely adheres to the jinja2 template engine syntax.
Through transformations, kgateway gives us access to an expression language, which includes conditional statements, and access to a number of functions.
How it works
Transformations are configured with the TrafficPolicy resource and can be applied to either the request or the response, or both.
The TrafficPolicy resource in turn is associated to select HTTP requests through the targetRefs
field, which can point to an HTTPRoute. The HTTPRoute routing rule’s matches field can target specific requests based on various request attributes including path, headers, query parameters, and the HTTP method.
Let’s study some examples.
Examples
If you wish to try out any of the below examples yourself, you’ll need:
- A Kubernetes cluster with kgateway deployed
- A backing workload, such as httpbin
- A provisioned Gateway and HTTPRoute with a rule to send requests to the workload
You can find the details in the blog posts Guide to installing kgateway and Gateway API basics.
Example 1: Decode a header value in a request
This example configures a TrafficPolicy to add a request header named x-decoded-message
containing the value of a base64-decoded header named “message”.
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: transformation
spec:
targetRefs:
- name: httpbin
kind: HTTPRoute
group: gateway.networking.k8s.io
transformation:
request:
add:
- name: x-decoded-message
value: '{{ base64_decode(request_header("message")) }}'
Set the environment variable MESSAGE to the base64-encoded value of “hello world”:
export MESSAGE=$(echo -n "hello world" | base64)
Send a request through the ingress gateway with the “message” header:
curl -H "message: $MESSAGE" http://httpbin.example.com/headers --resolve httpbin.example.com:80:$GW_IP
In the response, note how the encoded message was decoded (and placed in the new header) by the gateway prior to forwarding the request on to httpbin
:
{
"headers": {
...
"Message": [
"aGVsbG8gd29ybGQ="
],
"X-Decoded-Message": [
"hello world"
],
...
}
}
Example 2: Add a response header conditionally
This example configures a TrafficPolicy to set the response header named x-host-match
to “yes” if the request was sent to the hostname “httpbin.example.com”:
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: transformation
spec:
targetRefs:
- name: httpbin
kind: HTTPRoute
group: gateway.networking.k8s.io
transformation:
response:
add:
- name: x-host-match
value: '{%- if "httpbin.example.com" in request_header("Host") -%}yes{% else %}nope{% endif %}'
The value of the Host
request header is an array, and so condition uses the in
operator to check for the presence of the host name in the list.
Send a request through the ingress gateway using the host name httpbin.example.com
:
curl --verbose http://httpbin.example.com/get --resolve httpbin.example.com:80:$GW_IP
Note the presence and value of the header x-host-match
in the response:
* Connected to httpbin.example.com (127.0.0.1) port 80
* using HTTP/1.x
> GET /get HTTP/1.1
> Host: httpbin.example.com
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< access-control-allow-credentials: true
< access-control-allow-origin: *
< content-type: application/json; charset=utf-8
< date: Thu, 03 Apr 2025 18:08:45 GMT
< content-length: 545
< x-envoy-upstream-service-time: 1
< x-host-match: yes
< server: envoy
...
Example 3: Response body transformation
The httpbin
sample application contains a /json
endpoint that returns some “canned” response about a slideshow:
curl http://httpbin.example.com/json --resolve httpbin.example.com:80:$GW_IP | jq
Here is the default response:
{
"slideshow": {
"author": "Yours Truly",
"date": "date of publication",
"slides": [
{
"title": "Wake up to WonderWidgets!",
"type": "all"
},
{
"items": [
"Why <em>WonderWidgets</em> are great",
"Who <em>buys</em> WonderWidgets"
],
"title": "Overview",
"type": "all"
}
],
"title": "Sample Slide Show"
}
}
Say that we wish to transform the response as follows:
- Wrap the response in a field named
response
, - Rename
title
todescription
, - Include only the
slides
section from the original response.
Here is a TrafficPolicy that applies the transformation:
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: transformation
spec:
targetRefs:
- name: httpbin
kind: HTTPRoute
group: gateway.networking.k8s.io
transformation:
response:
body:
parseAs: AsJson
value: |
{
"response": {
"description": "{{slideshow.title}}",
"slides": {{slideshow.slides}}
}
}
Apply the traffic policy, then send in another request:
curl http://httpbin.example.com/json --resolve httpbin.example.com:80:$GW_IP | jq
Review the transformed response:
{
"response": {
"description": "Sample Slide Show",
"slides": [
{
"title": "Wake up to WonderWidgets!",
"type": "all"
},
{
"items": [
"Why <em>WonderWidgets</em> are great",
"Who <em>buys</em> WonderWidgets"
],
"title": "Overview",
"type": "all"
}
]
}
}
Summary
Transformations is a powerful feature that provides access to an expression language to transform requests and (or) responses in a number of ways. This can be very useful in a number of use cases, providing a way to adapt incoming requests to the expectations of the running service, or to adapt the server’s responses to the expectations of a client.
Transformations is but one of a number of features that the kgateway project provides.