Global policy attachment
By default, you must attach policies to resources that are in the same namespace. However, you might have policies that you want to reuse across teams, such as to standardize security protections across your organization.
To do so, you can create policies in a “global” namespace. Then, the policies can attach to resources in any namespace in your cluster through label selectors.
Before you begin
-
Follow the Get started guide to install kgateway.
-
Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.
-
Get the external address of the gateway and save it in an environment variable.
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $INGRESS_GW_ADDRESS
kubectl port-forward deployment/http -n kgateway-system 8080:8080
Step 1: Review default policy behavior
By default, policies are attached to resources in the same namespace. This way, each team manages their own apps, routing resources, and policies.
-
Send a request to the httpbin service on the
/anything
path and note the expected 200 response.curl -i http://$INGRESS_GW_ADDRESS:8080/anything -H "host: www.example.com:8080"
curl -i localhost:8080/anything -H "host: www.example.com"
Example output:
HTTP/1.1 200 OK access-control-allow-credentials: true access-control-allow-origin: * content-type: application/json; encoding=utf-8 date: Tue, 29 Jul 2025 20:08:21 GMT content-length: 587 x-envoy-upstream-service-time: 1 server: envoy
{ "args": {}, "headers": { "Accept": [ "*/*" ], "Host": [ "www.example.com" ], "User-Agent": [ "curl/8.7.1" ], "X-Envoy-Expected-Rq-Timeout-Ms": [ "15000" ], "X-Envoy-External-Address": [ "127.0.0.1" ], "X-Forwarded-For": [ "10.244.0.7" ], "X-Forwarded-Proto": [ "http" ], "X-Request-Id": [ "7ec1f5f7-72b7-4053-b2e4-0117a2438d4c" ] }, "origin": "10.244.0.7", "url": "http://www.example.com/anything", "data": "", "files": null, "form": null, "json": null }
-
Create a TrafficPolicy that rewrites the path to
/status/418
if it has thetransform:status
header. Note that the policy is in the same namespace as the HTTPRoute.kubectl apply -f- <<EOF apiVersion: gateway.kgateway.dev/v1alpha1 kind: TrafficPolicy metadata: name: transformation namespace: httpbin spec: targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: httpbin transformation: request: set: - name: ":path" value: '{% if request_header("transform") == "status" %}/status/418{% else %}{{ header(":path") }}{% endif %}' EOF
-
Repeat the request with the
transform:status
header. The request is now transformed from the/anything
path to the/status/418
path.curl -i http://$INGRESS_GW_ADDRESS:8080/anything -H "host: www.example.com:8080" -H "transform: status"
curl -i localhost:8080/anything -H "host: www.example.com" -H "transform: status"
Example output: Notice that the response is now
418 I'm a teapot!
instead of the initial 200 response.HTTP/1.1 418 Unknown access-control-allow-credentials: true access-control-allow-origin: * x-more-info: http://tools.ietf.org/html/rfc2324 date: Wed, 30 Jul 2025 15:45:07 GMT content-length: 13 content-type: text/plain; charset=utf-8 x-envoy-upstream-service-time: 1 server: envoy I'm a teapot!
-
Delete the TrafficPolicy.
kubectl delete TrafficPolicy -n httpbin transformation
-
Create the same TrafficPolicy but in a different namespace, such as the
kgateway-system
namespace.kubectl apply -f- <<EOF apiVersion: gateway.kgateway.dev/v1alpha1 kind: TrafficPolicy metadata: name: transformation namespace: kgateway-system spec: targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: httpbin transformation: request: set: - name: ":path" value: '{% if request_header("transform") == "status" %}/status/418{% else %}{{ header(":path") }}{% endif %}' EOF
-
Repeat the request. This time, the request is not transformed. The policy cannot be attached because the resource is in a different namespace. Instead, you get the initial 200 response.
curl -i http://$INGRESS_GW_ADDRESS:8080/anything -H "host: www.example.com:8080" -H "transform: status"
curl -i localhost:8080/anything -H "host: www.example.com" -H "transform: status"
Example output:
HTTP/1.1 200 OK access-control-allow-credentials: true access-control-allow-origin: * content-type: application/json; encoding=utf-8 date: Tue, 29 Jul 2025 20:08:21 GMT content-length: 587 x-envoy-upstream-service-time: 1 server: envoy
{ "args": {}, "headers": { "Accept": [ "*/*" ], "Host": [ "www.example.com" ], "User-Agent": [ "curl/8.7.1" ], "X-Envoy-Expected-Rq-Timeout-Ms": [ "15000" ], "X-Envoy-External-Address": [ "127.0.0.1" ], "X-Forwarded-For": [ "10.244.0.7" ], "X-Forwarded-Proto": [ "http" ], "X-Request-Id": [ "7ec1f5f7-72b7-4053-b2e4-0117a2438d4c" ] }, "origin": "10.244.0.7", "url": "http://www.example.com/anything", "data": "", "files": null, "form": null, "json": null }
Step 2: Enable global policy attachment
To enable the global policy attachment feature, upgrade your kgateway Helm installation.
-
Get the Helm values for your current Helm installation.
helm get values kgateway -n kgateway-system -o yaml > kgateway.yaml open kgateway.yaml
-
Add the following values to the Helm values file to enable the global policy namespace feature. The example uses the
kgateway-system
namespace as the “global” namespace, but you can use any existing namespace that you want.controller: extraEnv: KGW_GLOBAL_POLICY_NAMESPACE: kgateway-system
-
Upgrade your Helm installation. Replace the
--version v2.0.3
option to match your current version.helm upgrade -i --namespace kgateway-system --version v2.0.3 kgateway oci://cr.kgateway.dev/kgateway-dev/charts/kgateway -f kgateway.yaml
Step 3: Create a global policy
Create a global policy in the kgateway-system
namespace. Then, use a global-policy
label selector to attach the policy to resources in any namespace.
-
Update the HTTPRoute for the httpbin service to add a label selector. The label selector can be any value that you want, but it must match the label selector that you add in the TrafficPolicy.
kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httpbin namespace: httpbin labels: global-policy: transformation spec: parentRefs: - name: http namespace: kgateway-system hostnames: - "www.example.com" rules: - backendRefs: - name: httpbin port: 8000 EOF
-
Update the TrafficPolicy to target the HTTPRoute by using the
global-policy
label in thetargetSelectors
field (instead of thetargetRefs
field).kubectl apply -f- <<EOF apiVersion: gateway.kgateway.dev/v1alpha1 kind: TrafficPolicy metadata: name: transformation namespace: kgateway-system spec: targetSelectors: - group: gateway.networking.k8s.io kind: HTTPRoute matchLabels: global-policy: transformation transformation: request: set: - name: ":path" value: '{% if request_header("transform") == "status" %}/status/418{% else %}{{ header(":path") }}{% endif %}' EOF
-
Send a request with the
transform:status
header. This time, the request is transformed even though the HTTPRoute and transformation policy are in different namespaces.curl -i http://$INGRESS_GW_ADDRESS:8080/anything -H "host: www.example.com:8080" -H "transform: status"
curl -i localhost:8080/anything -H "host: www.example.com" -H "transform: status"
Example output:
HTTP/1.1 418 Unknown access-control-allow-credentials: true access-control-allow-origin: * x-more-info: http://tools.ietf.org/html/rfc2324 date: Wed, 30 Jul 2025 15:49:17 GMT content-length: 13 content-type: text/plain; charset=utf-8 x-envoy-upstream-service-time: 0 server: envoy I'm a teapot!%
More resources
For more examples of attaching policies to different resources, review each policy’s docs.
Cleanup
You can remove the resources that you created in this guide.-
Delete the transformation policy.
kubectl delete TrafficPolicy -n kgateway-system transformation
-
Restore the sample httpbin HTTPRoute.
kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httpbin namespace: httpbin spec: parentRefs: - name: http namespace: kgateway-system hostnames: - "www.example.com" rules: - backendRefs: - name: httpbin port: 8000 EOF