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 Solo Enterprise for 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_ADDRESSkubectl 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
/anythingpath 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 EnterpriseKgatewayTrafficPolicy that rewrites the path to
/status/418if it has thetransform:statusheader. Note that the policy is in the same namespace as the HTTPRoute.kubectl apply -f- <<EOF apiVersion: enterprisekgateway.solo.io/v1alpha1 kind: EnterpriseKgatewayTrafficPolicy 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 %}' EOFRepeat the request with the
transform:statusheader. The request is now transformed from the/anythingpath to the/status/418path.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 EnterpriseKgatewayTrafficPolicy.
kubectl delete EnterpriseKgatewayTrafficPolicy -n httpbin transformationCreate the same EnterpriseKgatewayTrafficPolicy but in a different namespace, such as the
kgateway-systemnamespace.kubectl apply -f- <<EOF apiVersion: enterprisekgateway.solo.io/v1alpha1 kind: EnterpriseKgatewayTrafficPolicy 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 %}' EOFRepeat 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 Solo Enterprise for kgateway Helm installation.
Get the Helm values for your current Helm installation.
helm get values enterprise-kgateway -n kgateway-system -o yaml > enterprise-kgateway.yaml open enterprise-kgateway.yamlAdd the following values to the Helm values file to enable the global policy namespace feature. The example uses the
kgateway-systemnamespace as the “global” namespace, but you can use any existing namespace that you want.controller: extraEnv: KGW_GLOBAL_POLICY_NAMESPACE: kgateway-systemUpgrade your Helm installation. Replace the
--version 2.1.5option to match your current version.helm upgrade -i --namespace kgateway-system --version 2.1.5 enterprise-kgateway oci://us-docker.pkg.dev/solo-public/enterprise-kgateway/charts/enterprise-kgateway -f enterprise-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 EnterpriseKgatewayTrafficPolicy.
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 EOFUpdate the EnterpriseKgatewayTrafficPolicy to target the HTTPRoute by using the
global-policylabel in thetargetSelectorsfield (instead of thetargetRefsfield).kubectl apply -f- <<EOF apiVersion: enterprisekgateway.solo.io/v1alpha1 kind: EnterpriseKgatewayTrafficPolicy 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 %}' EOFSend a request with the
transform:statusheader. 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!%
Cleanup
You can remove the resources that you created in this guide.Delete the transformation policy.
kubectl delete EnterpriseKgatewayTrafficPolicy -n kgateway-system transformationRestore 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