Repository: envoyproxy/gateway
Pull request #1758 (2023-08-03 01:47:27)

Add ClusterIP to the services in the egctl test data
**What type of PR is this?** fix: fix egctl test data, also add endpoints ValidateAll().
<!--
Your PR title should be descriptive, and generally start with type that contains a subsystem name with `()` if necessary 
and summary followed by a colon. format `chore/docs/feat/fix/refactor/style/test: summary`.
Examples:
* "docs: fix grammar error"
* "feat(translator): add new feature"
* "fix: fix xx bug"
* "chore: change ci & build tools etc"
-->

**What this PR does / why we need it**:  xds endpoint configuration requires address under socketAddress field. We need to provide ClusterIP in the input data file.

**Which issue(s) this PR fixes**: #1742 
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #


arkodg: thanks @Ronnie-personal ! does this allow you to add the `ValidateAll()` method back for Endpoints ?
Ronnie-personal: > thanks @Ronnie-personal ! does this allow you to add the `ValidateAll()` method back for Endpoints ?

Yes, we will be able to add  endpoints ValidateAll(), after this change.
I tested locally, egctl tests passed with ValidateAll() in place.
arkodg: Can you add ValidateAll into this PR as well ?
Ronnie-personal: > Can you add ValidateAll into this PR as well ?

Sure, will do.

Patch:
diff --git a/.github/codecov.yml b/.github/codecov.yml
index 13e511f21..af7439b56 100644
--- a/.github/codecov.yml
+++ b/.github/codecov.yml
@@ -5,3 +5,8 @@ coverage:
         target: 60%
         threshold: 5%
         if_ci_failed: error
+    patch:
+      default:
+        target: 60%
+        threshold: 5%
+        if_ci_failed: error
diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go
index 7aa208c57..9dcb5a708 100644
--- a/internal/gatewayapi/helpers.go
+++ b/internal/gatewayapi/helpers.go
@@ -254,6 +254,9 @@ func ValidateGRPCRouteFilter(filter *v1alpha2.GRPCRouteFilter, extGKs ...schema.
 		case string(filter.ExtensionRef.Group) == egv1a1.GroupVersion.Group &&
 			string(filter.ExtensionRef.Kind) == egv1a1.KindAuthenticationFilter:
 			return nil
+		case string(filter.ExtensionRef.Group) == egv1a1.GroupVersion.Group &&
+			string(filter.ExtensionRef.Kind) == egv1a1.KindRateLimitFilter:
+			return nil
 		default:
 			for _, gk := range extGKs {
 				if filter.ExtensionRef.Group == v1beta1.Group(gk.Group) &&
@@ -276,6 +279,14 @@ func IsAuthnGRPCFilter(filter *v1alpha2.GRPCRouteFilter) bool {
 		string(filter.ExtensionRef.Kind) == egv1a1.KindAuthenticationFilter
 }
 
+// IsRateLimitGRPCFilter returns true if the provided filter is an RateLimitFilter.
+func IsRateLimitGRPCFilter(filter *v1alpha2.GRPCRouteFilter) bool {
+	return filter.Type == v1alpha2.GRPCRouteFilterExtensionRef &&
+		filter.ExtensionRef != nil &&
+		string(filter.ExtensionRef.Group) == egv1a1.GroupVersion.Group &&
+		string(filter.ExtensionRef.Kind) == egv1a1.KindRateLimitFilter
+}
+
 // GatewayOwnerLabels returns the Gateway Owner labels using
 // the provided namespace and name as the values.
 func GatewayOwnerLabels(namespace, name string) map[string]string {
diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go
index 0dd53e400..36c98a6be 100644
--- a/internal/provider/kubernetes/controller.go
+++ b/internal/provider/kubernetes/controller.go
@@ -56,6 +56,7 @@ const (
 	authenFilterHTTPRouteIndex    = "authenHTTPRouteIndex"
 	rateLimitFilterHTTPRouteIndex = "rateLimitHTTPRouteIndex"
 	authenFilterGRPCRouteIndex    = "authenGRPCRouteIndex"
+	rateLimitFilterGRPCRouteIndex = "rateLimitGRPCRouteIndex"
 )
 
 type gatewayAPIReconciler struct {
@@ -663,6 +664,10 @@ func addGRPCRouteIndexers(ctx context.Context, mgr manager.Manager) error {
 		return err
 	}
 
+	if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.GRPCRoute{}, rateLimitFilterGRPCRouteIndex, rateLimitFilterGRPCRouteIndexFunc); err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -725,6 +730,27 @@ func authenFilterGRPCRouteIndexFunc(rawObj client.Object) []string {
 	return filters
 }
 
+func rateLimitFilterGRPCRouteIndexFunc(rawObj client.Object) []string {
+	grpcroute := rawObj.(*gwapiv1a2.GRPCRoute)
+	var filters []string
+	for _, rule := range grpcroute.Spec.Rules {
+		for i := range rule.Filters {
+			filter := rule.Filters[i]
+			if gatewayapi.IsRateLimitGRPCFilter(&filter) {
+				if err := gatewayapi.ValidateGRPCRouteFilter(&filter); err == nil {
+					filters = append(filters,
+						types.NamespacedName{
+							Namespace: grpcroute.Namespace,
+							Name:      string(filter.ExtensionRef.Name),
+						}.String(),
+					)
+				}
+			}
+		}
+	}
+	return filters
+}
+
 // addTLSRouteIndexers adds indexing on TLSRoute, for Service objects that are
 // referenced in TLSRoute objects via `.spec.rules.backendRefs`. This helps in
 // querying for TLSRoutes that are affected by a particular Service CRUD.
diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go
index 025b5e595..ecb954081 100644
--- a/internal/provider/kubernetes/routes.go
+++ b/internal/provider/kubernetes/routes.go
@@ -87,7 +87,8 @@ func (r *gatewayAPIReconciler) processGRPCRoutes(ctx context.Context, gatewayNam
 	resourceMap *resourceMappings, resourceTree *gatewayapi.Resources) error {
 	grpcRouteList := &gwapiv1a2.GRPCRouteList{}
 
-	// An GRPCRoute may reference an AuthenticationFilter, so add them to the resource map first (if they exist).
+	// An GRPCRoute may reference an AuthenticationFilter and RateLimitFilter,
+	// so add them to the resource map first (if they exist).
 	authenFilters, err := r.getAuthenticationFilters(ctx)
 	if err != nil {
 		return err
@@ -97,6 +98,15 @@ func (r *gatewayAPIReconciler) processGRPCRoutes(ctx context.Context, gatewayNam
 		resourceMap.authenFilters[utils.NamespacedName(&filter)] = &filter
 	}
 
+	rateLimitFilters, err := r.getRateLimitFilters(ctx)
+	if err != nil {
+		return err
+	}
+	for i := range rateLimitFilters {
+		filter := rateLimitFilters[i]
+		resourceMap.rateLimitFilters[utils.NamespacedName(&filter)] = &filter
+	}
+
 	if err := r.client.List(ctx, grpcRouteList, &client.ListOptions{
 		FieldSelector: fields.OneTermEqualSelector(gatewayGRPCRouteIndex, gatewayNamespaceName),
 	}); err != nil {
@@ -163,12 +173,20 @@ func (r *gatewayAPIReconciler) processGRPCRoutes(ctx context.Context, gatewayNam
 						Namespace: grpcRoute.Namespace,
 						Name:      string(filter.ExtensionRef.Name),
 					}
+
 					authFilter, ok := resourceMap.authenFilters[key]
 					if !ok {
 						r.log.Error(err, "AuthenticationFilter not found; bypassing rule", "index", i)
 						continue
 					}
 					resourceTree.AuthenticationFilters = append(resourceTree.AuthenticationFilters, authFilter)
+
+					rateLimitFilter, ok := resourceMap.rateLimitFilters[key]
+					if !ok {
+						r.log.Error(err, "RateLimitFilter not found; bypassing rule", "index", i)
+						continue
+					}
+					resourceTree.RateLimitFilters = append(resourceTree.RateLimitFilters, rateLimitFilter)
 				}
 			}
 		}
diff --git a/internal/xds/types/resourceversiontable.go b/internal/xds/types/resourceversiontable.go
index 06eb94f7e..067f67273 100644
--- a/internal/xds/types/resourceversiontable.go
+++ b/internal/xds/types/resourceversiontable.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 
 	clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
+	endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
 	listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
@@ -107,8 +108,13 @@ func (t *ResourceVersionTable) AddXdsResource(rType resourcev3.Type, xdsResource
 		}
 
 	case resourcev3.EndpointType:
-		// TBD - ValidateAll() breaks existing test internal/cmd/egctl/translate_test
-		// authn-single-route-single-match-to-xds.endpoint expects address for socketAddress field, but this field currently only has port, does not have address
+		if resourceOfType, ok := xdsResource.(*endpointv3.ClusterLoadAssignment); ok {
+			if err := resourceOfType.ValidateAll(); err != nil {
+				return fmt.Errorf("validation failed for xds resource %+v, err:%v", xdsResource, err)
+			}
+		} else {
+			return fmt.Errorf("failed to cast xds resource %+v to ClusterLoadAssignment type", xdsResource)
+		}
 
 	case resourcev3.ClusterType:
 		// Handle specific operations


Test patch:
diff --git a/docs/latest/user/authn.md b/docs/latest/user/authn.md
index f74059062..0ab4d96f3 100644
--- a/docs/latest/user/authn.md
+++ b/docs/latest/user/authn.md
@@ -7,11 +7,14 @@ supports validating a JWT from an HTTP header, e.g. `Authorization: Bearer <toke
 ## Installation
 
 Follow the steps from the [Quickstart](quickstart.md) guide to install Envoy Gateway and the example manifest.
-Before proceeding, you should be able to query the example backend using HTTP.
+For GRPC - follow the steps from the [GRPC Routing](grpc-routing.md) example.
+Before proceeding, you should be able to query the example backend using HTTP or GRPC.
 
 ## Configuration
 
-Allow requests with a valid JWT by creating an [AuthenticationFilter][] and referencing it from the example HTTPRoute.
+Allow requests with a valid JWT by creating an [AuthenticationFilter][] and referencing it from the example HTTPRoute or GRPCRoute.
+
+### HTTPRoute
 
 ```shell
 kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/latest/examples/kubernetes/authn/jwt.yaml
@@ -35,6 +38,29 @@ Verify the AuthenticationFilter configuration:
 kubectl get authenticationfilter/jwt-example -o yaml
 ```
 
+### GRPCRoute
+
+```shell
+kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/latest/examples/kubernetes/authn/grtpc-jwt.yaml
+```
+
+The GRPCRoute is now updated to authenticate all requests to `yages` service, by referencing an AuthenticationFilter that provides the JWT authentication configuration.
+
+Verify the GRPCRoute configuration and status:
+
+```shell
+kubectl get grpcroute/yages -o yaml
+```
+
+The AuthenticationFilter is configured for JWT authentication and uses a single [JSON Web Key Set (JWKS)][jwks]
+provider for authenticating the JWT.
+
+Verify the AuthenticationFilter configuration:
+
+```shell
+kubectl get authenticationfilter/jwt-example -o yaml
+```
+
 ## Testing
 
 Ensure the `GATEWAY_HOST` environment variable from the [Quickstart](quickstart.md) guide is set. If not, follow the
@@ -44,6 +70,8 @@ Quickstart instructions to set the variable.
 echo $GATEWAY_HOST
 ```
 
+### HTTPRoute
+
 Verify that requests to `/foo` are denied without a JWT:
 
 ```shell
@@ -75,6 +103,43 @@ Verify that requests to `/bar` are allowed __without__ a JWT:
 curl -sS -o /dev/null -H "Host: www.example.com" -w "%{http_code}\n" http://$GATEWAY_HOST/bar
 ```
 
+### GRPCRoute
+
+Verify that requests to `yages`service are denied without a JWT:
+
+```shell
+grpcurl -plaintext -authority=grpc-example.com ${GATEWAY_HOST}:80 yages.Echo/Ping
+```
+
+You should see the below response
+
+```shell
+Error invoking method "yages.Echo/Ping": rpc error: code = Unauthenticated desc = failed to query for service descriptor "yages.Echo": Jwt is missing
+```
+
+Get the JWT used for testing request authentication:
+
+```shell
+TOKEN=$(curl https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/authn/test.jwt -s) && echo "$TOKEN" | cut -d '.' -f2 - | base64 --decode -
+```
+
+__Note:__ The above command decodes and returns the token's payload. You can replace `f2` with `f1` to view the token's
+header.
+
+Verify that a request to `yages` service with a valid JWT is allowed:
+
+```shell
+grpcurl -plaintext -H "authorization: Bearer $TOKEN" -authority=grpc-example.com ${GATEWAY_HOST}:80 yages.Echo/Ping
+```
+
+You should see the below response
+
+```shell
+{
+  "text": "pong"
+}
+```
+
 ## Clean-Up
 
 Follow the steps from the [Quickstart](quickstart.md) guide to uninstall Envoy Gateway and the example manifest.
diff --git a/internal/cmd/egctl/testdata/translate/in/authn-single-route-single-match-to-xds.yaml b/internal/cmd/egctl/testdata/translate/in/authn-single-route-single-match-to-xds.yaml
index 50856df63..a2100a22c 100644
--- a/internal/cmd/egctl/testdata/translate/in/authn-single-route-single-match-to-xds.yaml
+++ b/internal/cmd/egctl/testdata/translate/in/authn-single-route-single-match-to-xds.yaml
@@ -29,6 +29,7 @@ metadata:
     app: backend
     service: backend
 spec:
+  clusterIP: 7.7.7.7
   ports:
     - name: http
       port: 3000
diff --git a/internal/cmd/egctl/testdata/translate/in/echo-gateway-api.yaml b/internal/cmd/egctl/testdata/translate/in/echo-gateway-api.yaml
index 032216678..3cba5df26 100644
--- a/internal/cmd/egctl/testdata/translate/in/echo-gateway-api.yaml
+++ b/internal/cmd/egctl/testdata/translate/in/echo-gateway-api.yaml
@@ -29,6 +29,7 @@ metadata:
     app: backend
     service: backend
 spec:
+  clusterIP: 7.7.7.7
   ports:
     - name: http
       port: 3000
diff --git a/internal/cmd/egctl/testdata/translate/in/envoy-patch-policy.yaml b/internal/cmd/egctl/testdata/translate/in/envoy-patch-policy.yaml
index 4851a4684..17b25b2dd 100644
--- a/internal/cmd/egctl/testdata/translate/in/envoy-patch-policy.yaml
+++ b/internal/cmd/egctl/testdata/translate/in/envoy-patch-policy.yaml
@@ -32,6 +32,7 @@ metadata:
     app: backend
     service: backend
 spec:
+  clusterIP: 7.7.7.7
   ports:
     - name: http
       port: 3000
diff --git a/internal/cmd/egctl/testdata/translate/in/quickstart.yaml b/internal/cmd/egctl/testdata/translate/in/quickstart.yaml
index 032216678..3cba5df26 100644
--- a/internal/cmd/egctl/testdata/translate/in/quickstart.yaml
+++ b/internal/cmd/egctl/testdata/translate/in/quickstart.yaml
@@ -29,6 +29,7 @@ metadata:
     app: backend
     service: backend
 spec:
+  clusterIP: 7.7.7.7
   ports:
     - name: http
       port: 3000
diff --git a/internal/cmd/egctl/testdata/translate/in/rate-limit-filter-single-route-single-match-to-xds.yaml b/internal/cmd/egctl/testdata/translate/in/rate-limit-filter-single-route-single-match-to-xds.yaml
index b49473491..040c9c074 100644
--- a/internal/cmd/egctl/testdata/translate/in/rate-limit-filter-single-route-single-match-to-xds.yaml
+++ b/internal/cmd/egctl/testdata/translate/in/rate-limit-filter-single-route-single-match-to-xds.yaml
@@ -29,6 +29,7 @@ metadata:
     app: backend
     service: backend
 spec:
+  clusterIP: 7.7.7.7
   ports:
     - name: http
       port: 3000
diff --git a/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.all.json b/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.all.json
index 344e7b8e6..013d43169 100644
--- a/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.all.json
+++ b/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.all.json
@@ -191,6 +191,7 @@
                         "endpoint": {
                           "address": {
                             "socketAddress": {
+                              "address": "7.7.7.7",
                               "portValue": 3000
                             }
                           }
diff --git a/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.all.yaml b/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.all.yaml
index 040df2cf7..80d5cd100 100644
--- a/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.all.yaml
@@ -109,6 +109,7 @@ xds:
             - endpoint:
                 address:
                   socketAddress:
+                    address: 7.7.7.7
                     portValue: 3000
               loadBalancingWeight: 1
             loadBalancingWeight: 1
diff --git a/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.endpoint.yaml b/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.endpoint.yaml
index a579fde18..d83fc305d 100644
--- a/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.endpoint.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/authn-single-route-single-match-to-xds.endpoint.yaml
@@ -10,6 +10,7 @@ xds:
           - endpoint:
               address:
                 socketAddress:
+                  address: "7.7.7.7"
                   portValue: 3000
             loadBalancingWeight: 1
           loadBalancingWeight: 1
diff --git a/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.all.json b/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.all.json
index 81e300b12..109313bcd 100644
--- a/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.all.json
+++ b/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.all.json
@@ -191,6 +191,7 @@
                         "endpoint": {
                           "address": {
                             "socketAddress": {
+                              "address": "7.7.7.7",
                               "portValue": 3000
                             }
                           }
diff --git a/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.all.yaml b/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.all.yaml
index 6eb4954ec..e2b2e5a2a 100644
--- a/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.all.yaml
@@ -109,6 +109,7 @@ xds:
             - endpoint:
                 address:
                   socketAddress:
+                    address: 7.7.7.7
                     portValue: 3000
               loadBalancingWeight: 1
             loadBalancingWeight: 1
diff --git a/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.endpoint.yaml b/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.endpoint.yaml
index de63801b2..85c4f2ee3 100644
--- a/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.endpoint.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/rate-limit-filter-single-route-single-match-to-xds.endpoint.yaml
@@ -10,6 +10,7 @@ xds:
           - endpoint:
               address:
                 socketAddress:
+                  address: 7.7.7.7
                   portValue: 3000
             loadBalancingWeight: 1
           loadBalancingWeight: 1
diff --git a/internal/gatewayapi/helpers_test.go b/internal/gatewayapi/helpers_test.go
index 8379007f6..99fcc7bfa 100644
--- a/internal/gatewayapi/helpers_test.go
+++ b/internal/gatewayapi/helpers_test.go
@@ -128,6 +128,42 @@ func TestValidateGRPCFilterRef(t *testing.T) {
 			},
 			expected: true,
 		},
+		{
+			name: "invalid ratelimitfilter group",
+			filter: &gwapiv1a2.GRPCRouteFilter{
+				Type: gwapiv1a2.GRPCRouteFilterExtensionRef,
+				ExtensionRef: &gwapiv1b1.LocalObjectReference{
+					Group: "UnsupportedGroup",
+					Kind:  egv1a1.KindRateLimitFilter,
+					Name:  "test",
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "invalid ratelimitfilter kind",
+			filter: &gwapiv1a2.GRPCRouteFilter{
+				Type: gwapiv1a2.GRPCRouteFilterExtensionRef,
+				ExtensionRef: &gwapiv1b1.LocalObjectReference{
+					Group: gwapiv1b1.Group(egv1a1.GroupVersion.Group),
+					Kind:  "UnsupportedKind",
+					Name:  "test",
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "valid ratelimitfilter",
+			filter: &gwapiv1a2.GRPCRouteFilter{
+				Type: gwapiv1a2.GRPCRouteFilterExtensionRef,
+				ExtensionRef: &gwapiv1b1.LocalObjectReference{
+					Group: gwapiv1b1.Group(egv1a1.GroupVersion.Group),
+					Kind:  egv1a1.KindRateLimitFilter,
+					Name:  "test",
+				},
+			},
+			expected: true,
+		},
 	}
 	for _, tc := range testCases {
 		tc := tc
diff --git a/internal/gatewayapi/testdata/grpcroute-with-valid-ratelimitfilter.in.yaml b/internal/gatewayapi/testdata/grpcroute-with-valid-ratelimitfilter.in.yaml
new file mode 100644
index 000000000..06766cdf7
--- /dev/null
+++ b/internal/gatewayapi/testdata/grpcroute-with-valid-ratelimitfilter.in.yaml
@@ -0,0 +1,55 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1beta1
+  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:
+    - filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: RateLimitFilter
+          name: test
+      backendRefs:
+      - name: service-1
+        port: 8080
+rateLimitFilters:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: RateLimitFilter
+  metadata:
+    namespace: default
+    name: test
+  spec:
+    type: Global
+    global:
+      rules:
+      - clientSelectors:
+        - headers:
+          - name: x-user-id
+            value: one
+          - name: x-org-id
+            type: Distinct
+        limit:
+          requests: 10
+          unit: Hour
diff --git a/internal/gatewayapi/testdata/grpcroute-with-valid-ratelimitfilter.out.yaml b/internal/gatewayapi/testdata/grpcroute-with-valid-ratelimitfilter.out.yaml
new file mode 100644
index 000000000..4ce3b9157
--- /dev/null
+++ b/internal/gatewayapi/testdata/grpcroute-with-valid-ratelimitfilter.out.yaml
@@ -0,0 +1,124 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1beta1
+  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
+      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: gateway.envoyproxy.io
+          kind: RateLimitFilter
+          name: test
+        type: ExtensionRef
+  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: ""
+        ports:
+        - containerPort: 10080
+          name: http
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: true
+      name: envoy-gateway/gateway-1/http
+      port: 10080
+      routes:
+      - backendWeights:
+          invalid: 0
+          valid: 0
+        destinations:
+        - host: 7.7.7.7
+          port: 8080
+          weight: 1
+        name: default/grpcroute-1/rule/0/match/-1-*
+        rateLimit:
+          global:
+            rules:
+            - headerMatches:
+              - distinct: false
+                exact: one
+                name: x-user-id
+              - distinct: true
+                name: x-org-id
+              limit:
+                requests: 10
+                unit: Hour
diff --git a/internal/provider/kubernetes/routes_test.go b/internal/provider/kubernetes/routes_test.go
index 0ce43615a..1baf56acd 100644
--- a/internal/provider/kubernetes/routes_test.go
+++ b/internal/provider/kubernetes/routes_test.go
@@ -587,6 +587,7 @@ func TestProcessGRPCRoutes(t *testing.T) {
 		name               string
 		routes             []*gwapiv1a2.GRPCRoute
 		authenFilters      []*egv1a1.AuthenticationFilter
+		rateLimitFilters   []*egv1a1.RateLimitFilter
 		extensionAPIGroups []schema.GroupVersionKind
 		expected           bool
 	}{
@@ -711,6 +712,94 @@ func TestProcessGRPCRoutes(t *testing.T) {
 			},
 			expected: true,
 		},
+		{
+			name: "grpcroute with one ratelimitfilter",
+			routes: []*gwapiv1a2.GRPCRoute{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "test",
+						Name:      "test",
+					},
+					Spec: gwapiv1a2.GRPCRouteSpec{
+						CommonRouteSpec: gwapiv1b1.CommonRouteSpec{
+							ParentRefs: []gwapiv1b1.ParentReference{
+								{
+									Name: "test",
+								},
+							},
+						},
+						Rules: []gwapiv1a2.GRPCRouteRule{
+							{
+								Matches: []gwapiv1a2.GRPCRouteMatch{
+									{
+										Method: &gwapiv1a2.GRPCMethodMatch{
+											Method: gatewayapi.StringPtr("Ping"),
+										},
+									},
+								},
+								Filters: []gwapiv1a2.GRPCRouteFilter{
+									{
+										Type: gwapiv1a2.GRPCRouteFilterExtensionRef,
+										ExtensionRef: &gwapiv1b1.LocalObjectReference{
+											Group: gwapiv1b1.Group(egv1a1.GroupVersion.Group),
+											Kind:  gwapiv1b1.Kind(egv1a1.KindRateLimitFilter),
+											Name:  gwapiv1b1.ObjectName("test"),
+										},
+									},
+								},
+								BackendRefs: []gwapiv1a2.GRPCBackendRef{
+									{
+										BackendRef: gwapiv1b1.BackendRef{
+											BackendObjectReference: gwapiv1b1.BackendObjectReference{
+												Group: gatewayapi.GroupPtr(corev1.GroupName),
+												Kind:  gatewayapi.KindPtr(gatewayapi.KindService),
+												Name:  "test",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			rateLimitFilters: []*egv1a1.RateLimitFilter{
+				{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       egv1a1.KindRateLimitFilter,
+						APIVersion: egv1a1.GroupVersion.String(),
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "test",
+						Name:      "test",
+					},
+					Spec: egv1a1.RateLimitFilterSpec{
+						Type: egv1a1.KindRateLimitFilter,
+						Global: &egv1a1.GlobalRateLimit{
+							Rules: []egv1a1.RateLimitRule{
+								{
+									ClientSelectors: []egv1a1.RateLimitSelectCondition{
+										{
+											Headers: []egv1a1.HeaderMatch{
+												{
+													Name:  "x-user-id",
+													Value: gatewayapi.StringPtr("one"),
+												},
+											},
+										},
+									},
+									Limit: egv1a1.RateLimitValue{
+										Requests: 5,
+										Unit:     "Second",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expected: true,
+		},
 	}
 
 	for i := range testCases {
@@ -737,6 +826,9 @@ func TestProcessGRPCRoutes(t *testing.T) {
 			for _, filter := range tc.authenFilters {
 				objs = append(objs, filter)
 			}
+			for _, filter := range tc.rateLimitFilters {
+				objs = append(objs, filter)
+			}
 			if len(tc.extensionAPIGroups) > 0 {
 				r.extGVKs = append(r.extGVKs, tc.extensionAPIGroups...)
 			}
@@ -764,6 +856,15 @@ func TestProcessGRPCRoutes(t *testing.T) {
 						require.Equal(t, filter, resourceMap.authenFilters[key])
 					}
 				}
+				if tc.rateLimitFilters != nil {
+					for i, filter := range tc.rateLimitFilters {
+						key := types.NamespacedName{
+							Namespace: tc.routes[i].Namespace,
+							Name:      filter.Name,
+						}
+						require.Equal(t, filter, resourceMap.rateLimitFilters[key])
+					}
+				}
 			} else {
 				require.Error(t, err)
 			}
diff --git a/internal/xds/translator/testdata/in/xds-ir/tcp-route-invalid-endpoint.yaml b/internal/xds/translator/testdata/in/xds-ir/tcp-route-invalid-endpoint.yaml
new file mode 100644
index 000000000..7f38550e8
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/tcp-route-invalid-endpoint.yaml
@@ -0,0 +1,8 @@
+tcp:
+- name: "tcp-route-simple"
+  address: "0.0.0.0"
+  port: 10080
+  destinations:
+  - port: 50000
+  - host: "5.6.7.8"
+    port: 50001
diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go
index 11b082675..590938fa2 100644
--- a/internal/xds/translator/translator_test.go
+++ b/internal/xds/translator/translator_test.go
@@ -239,6 +239,9 @@ func TestTranslateXdsNegative(t *testing.T) {
 		{
 			name: "tcp-route-invalid",
 		},
+		{
+			name: "tcp-route-invalid-endpoint",
+		},
 		{
 			name: "udp-route-invalid",
 		},
diff --git a/internal/xds/types/resourceversiontable_test.go b/internal/xds/types/resourceversiontable_test.go
index 61dbe1ce4..8da1c0cc7 100644
--- a/internal/xds/types/resourceversiontable_test.go
+++ b/internal/xds/types/resourceversiontable_test.go
@@ -170,6 +170,58 @@ func TestAddOrReplaceXdsResource(t *testing.T) {
 			},
 		},
 	}
+	testEndpoint := &endpointv3.ClusterLoadAssignment{
+		ClusterName: "test-cluster",
+		Endpoints: []*endpointv3.LocalityLbEndpoints{
+			{
+				LbEndpoints: []*endpointv3.LbEndpoint{
+					{
+						HostIdentifier: &endpointv3.LbEndpoint_Endpoint{
+							Endpoint: &endpointv3.Endpoint{
+								Address: &corev3.Address{
+									Address: &corev3.Address_SocketAddress{
+										SocketAddress: &corev3.SocketAddress{
+											Address: "exampleservice.examplenamespace.svc.cluster.local",
+											PortSpecifier: &corev3.SocketAddress_PortValue{
+												PortValue: 5000,
+											},
+											Protocol: corev3.SocketAddress_TCP,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	updatedEndpoint := &endpointv3.ClusterLoadAssignment{
+		ClusterName: "test-cluster",
+		Endpoints: []*endpointv3.LocalityLbEndpoints{
+			{
+				LbEndpoints: []*endpointv3.LbEndpoint{
+					{
+						HostIdentifier: &endpointv3.LbEndpoint_Endpoint{
+							Endpoint: &endpointv3.Endpoint{
+								Address: &corev3.Address{
+									Address: &corev3.Address_SocketAddress{
+										SocketAddress: &corev3.SocketAddress{
+											Address: "modified.example.svc.cluster.local",
+											PortSpecifier: &corev3.SocketAddress_PortValue{
+												PortValue: 5000,
+											},
+											Protocol: corev3.SocketAddress_TCP,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
 	testRouteConfig := &routev3.RouteConfiguration{
 		Name: "test-route-config",
 		VirtualHosts: []*routev3.VirtualHost{
@@ -274,6 +326,58 @@ func TestAddOrReplaceXdsResource(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "inject-new-endpoint",
+			tableIn: &ResourceVersionTable{
+				XdsResources: XdsResources{
+					resourcev3.EndpointType: []types.Resource{},
+				},
+			},
+			typeIn:     resourcev3.EndpointType,
+			resourceIn: testEndpoint,
+			funcIn: func(existing types.Resource, new types.Resource) bool {
+				oldEndpoint := existing.(*endpointv3.ClusterLoadAssignment)
+				newEndpoint := new.(*endpointv3.ClusterLoadAssignment)
+				if newEndpoint == nil || oldEndpoint == nil {
+					return false
+				}
+				if oldEndpoint.ClusterName == newEndpoint.ClusterName {
+					return true
+				}
+				return false
+			},
+			tableOut: &ResourceVersionTable{
+				XdsResources: XdsResources{
+					resourcev3.EndpointType: []types.Resource{testEndpoint},
+				},
+			},
+		},
+		{
+			name: "replace-endpoint",
+			tableIn: &ResourceVersionTable{
+				XdsResources: XdsResources{
+					resourcev3.EndpointType: []types.Resource{testEndpoint},
+				},
+			},
+			typeIn:     resourcev3.EndpointType,
+			resourceIn: updatedEndpoint,
+			funcIn: func(existing types.Resource, new types.Resource) bool {
+				oldEndpoint := existing.(*endpointv3.ClusterLoadAssignment)
+				newEndpoint := new.(*endpointv3.ClusterLoadAssignment)
+				if newEndpoint == nil || oldEndpoint == nil {
+					return false
+				}
+				if oldEndpoint.ClusterName == newEndpoint.ClusterName {
+					return true
+				}
+				return false
+			},
+			tableOut: &ResourceVersionTable{
+				XdsResources: XdsResources{
+					resourcev3.EndpointType: []types.Resource{updatedEndpoint},
+				},
+			},
+		},
 		{
 			name: "inject-new-listener",
 			tableIn: &ResourceVersionTable{
@@ -507,6 +611,32 @@ func TestInvalidAddXdsResource(t *testing.T) {
 			},
 		},
 	}
+	invalidEndpoint := &endpointv3.ClusterLoadAssignment{
+		ClusterName: "test-cluster",
+		Endpoints: []*endpointv3.LocalityLbEndpoints{
+			{
+				LbEndpoints: []*endpointv3.LbEndpoint{
+					{
+						HostIdentifier: &endpointv3.LbEndpoint_Endpoint{
+							Endpoint: &endpointv3.Endpoint{
+								Address: &corev3.Address{
+									Address: &corev3.Address_SocketAddress{
+										SocketAddress: &corev3.SocketAddress{
+											Address: "",
+											PortSpecifier: &corev3.SocketAddress_PortValue{
+												PortValue: 5000,
+											},
+											Protocol: corev3.SocketAddress_TCP,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
 
 	invalidSecret := &tlsv3.Secret{
 		Name: "=*&",
@@ -532,7 +662,7 @@ func TestInvalidAddXdsResource(t *testing.T) {
 		tableOut   *ResourceVersionTable
 	}{
 		{
-			name: "inject-new-listener",
+			name: "inject-invalid-listener",
 			tableIn: &ResourceVersionTable{
 				XdsResources: XdsResources{
 					resourcev3.ListenerType: []types.Resource{},
@@ -558,7 +688,7 @@ func TestInvalidAddXdsResource(t *testing.T) {
 			},
 		},
 		{
-			name: "inject-new-route-config",
+			name: "inject-invalid-route-config",
 			tableIn: &ResourceVersionTable{
 				XdsResources: XdsResources{
 					resourcev3.RouteType: []types.Resource{},
@@ -580,7 +710,7 @@ func TestInvalidAddXdsResource(t *testing.T) {
 			tableOut: nil,
 		},
 		{
-			name: "inject-new-cluster",
+			name: "inject-invalid-cluster",
 			tableIn: &ResourceVersionTable{
 				XdsResources: XdsResources{
 					resourcev3.ClusterType: []types.Resource{},
@@ -711,6 +841,50 @@ func TestInvalidAddXdsResource(t *testing.T) {
 			},
 			tableOut: nil,
 		},
+		{
+			name: "inject-invalid-endpoint",
+			tableIn: &ResourceVersionTable{
+				XdsResources: XdsResources{
+					resourcev3.EndpointType: []types.Resource{},
+				},
+			},
+			typeIn:     resourcev3.EndpointType,
+			resourceIn: invalidEndpoint,
+			funcIn: func(existing types.Resource, new types.Resource) bool {
+				oldEndpoint := existing.(*endpointv3.ClusterLoadAssignment)
+				newEndpoint := new.(*endpointv3.ClusterLoadAssignment)
+				if newEndpoint == nil || oldEndpoint == nil {
+					return false
+				}
+				if oldEndpoint.ClusterName == newEndpoint.ClusterName {
+					return true
+				}
+				return false
+			},
+			tableOut: nil,
+		},
+		{
+			name: "cast-endpoint-type",
+			tableIn: &ResourceVersionTable{
+				XdsResources: XdsResources{
+					resourcev3.EndpointType: []types.Resource{},
+				},
+			},
+			typeIn:     resourcev3.EndpointType,
+			resourceIn: invalidListener,
+			funcIn: func(existing types.Resource, new types.Resource) bool {
+				oldEndpoint := existing.(*endpointv3.ClusterLoadAssignment)
+				newEndpoint := new.(*endpointv3.ClusterLoadAssignment)
+				if newEndpoint == nil || oldEndpoint == nil {
+					return false
+				}
+				if oldEndpoint.ClusterName == newEndpoint.ClusterName {
+					return true
+				}
+				return false
+			},
+			tableOut: nil,
+		},
 	}
 	for _, tc := range testCases {
 		tc := tc