Skip to content

extensionManager.policyResources: support cross-namespace Gateway attachment for extension-server policies #9584

Description

@siddharth1036

Problem Statement

When a CRD is registered under extensionManager.policyResources, Envoy Gateway treats it as a "directly attached" Gateway API policy and re-translates xDS on changes. However, an extension-server policy can only attach to a Gateway that lives in the same namespace as the policy. Targeting a Gateway in a different namespace silently fails to attach (and, for our extension, means the policy never drives re-translation).

This is currently documented as a one-line limitation on the API field but not explained, and there is no supported mechanism (e.g. ReferenceGrant) to opt into cross-namespace attachment.

From api/v1alpha1/envoygateway_types.go (ExtensionManager.PolicyResources):

// PolicyResources defines the set of K8S resources the extension server will handle
// as directly attached Gateway API policies. Only policies in the same namespace as
// the target Gateway resources are supported. Cross-namespace attachments are not supported.
PolicyResources []GroupVersionKind `json:"policyResources,omitempty"`

Environment

  • Envoy Gateway: v1.8.1
  • Provider: Kubernetes
  • Using extensionManager.hooks.xdsTranslator + extensionManager.policyResources with a custom CRD.

Root cause (code references)

  1. The target reference used for extension-server policies is LocalPolicyTargetReferenceWithSectionName, which has no namespace field — so it can only ever refer to an object in the policy's own namespace.

  2. internal/gatewayapi/extensionserverpolicy.goresolveExtServerPolicyGatewayTargetRef() hard-codes the Gateway lookup namespace to the policy's namespace:

func resolveExtServerPolicyGatewayTargetRef(policy *unstructured.Unstructured, target gwapiv1.LocalPolicyTargetReferenceWithSectionName, gateways map[types.NamespacedName]*policyGatewayTargetContext) *GatewayContext {
	key := types.NamespacedName{
		Name:      string(target.Name),
		Namespace: policy.GetNamespace(), // <-- always the policy's namespace
	}
	gateway, ok := gateways[key]
	if !ok {
		return nil // cross-namespace target => "no targets found for the policy"
	}
	return gateway.GatewayContext
}
  1. internal/provider/kubernetes/controller.goprocessExtensionServerPolicies() additionally requires a targetRef/targetRefs to be present at all, otherwise the policy is dropped:
_, foundTargetRef := policySpec["targetRef"]
_, foundTargetRefs := policySpec["targetRefs"]
if !foundTargetRef && !foundTargetRefs {
	return fmt.Errorf("not a policy object - no targetRef or targetRefs found in %s.%s %s", ...)
}

Net effect: a policyResources policy must (a) carry a targetRef/targetRefs, and (b) live in the same namespace as its target Gateway. There is no way to reference a Gateway in another namespace.

Expected behavior

Allow an extension-server policy to attach to a Gateway in another namespace, gated by a ReferenceGrant (consistent with how Gateway API handles cross-namespace references elsewhere), e.g.:

  • Support a namespaced target reference (e.g. LocalPolicyTargetReferenceWithSectionName extended with an optional namespace, or accept NamespacedPolicyTargetReference) for extension-server policies, and
  • Require a ReferenceGrant in the Gateway's namespace granting from: <policy GVK/namespace> to: Gateway before honoring the cross-namespace attachment.

Proposed change

  1. In resolveExtServerPolicyGatewayTargetRef, use the target's namespace when provided (falling back to policy.GetNamespace()), and enforce a ReferenceGrant check for cross-namespace targets.
  2. In processExtensionServerPolicies, keep the targetRef presence check, but ensure cross-namespace targets are retained through translation so status can be computed.
  3. Update the PolicyResources field docs and the extension-server policy documentation to describe the cross-namespace rules and required ReferenceGrant.

Steps to reproduce

  1. Configure extensionManager.policyResources for a custom policy CRD and an xdsTranslator post hook.
  2. Create a Gateway gw in namespace infra.
  3. Create the custom policy in namespace app with a targetRefs entry {group: gateway.networking.k8s.io, kind: Gateway, name: gw}.
  4. Observe: the policy is treated as having no matching target ("no targets found for the policy"); it does not attach, and changes to it don't trigger re-translation.
  5. Move the policy to namespace infra (same as the Gateway) → it attaches and re-translation works.

Would love to understand if there is a reason we dont have a cross-namespace support for extension-server Policy Resources.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions