Global policy attachment

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.

⚠️
Because it increases the number of policy attachments to calculate, the global policy namespace feature can impact performance at scale. It also changes the standard policy attachment behavior, which can make debugging more difficult. As such, make sure to establish clear guidelines for using this feature, such as how many global policies are available for teams to use.

Before you begin

  1. Follow the Get started guide to install kgateway.

  2. Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.

  3. 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.

  1. 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
    }
  2. Create a TrafficPolicy that rewrites the path to /status/418 if it has the transform: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
  3. 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!
  4. Delete the TrafficPolicy.

    kubectl delete TrafficPolicy -n httpbin transformation
  5. 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
  6. 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.

  1. Get the Helm values for your current Helm installation.

    helm get values kgateway -n kgateway-system -o yaml > kgateway.yaml
    open kgateway.yaml
  2. 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
  3. 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.

  1. 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
  2. Update the TrafficPolicy to target the HTTPRoute by using the global-policy label in the targetSelectors field (instead of the targetRefs 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
  3. 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.
  1. Delete the transformation policy.

    kubectl delete TrafficPolicy -n kgateway-system transformation
  2. 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