Repository: envoyproxy/gateway

Issue:
Unable to attach custom resources as extension ref filter in GRPCRoutes
*Description*:
Hi,

I’m trying to attach a custom extension to a GRPCRoute using ExtensionRef. Specifically, I have a custom CRD called RouteMetadata that I want to attach via the ExtensionRef filter. like this

```
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: 6test
  namespace: default
spec:
  rules:
    - backendRefs:
        - group: gateway.envoyproxy.io
          kind: Backend
          name: test
      filters:
        - extensionRef:
            group: dp.wso2.com
            kind: RoutePolicy
            name: 6a254687f3229c35dd0189aac7f7fc4b6228e97a2d319a3fa5
          type: ExtensionRef
        - extensionRef:
            group: dp.wso2.com
            kind: RouteMetadata
            name: 6a254687f3229c35dd0189aac7f7fc4b6228e97a2d319a3fa5
          type: ExtensionRef
      matches:
        - method:
            method: GetStudent
```

This works fine when attaching to an HTTPRoute (I registered it under extensionManager.resources), but when I try with GRPCRoutes I get the following error:
message: 'Invalid filter ExtensionRef: unknown kind dp.wso2.com/RouteMetadata'
Looking at the code, I noticed that in one place ValidateGRPCRouteFilter is called without the external kinds list:
 :point_right: 
https://github.com/envoyproxy/gateway/blob/main/internal/gatewayapi/filters.go#L139

But in another place it’s called with the external kinds list:
 :point_right: https://github.com/envoyproxy/gateway/blob/e152be3424d64c6c189a931b6be431ad3b228fb6/internal/provider/kubernetes/routes.go#L153

Looks like a bug.

Discussion:

Solution patch:
diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go
index 47f0f5569..d1d07fcef 100644
--- a/internal/gatewayapi/filters.go
+++ b/internal/gatewayapi/filters.go
@@ -136,7 +136,7 @@ func (t *Translator) ProcessGRPCFilters(parentRef *RouteParentContext,
 		if httpFiltersContext.DirectResponse != nil {
 			break
 		}
-		if err := ValidateGRPCRouteFilter(&filter); err != nil {
+		if err := ValidateGRPCRouteFilter(&filter, t.ExtensionGroupKinds...); err != nil {
 			t.processInvalidHTTPFilter(string(filter.Type), httpFiltersContext, err)
 			break
 		}
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index 8678032ee..192f79b93 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -22,6 +22,7 @@ bug fixes: |
   Fixed indexer and controller crashing when BackendTrafficPolicy has a redirect response override.
   Fixed Lua validator log level to be suppressed by default.
   Fixed ProxyTopologyInjector cache sync race condition that caused injection failures
+  Fixed validation for grpc routes with extension ref filters.
 
 # Enhancements that improve performance.
 performance improvements: |



Test patch:
diff --git a/internal/gatewayapi/testdata/extensions/grpcroute-with-valid-extension-filter.in.yaml b/internal/gatewayapi/testdata/extensions/grpcroute-with-valid-extension-filter.in.yaml
new file mode 100644
index 000000000..c44e2ca8d
--- /dev/null
+++ b/internal/gatewayapi/testdata/extensions/grpcroute-with-valid-extension-filter.in.yaml
@@ -0,0 +1,48 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    namespace: envoy-gateway
+    name: gateway-1
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      allowedRoutes:
+        namespaces:
+          from: All
+grpcRoutes:
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: GRPCRoute
+  metadata:
+    namespace: default
+    name: grpcroute-1
+  spec:
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - method:
+          service: com.example.Service
+          type: Exact
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: foo.example.io
+          kind: Foo
+          name: test
+extensionRefFilters:
+- apiVersion: foo.example.io/v1alpha1
+  kind: Foo
+  metadata:
+    name: test
+    namespace: default
+  spec:
+    data: "stuff"
diff --git a/internal/gatewayapi/testdata/extensions/grpcroute-with-valid-extension-filter.out.yaml b/internal/gatewayapi/testdata/extensions/grpcroute-with-valid-extension-filter.out.yaml
new file mode 100644
index 000000000..be7d056cd
--- /dev/null
+++ b/internal/gatewayapi/testdata/extensions/grpcroute-with-valid-extension-filter.out.yaml
@@ -0,0 +1,184 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+grpcRoutes:
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: GRPCRoute
+  metadata:
+    creationTimestamp: null
+    name: grpcroute-1
+    namespace: default
+  spec:
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - extensionRef:
+          group: foo.example.io
+          kind: Foo
+          name: test
+        type: ExtensionRef
+      matches:
+      - method:
+          service: com.example.Service
+          type: Exact
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+        ownerReference:
+          kind: GatewayClass
+          name: envoy-gateway-class
+      name: envoy-gateway/gateway-1
+      namespace: ""
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      json:
+      - path: /dev/stdout
+    globalResources:
+      proxyServiceCluster:
+        name: envoy-gateway/gateway-1
+        settings:
+        - addressType: IP
+          endpoints:
+          - host: 7.6.5.4
+            port: 8080
+            zone: zone1
+          metadata:
+            name: envoy-envoy-gateway-gateway-1-196ae069
+            sectionName: "8080"
+          name: envoy-gateway/gateway-1
+          protocol: TCP
+    http:
+    - address: 0.0.0.0
+      externalPort: 80
+      hostnames:
+      - '*'
+      isHTTP2: true
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          metadata:
+            kind: GRPCRoute
+            name: grpcroute-1
+            namespace: default
+          name: grpcroute/default/grpcroute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            metadata:
+              name: service-1
+              namespace: default
+              sectionName: "8080"
+            name: grpcroute/default/grpcroute-1/rule/0/backend/0
+            protocol: GRPC
+            weight: 1
+        extensionRefs:
+        - object:
+            apiVersion: foo.example.io/v1alpha1
+            kind: Foo
+            metadata:
+              name: test
+              namespace: default
+            spec:
+              data: stuff
+        hostname: '*'
+        isHTTP2: true
+        metadata:
+          kind: GRPCRoute
+          name: grpcroute-1
+          namespace: default
+        name: grpcroute/default/grpcroute-1/rule/0/match/0/*
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /com.example.Service
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003