Repository: envoyproxy/gateway
Pull request #3399 (2024-05-15 18:15:30)

feat: authorization implementation
This PR adds gateway API translator, XDS translator, and e2e tests for the `Authorization` setting of the `SecurityPolicy`.

Only source IP is supported for now.  Other principals such as JWT claims, basic auth, etc. will be supported later.

Fix: https://github.com/envoyproxy/gateway/issues/2462 
Related: https://github.com/envoyproxy/gateway/issues/2250


Example config:
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: http-with-authorization-client-ip
  namespace: gateway-conformance-infra
spec:
  parentRefs:
  - name: same-namespace
  rules:
  - matches:
    - path:
        type: Exact
        value: /protected
    backendRefs:
    - name: infra-backend-v1
      port: 8080
---
      
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
  name: authorization-client-ip
  namespace: gateway-conformance-infra
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: http-with-authorization-client-ip
  authorization:
    defaultAction: Deny
    rules:
    - action: Allow
      principal:
        clientCIDRs:
        - 10.0.1.0/24
        - 10.0.2.0/24
---

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: ClientTrafficPolicy
metadata:
  name: enable-client-ip-detection
  namespace: gateway-conformance-infra
spec:
  clientIPDetection:
    xForwardedFor:
      numTrustedHops: 1
  targetRef:
    group: gateway.networking.k8s.io
    kind: Gateway
    name: same-namespace
```

Test results:
```bash
curl -I --header "Host: www.example.com" --header "X-Forwarded-For: 10.0.2.1" http://172.18.255.203:80/protected
HTTP/1.1 200 OK

curl -I --header "Host: www.example.com" --header "X-Forwarded-For: 10.0.3.1" http://172.18.255.203:80/protected
HTTP/1.1 403 Forbidden
```

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
Attention: Patch coverage is `80.73394%` with `42 lines` in your changes are missing coverage. Please review.
> Project coverage is 67.51%. Comparing base [(`c4b0216`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/c4b021671fd1a1df851959775d956ec929ffbc89?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`c77ba23`)](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).
> Report is 15 commits behind head on main.

| [Files](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) | Patch % | Lines |
|---|---|---|
| [internal/xds/translator/authorization.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&filepath=internal%2Fxds%2Ftranslator%2Fauthorization.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwveGRzL3RyYW5zbGF0b3IvYXV0aG9yaXphdGlvbi5nbw==) | 78.01% | [16 Missing and 15 partials :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/gatewayapi/securitypolicy.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Fsecuritypolicy.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9zZWN1cml0eXBvbGljeS5nbw==) | 88.46% | [3 Missing and 3 partials :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/gatewayapi/helpers.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Fhelpers.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9oZWxwZXJzLmdv) | 81.81% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/gatewayapi/backendtrafficpolicy.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Fbackendtrafficpolicy.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9iYWNrZW5kdHJhZmZpY3BvbGljeS5nbw==) | 75.00% | [1 Missing :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/xds/translator/local\_ratelimit.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&filepath=internal%2Fxds%2Ftranslator%2Flocal_ratelimit.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwveGRzL3RyYW5zbGF0b3IvbG9jYWxfcmF0ZWxpbWl0Lmdv) | 50.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/xds/translator/ratelimit.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&filepath=internal%2Fxds%2Ftranslator%2Fratelimit.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwveGRzL3RyYW5zbGF0b3IvcmF0ZWxpbWl0Lmdv) | 50.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #3399      +/-   ##
==========================================
+ Coverage   66.97%   67.51%   +0.54%     
==========================================
  Files         164      167       +3     
  Lines       23882    19364    -4518     
==========================================
- Hits        15994    13073    -2921     
+ Misses       6964     5342    -1622     
- Partials      924      949      +25     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/3399?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

zufardhiyaulhaq: @zhaohuabing questions, what is the differentiation of Authorization API & SecurityPolicy API.
since currently JWT, basic auth is located on securitypolicy. do we plan to move it to Authorization API in the future?
zhaohuabing: @zufardhiyaulhaq Authorization is part of the `SeucrityPolicy`, as the example in the PR description shows.
zufardhiyaulhaq: ahh just realized that @zhaohuabing thanks!
zetaab: I am testing this and got error

`'Unable to translate authorization rule: invalid CIDR address: 1.2.3.4'`

I could apply the configuration fine, but I was wondering why it was not provisioned. I could see that error in securitypolicy status. Just thinking, should it be validated in creation that ip address contains mask as well?
zhaohuabing: > I am testing this and got error
> 
> `'Unable to translate authorization rule: invalid CIDR address: 1.2.3.4'`
> 
> I could apply the configuration fine, but I was wondering why it was not provisioned. I could see that error in securitypolicy status. Just thinking, should it be validated in creation of securitypolicy that ip address contains mask as well?

Good point. I added a regex validation for CIDR. It should prevent most invalidate input such as lacking mask, but still need a second check in gateway API translation for some corner cases.

> Otherwise looks good, I have verified and looks like working like should. However, I am just thinking that we might need to split the filter to two different rbac filters? I am thinking like ip policies as first in order, and then others after authentication things like jwt (that we can use jwt information for authorization)?

I actually proposed the same during the API review. :-)

Most gateway implementations have a standalone IP Allow/Deny list. However, some, like the Istio Authorization API, integrate this feature into their Authorization API.

Incorporating the client IP range in the Authorization enables use cases that require a combination of the client IP and other conditions. For instance, " allows admin users access an HTTPRoute from use-west-region, but not from other regions", which is either impossible or significantly challenging  to implement with two independent filters.


Patch:
diff --git a/api/v1alpha1/authorization_types.go b/api/v1alpha1/authorization_types.go
index c52a2063b..e02ba4daf 100644
--- a/api/v1alpha1/authorization_types.go
+++ b/api/v1alpha1/authorization_types.go
@@ -6,7 +6,8 @@
 package v1alpha1
 
 // Authorization defines the authorization configuration.
-// +notImplementedHide
+//
+// Note: if neither `Rules` nor `DefaultAction` is specified, the default action is to deny all requests.
 type Authorization struct {
 	// Rules defines a list of authorization rules.
 	// These rules are evaluated in order, the first matching rule will be applied,
@@ -16,50 +17,53 @@ type Authorization struct {
 	// and the second rule denies it, when a request matches both rules, it will be allowed.
 	//
 	// +optional
-	Rules []Rule `json:"rules,omitempty"`
+	Rules []AuthorizationRule `json:"rules,omitempty"`
 
 	// DefaultAction defines the default action to be taken if no rules match.
 	// If not specified, the default action is Deny.
 	// +optional
-	DefaultAction *RuleActionType `json:"defaultAction"`
+	DefaultAction *AuthorizationAction `json:"defaultAction"`
 }
 
-// Rule defines the single authorization rule.
-// +notImplementedHide
-type Rule struct {
+// AuthorizationRule defines a single authorization rule.
+type AuthorizationRule struct {
+	// Name is a user-friendly name for the rule. It's just for display purposes.
+	// +optional
+	Name *string `json:"name,omitempty"`
+
 	// Action defines the action to be taken if the rule matches.
-	Action RuleActionType `json:"action"`
+	Action AuthorizationAction `json:"action"`
 
 	// Principal specifies the client identity of a request.
 	Principal Principal `json:"principal"`
-
-	// Permissions contains allowed HTTP methods.
-	// If empty, all methods are matching.
-	//
-	// +optional
-	// Permissions []string `json:"permissions,omitempty"`
 }
 
 // Principal specifies the client identity of a request.
-// +notImplementedHide
+// A client identity can be a client IP, a JWT claim, username from the Authorization header,
+// or any other identity that can be extracted from a custom header.
+// Currently, only the client IP is supported.
 type Principal struct {
-	// ClientCIDR is the IP CIDR range of the client.
+	// ClientCIDRs are the IP CIDR ranges of the client.
 	// Valid examples are "192.168.1.0/24" or "2001:db8::/64"
 	//
-	// By default, the client IP is inferred from the x-forwarder-for header and proxy protocol.
-	// You can use the `EnableProxyProtocol` and `ClientIPDetection` options in
+	// The client IP is inferred from the X-Forwarded-For header, a custom header,
+	// or the proxy protocol.
+	// You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
 	// the `ClientTrafficPolicy` to configure how the client IP is detected.
-	ClientCIDR []string `json:"clientCIDR,omitempty"`
+	// +kubebuilder:validation:MinItems=1
+	ClientCIDRs []CIDR `json:"clientCIDRs"`
+
+	// TODO: Zhaohuabing the MinItems=1 validation can be relaxed to allow empty list
+	// after other principal types are supported. However, at least one principal is required
 }
 
-// RuleActionType specifies the types of authorization rule action.
+// AuthorizationAction defines the action to be taken if a rule matches.
 // +kubebuilder:validation:Enum=Allow;Deny
-// +notImplementedHide
-type RuleActionType string
+type AuthorizationAction string
 
 const (
-	// Allow is the action to allow the request.
-	Allow RuleActionType = "Allow"
-	// Deny is the action to deny the request.
-	Deny RuleActionType = "Deny"
+	// AuthorizationActionAllow is the action to allow the request.
+	AuthorizationActionAllow AuthorizationAction = "Allow"
+	// AuthorizationActionDeny is the action to deny the request.
+	AuthorizationActionDeny AuthorizationAction = "Deny"
 )
diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go
index 09ac1bcbe..a7b258551 100644
--- a/api/v1alpha1/envoyproxy_types.go
+++ b/api/v1alpha1/envoyproxy_types.go
@@ -109,6 +109,8 @@ type EnvoyProxySpec struct {
 	//
 	// - envoy.filters.http.wasm
 	//
+	// - envoy.filters.http.rbac
+	//
 	// - envoy.filters.http.local_ratelimit
 	//
 	// - envoy.filters.http.ratelimit
@@ -151,7 +153,7 @@ type FilterPosition struct {
 }
 
 // EnvoyFilter defines the type of Envoy HTTP filter.
-// +kubebuilder:validation:Enum=envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.basic_authn;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.fault;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit;envoy.filters.http.wasm;envoy.filters.http.ext_proc
+// +kubebuilder:validation:Enum=envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.basic_authn;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.fault;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit;envoy.filters.http.wasm;envoy.filters.http.ext_proc;envoy.filters.http.rbac
 type EnvoyFilter string
 
 const (
@@ -184,6 +186,9 @@ const (
 	// EnvoyFilterRateLimit defines the Envoy HTTP rate limit filter.
 	EnvoyFilterRateLimit EnvoyFilter = "envoy.filters.http.ratelimit"
 
+	// EnvoyFilterRBAC defines the Envoy RBAC filter.
+	EnvoyFilterRBAC EnvoyFilter = "envoy.filters.http.rbac"
+
 	// EnvoyFilterRouter defines the Envoy HTTP router filter.
 	EnvoyFilterRouter EnvoyFilter = "envoy.filters.http.router"
 )
diff --git a/api/v1alpha1/ext_auth_types.go b/api/v1alpha1/ext_auth_types.go
index 6e455aa42..906f13c40 100644
--- a/api/v1alpha1/ext_auth_types.go
+++ b/api/v1alpha1/ext_auth_types.go
@@ -9,14 +9,14 @@ import (
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 )
 
+// ExtAuth defines the configuration for External Authorization.
+//
 // +kubebuilder:validation:XValidation:rule="(has(self.grpc) || has(self.http))",message="one of grpc or http must be specified"
 // +kubebuilder:validation:XValidation:rule="(has(self.grpc) && !has(self.http)) || (!has(self.grpc) && has(self.http))",message="only one of grpc or http can be specified"
-// +kubebuilder:validation:XValidation:rule="has(self.grpc) ? (!has(self.grpc.backendRef.group) || self.grpc.backendRef.group == \"\") : true", message="group is invalid, only the core API group (specified by omitting the group field or setting it to an empty string) is supported"
-// +kubebuilder:validation:XValidation:rule="has(self.grpc) ? (!has(self.grpc.backendRef.kind) || self.grpc.backendRef.kind == 'Service') : true", message="kind is invalid, only Service (specified by omitting the kind field or setting it to 'Service') is supported"
-// +kubebuilder:validation:XValidation:rule="has(self.http) ? (!has(self.http.backendRef.group) || self.http.backendRef.group == \"\") : true", message="group is invalid, only the core API group (specified by omitting the group field or setting it to an empty string) is supported"
-// +kubebuilder:validation:XValidation:rule="has(self.http) ? (!has(self.http.backendRef.kind) || self.http.backendRef.kind == 'Service') : true", message="kind is invalid, only Service (specified by omitting the kind field or setting it to 'Service') is supported"
-//
-// ExtAuth defines the configuration for External Authorization.
+// +kubebuilder:validation:XValidation:rule="has(self.grpc) ? (!has(self.grpc.backendRef) || !has(self.grpc.backendRef.group) || self.grpc.backendRef.group == \"\") : true", message="group is invalid, only the core API group (specified by omitting the group field or setting it to an empty string) is supported"
+// +kubebuilder:validation:XValidation:rule="has(self.grpc) ? (!has(self.grpc.backendRef) || !has(self.grpc.backendRef.kind) || self.grpc.backendRef.kind == 'Service') : true", message="kind is invalid, only Service (specified by omitting the kind field or setting it to 'Service') is supported"
+// +kubebuilder:validation:XValidation:rule="has(self.http) ? (!has(self.http.backendRef) || !has(self.http.backendRef.group) || self.http.backendRef.group == \"\") : true", message="group is invalid, only the core API group (specified by omitting the group field or setting it to an empty string) is supported"
+// +kubebuilder:validation:XValidation:rule="has(self.http) ? (!has(self.http.backendRef) || !has(self.http.backendRef.kind) || self.http.backendRef.kind == 'Service') : true", message="kind is invalid, only Service (specified by omitting the kind field or setting it to 'Service') is supported"
 type ExtAuth struct {
 	// GRPC defines the gRPC External Authorization service.
 	// Either GRPCService or HTTPService must be specified,
@@ -55,19 +55,42 @@ type ExtAuth struct {
 // GRPCExtAuthService defines the gRPC External Authorization service
 // The authorization request message is defined in
 // https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto
+// +kubebuilder:validation:XValidation:message="backendRef or backendRefs needs to be set",rule="has(self.backendRef) || self.backendRefs.size() > 0"
 type GRPCExtAuthService struct {
 	// BackendRef references a Kubernetes object that represents the
 	// backend server to which the authorization request will be sent.
 	// Only service Kind is supported for now.
-	BackendRef gwapiv1.BackendObjectReference `json:"backendRef"`
+	// Deprecated: Use BackendRefs instead.
+	BackendRef *gwapiv1.BackendObjectReference `json:"backendRef,omitempty"`
+
+	// BackendRefs references a Kubernetes object that represents the
+	// backend server to which the authorization request will be sent.
+	// Only service Kind is supported for now.
+	//
+	// +optional
+	// +kubebuilder:validation:MaxItems=1
+	// +kubebuilder:validation:XValidation:message="only support Service kind.",rule="self.all(f, f.kind == 'Service')"
+	BackendRefs []BackendRef `json:"backendRefs,omitempty"`
 }
 
 // HTTPExtAuthService defines the HTTP External Authorization service
+//
+// +kubebuilder:validation:XValidation:message="backendRef or backendRefs needs to be set",rule="has(self.backendRef) || self.backendRefs.size() > 0"
 type HTTPExtAuthService struct {
 	// BackendRef references a Kubernetes object that represents the
 	// backend server to which the authorization request will be sent.
 	// Only service Kind is supported for now.
-	BackendRef gwapiv1.BackendObjectReference `json:"backendRef"`
+	// Deprecated: Use BackendRefs instead.
+	BackendRef *gwapiv1.BackendObjectReference `json:"backendRef,omitempty"`
+
+	// BackendRefs references a Kubernetes object that represents the
+	// backend server to which the authorization request will be sent.
+	// Only service Kind is supported for now.
+	//
+	// +optional
+	// +kubebuilder:validation:MaxItems=1
+	// +kubebuilder:validation:XValidation:message="only support Service kind.",rule="self.all(f, f.kind == 'Service')"
+	BackendRefs []BackendRef `json:"backendRefs,omitempty"`
 
 	// Path is the path of the HTTP External Authorization service.
 	// If path is specified, the authorization request will be sent to that path,
diff --git a/api/v1alpha1/share_types_helper.go b/api/v1alpha1/share_types_helper.go
new file mode 100644
index 000000000..ab0555356
--- /dev/null
+++ b/api/v1alpha1/share_types_helper.go
@@ -0,0 +1,18 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package v1alpha1
+
+import gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+
+func ToBackendObjectReference(ref BackendRef) *gwapiv1.BackendObjectReference {
+	return &gwapiv1.BackendObjectReference{
+		Group:     ref.Group,
+		Kind:      ref.Kind,
+		Namespace: ref.Namespace,
+		Name:      ref.Name,
+		Port:      ref.Port,
+	}
+}
diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go
index e07c8ec15..7446ccf36 100644
--- a/api/v1alpha1/shared_types.go
+++ b/api/v1alpha1/shared_types.go
@@ -446,3 +446,8 @@ type BackendRef struct {
 	// Only service Kind is supported for now.
 	gwapiv1.BackendObjectReference `json:",inline"`
 }
+
+// CIDR defines a CIDR Address range.
+// A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64".
+// +kubebuilder:validation:Pattern=`((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+))`
+type CIDR string
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index c3163b73a..a8a75b949 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -156,14 +156,14 @@ func (in *Authorization) DeepCopyInto(out *Authorization) {
 	*out = *in
 	if in.Rules != nil {
 		in, out := &in.Rules, &out.Rules
-		*out = make([]Rule, len(*in))
+		*out = make([]AuthorizationRule, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
 	if in.DefaultAction != nil {
 		in, out := &in.DefaultAction, &out.DefaultAction
-		*out = new(RuleActionType)
+		*out = new(AuthorizationAction)
 		**out = **in
 	}
 }
@@ -178,6 +178,27 @@ func (in *Authorization) DeepCopy() *Authorization {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AuthorizationRule) DeepCopyInto(out *AuthorizationRule) {
+	*out = *in
+	if in.Name != nil {
+		in, out := &in.Name, &out.Name
+		*out = new(string)
+		**out = **in
+	}
+	in.Principal.DeepCopyInto(&out.Principal)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationRule.
+func (in *AuthorizationRule) DeepCopy() *AuthorizationRule {
+	if in == nil {
+		return nil
+	}
+	out := new(AuthorizationRule)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *BackOffPolicy) DeepCopyInto(out *BackOffPolicy) {
 	*out = *in
@@ -2164,7 +2185,18 @@ func (in *FilterPosition) DeepCopy() *FilterPosition {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *GRPCExtAuthService) DeepCopyInto(out *GRPCExtAuthService) {
 	*out = *in
-	in.BackendRef.DeepCopyInto(&out.BackendRef)
+	if in.BackendRef != nil {
+		in, out := &in.BackendRef, &out.BackendRef
+		*out = new(apisv1.BackendObjectReference)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.BackendRefs != nil {
+		in, out := &in.BackendRefs, &out.BackendRefs
+		*out = make([]BackendRef, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCExtAuthService.
@@ -2397,7 +2429,18 @@ func (in *HTTPClientTimeout) DeepCopy() *HTTPClientTimeout {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *HTTPExtAuthService) DeepCopyInto(out *HTTPExtAuthService) {
 	*out = *in
-	in.BackendRef.DeepCopyInto(&out.BackendRef)
+	if in.BackendRef != nil {
+		in, out := &in.BackendRef, &out.BackendRef
+		*out = new(apisv1.BackendObjectReference)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.BackendRefs != nil {
+		in, out := &in.BackendRefs, &out.BackendRefs
+		*out = make([]BackendRef, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 	if in.Path != nil {
 		in, out := &in.Path, &out.Path
 		*out = new(string)
@@ -3380,9 +3423,9 @@ func (in *PerRetryPolicy) DeepCopy() *PerRetryPolicy {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Principal) DeepCopyInto(out *Principal) {
 	*out = *in
-	if in.ClientCIDR != nil {
-		in, out := &in.ClientCIDR, &out.ClientCIDR
-		*out = make([]string, len(*in))
+	if in.ClientCIDRs != nil {
+		in, out := &in.ClientCIDRs, &out.ClientCIDRs
+		*out = make([]CIDR, len(*in))
 		copy(*out, *in)
 	}
 }
@@ -4106,22 +4149,6 @@ func (in *RetryOn) DeepCopy() *RetryOn {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *Rule) DeepCopyInto(out *Rule) {
-	*out = *in
-	in.Principal.DeepCopyInto(&out.Principal)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule.
-func (in *Rule) DeepCopy() *Rule {
-	if in == nil {
-		return nil
-	}
-	out := new(Rule)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SecurityPolicy) DeepCopyInto(out *SecurityPolicy) {
 	*out = *in
diff --git a/charts/gateway-helm/README.md b/charts/gateway-helm/README.md
index afc7297dd..3caff48fd 100644
--- a/charts/gateway-helm/README.md
+++ b/charts/gateway-helm/README.md
@@ -91,8 +91,10 @@ To uninstall the chart:
 | deployment.ports[2].targetPort | int | `19001` |  |
 | deployment.replicas | int | `1` |  |
 | global.images.envoyGateway.image | string | `nil` |  |
-| global.images.envoyGateway.imagePullSecrets | list | `[]` |  |
 | global.images.envoyGateway.pullPolicy | string | `nil` |  |
+| global.images.envoyGateway.pullSecrets | list | `[]` |  |
 | global.images.ratelimit.image | string | `"docker.io/envoyproxy/ratelimit:master"` |  |
+| global.images.ratelimit.pullPolicy | string | `"IfNotPresent"` |  |
+| global.images.ratelimit.pullSecrets | list | `[]` |  |
 | kubernetesClusterDomain | string | `"cluster.local"` |  |
 
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
index 8ba48c431..b3896406b 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
@@ -254,6 +254,9 @@ spec:
                   - envoy.filters.http.wasm
 
 
+                  - envoy.filters.http.rbac
+
+
                   - envoy.filters.http.local_ratelimit
 
 
@@ -280,6 +283,7 @@ spec:
                       - envoy.filters.http.ratelimit
                       - envoy.filters.http.wasm
                       - envoy.filters.http.ext_proc
+                      - envoy.filters.http.rbac
                       type: string
                     before:
                       description: |-
@@ -296,6 +300,7 @@ spec:
                       - envoy.filters.http.ratelimit
                       - envoy.filters.http.wasm
                       - envoy.filters.http.ext_proc
+                      - envoy.filters.http.rbac
                       type: string
                     name:
                       description: Name of the filter.
@@ -310,6 +315,7 @@ spec:
                       - envoy.filters.http.ratelimit
                       - envoy.filters.http.wasm
                       - envoy.filters.http.ext_proc
+                      - envoy.filters.http.rbac
                       type: string
                   required:
                   - name
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
index 919d272cf..a7cf21754 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
@@ -70,7 +70,8 @@ spec:
                       For example, if there are two rules: the first rule allows the request
                       and the second rule denies it, when a request matches both rules, it will be allowed.
                     items:
-                      description: Rule defines the single authorization rule.
+                      description: AuthorizationRule defines a single authorization
+                        rule.
                       properties:
                         action:
                           description: Action defines the action to be taken if the
@@ -79,22 +80,34 @@ spec:
                           - Allow
                           - Deny
                           type: string
+                        name:
+                          description: Name is a user-friendly name for the rule.
+                            It's just for display purposes.
+                          type: string
                         principal:
                           description: Principal specifies the client identity of
                             a request.
                           properties:
-                            clientCIDR:
+                            clientCIDRs:
                               description: |-
-                                ClientCIDR is the IP CIDR range of the client.
+                                ClientCIDRs are the IP CIDR ranges of the client.
                                 Valid examples are "192.168.1.0/24" or "2001:db8::/64"
 
 
-                                By default, the client IP is inferred from the x-forwarder-for header and proxy protocol.
-                                You can use the `EnableProxyProtocol` and `ClientIPDetection` options in
+                                The client IP is inferred from the X-Forwarded-For header, a custom header,
+                                or the proxy protocol.
+                                You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
                                 the `ClientTrafficPolicy` to configure how the client IP is detected.
                               items:
+                                description: |-
+                                  CIDR defines a CIDR Address range.
+                                  A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64".
+                                pattern: ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+))
                                 type: string
+                              minItems: 1
                               type: array
+                          required:
+                          - clientCIDRs
                           type: object
                       required:
                       - action
@@ -247,6 +260,7 @@ spec:
                           BackendRef references a Kubernetes object that represents the
                           backend server to which the authorization request will be sent.
                           Only service Kind is supported for now.
+                          Deprecated: Use BackendRefs instead.
                         properties:
                           group:
                             default: ""
@@ -321,9 +335,97 @@ spec:
                         - message: Must have port for Service reference
                           rule: '(size(self.group) == 0 && self.kind == ''Service'')
                             ? has(self.port) : true'
-                    required:
-                    - backendRef
+                      backendRefs:
+                        description: |-
+                          BackendRefs references a Kubernetes object that represents the
+                          backend server to which the authorization request will be sent.
+                          Only service Kind is supported for now.
+                        items:
+                          description: BackendRef defines how an ObjectReference that
+                            is specific to BackendRef.
+                          properties:
+                            group:
+                              default: ""
+                              description: |-
+                                Group is the group of the referent. For example, "gateway.networking.k8s.io".
+                                When unspecified or empty string, core API group is inferred.
+                              maxLength: 253
+                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                              type: string
+                            kind:
+                              default: Service
+                              description: |-
+                                Kind is the Kubernetes resource kind of the referent. For example
+                                "Service".
+
+
+                                Defaults to "Service" when not specified.
+
+
+                                ExternalName services can refer to CNAME DNS records that may live
+                                outside of the cluster and as such are difficult to reason about in
+                                terms of conformance. They also may not be safe to forward to (see
+                                CVE-2021-25740 for more information). Implementations SHOULD NOT
+                                support ExternalName Services.
+
+
+                                Support: Core (Services with a type other than ExternalName)
+
+
+                                Support: Implementation-specific (Services with type ExternalName)
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
+                              type: string
+                            name:
+                              description: Name is the name of the referent.
+                              maxLength: 253
+                              minLength: 1
+                              type: string
+                            namespace:
+                              description: |-
+                                Namespace is the namespace of the backend. When unspecified, the local
+                                namespace is inferred.
+
+
+                                Note that when a namespace different than the local namespace is specified,
+                                a ReferenceGrant object is required in the referent namespace to allow that
+                                namespace's owner to accept the reference. See the ReferenceGrant
+                                documentation for details.
+
+
+                                Support: Core
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                              type: string
+                            port:
+                              description: |-
+                                Port specifies the destination port number to use for this resource.
+                                Port is required when the referent is a Kubernetes Service. In this
+                                case, the port number is the service port number, not the target port.
+                                For other resources, destination port might be derived from the referent
+                                resource or this field.
+                              format: int32
+                              maximum: 65535
+                              minimum: 1
+                              type: integer
+                          required:
+                          - name
+                          type: object
+                          x-kubernetes-validations:
+                          - message: Must have port for Service reference
+                            rule: '(size(self.group) == 0 && self.kind == ''Service'')
+                              ? has(self.port) : true'
+                        maxItems: 1
+                        type: array
+                        x-kubernetes-validations:
+                        - message: only support Service kind.
+                          rule: self.all(f, f.kind == 'Service')
                     type: object
+                    x-kubernetes-validations:
+                    - message: backendRef or backendRefs needs to be set
+                      rule: has(self.backendRef) || self.backendRefs.size() > 0
                   headersToExtAuth:
                     description: |-
                       HeadersToExtAuth defines the client request headers that will be included
@@ -350,6 +452,7 @@ spec:
                           BackendRef references a Kubernetes object that represents the
                           backend server to which the authorization request will be sent.
                           Only service Kind is supported for now.
+                          Deprecated: Use BackendRefs instead.
                         properties:
                           group:
                             default: ""
@@ -424,6 +527,93 @@ spec:
                         - message: Must have port for Service reference
                           rule: '(size(self.group) == 0 && self.kind == ''Service'')
                             ? has(self.port) : true'
+                      backendRefs:
+                        description: |-
+                          BackendRefs references a Kubernetes object that represents the
+                          backend server to which the authorization request will be sent.
+                          Only service Kind is supported for now.
+                        items:
+                          description: BackendRef defines how an ObjectReference that
+                            is specific to BackendRef.
+                          properties:
+                            group:
+                              default: ""
+                              description: |-
+                                Group is the group of the referent. For example, "gateway.networking.k8s.io".
+                                When unspecified or empty string, core API group is inferred.
+                              maxLength: 253
+                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                              type: string
+                            kind:
+                              default: Service
+                              description: |-
+                                Kind is the Kubernetes resource kind of the referent. For example
+                                "Service".
+
+
+                                Defaults to "Service" when not specified.
+
+
+                                ExternalName services can refer to CNAME DNS records that may live
+                                outside of the cluster and as such are difficult to reason about in
+                                terms of conformance. They also may not be safe to forward to (see
+                                CVE-2021-25740 for more information). Implementations SHOULD NOT
+                                support ExternalName Services.
+
+
+                                Support: Core (Services with a type other than ExternalName)
+
+
+                                Support: Implementation-specific (Services with type ExternalName)
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
+                              type: string
+                            name:
+                              description: Name is the name of the referent.
+                              maxLength: 253
+                              minLength: 1
+                              type: string
+                            namespace:
+                              description: |-
+                                Namespace is the namespace of the backend. When unspecified, the local
+                                namespace is inferred.
+
+
+                                Note that when a namespace different than the local namespace is specified,
+                                a ReferenceGrant object is required in the referent namespace to allow that
+                                namespace's owner to accept the reference. See the ReferenceGrant
+                                documentation for details.
+
+
+                                Support: Core
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                              type: string
+                            port:
+                              description: |-
+                                Port specifies the destination port number to use for this resource.
+                                Port is required when the referent is a Kubernetes Service. In this
+                                case, the port number is the service port number, not the target port.
+                                For other resources, destination port might be derived from the referent
+                                resource or this field.
+                              format: int32
+                              maximum: 65535
+                              minimum: 1
+                              type: integer
+                          required:
+                          - name
+                          type: object
+                          x-kubernetes-validations:
+                          - message: Must have port for Service reference
+                            rule: '(size(self.group) == 0 && self.kind == ''Service'')
+                              ? has(self.port) : true'
+                        maxItems: 1
+                        type: array
+                        x-kubernetes-validations:
+                        - message: only support Service kind.
+                          rule: self.all(f, f.kind == 'Service')
                       headersToBackend:
                         description: |-
                           HeadersToBackend are the authorization response headers that will be added
@@ -440,9 +630,10 @@ spec:
                           If path is specified, the authorization request will be sent to that path,
                           or else the authorization request will be sent to the root path.
                         type: string
-                    required:
-                    - backendRef
                     type: object
+                    x-kubernetes-validations:
+                    - message: backendRef or backendRefs needs to be set
+                      rule: has(self.backendRef) || self.backendRefs.size() > 0
                 type: object
                 x-kubernetes-validations:
                 - message: one of grpc or http must be specified
@@ -453,21 +644,21 @@ spec:
                 - message: group is invalid, only the core API group (specified by
                     omitting the group field or setting it to an empty string) is
                     supported
-                  rule: 'has(self.grpc) ? (!has(self.grpc.backendRef.group) || self.grpc.backendRef.group
-                    == "") : true'
+                  rule: 'has(self.grpc) ? (!has(self.grpc.backendRef) || !has(self.grpc.backendRef.group)
+                    || self.grpc.backendRef.group == "") : true'
                 - message: kind is invalid, only Service (specified by omitting the
                     kind field or setting it to 'Service') is supported
-                  rule: 'has(self.grpc) ? (!has(self.grpc.backendRef.kind) || self.grpc.backendRef.kind
-                    == ''Service'') : true'
+                  rule: 'has(self.grpc) ? (!has(self.grpc.backendRef) || !has(self.grpc.backendRef.kind)
+                    || self.grpc.backendRef.kind == ''Service'') : true'
                 - message: group is invalid, only the core API group (specified by
                     omitting the group field or setting it to an empty string) is
                     supported
-                  rule: 'has(self.http) ? (!has(self.http.backendRef.group) || self.http.backendRef.group
-                    == "") : true'
+                  rule: 'has(self.http) ? (!has(self.http.backendRef) || !has(self.http.backendRef.group)
+                    || self.http.backendRef.group == "") : true'
                 - message: kind is invalid, only Service (specified by omitting the
                     kind field or setting it to 'Service') is supported
-                  rule: 'has(self.http) ? (!has(self.http.backendRef.kind) || self.http.backendRef.kind
-                    == ''Service'') : true'
+                  rule: 'has(self.http) ? (!has(self.http.backendRef) || !has(self.http.backendRef.kind)
+                    || self.http.backendRef.kind == ''Service'') : true'
               jwt:
                 description: JWT defines the configuration for JSON Web Token (JWT)
                   authentication.
diff --git a/charts/gateway-helm/templates/_helpers.tpl b/charts/gateway-helm/templates/_helpers.tpl
index 0802657f8..d9aefc2a8 100755
--- a/charts/gateway-helm/templates/_helpers.tpl
+++ b/charts/gateway-helm/templates/_helpers.tpl
@@ -88,9 +88,9 @@ Pull secrets for the Envoy Gateway image.
 {{- if .Values.deployment.envoyGateway.imagePullSecrets -}}
 imagePullSecrets:
 {{ toYaml .Values.deployment.envoyGateway.imagePullSecrets }}
-{{- else if .Values.global.images.envoyGateway.imagePullSecrets -}}
+{{- else if .Values.global.images.envoyGateway.pullSecrets -}}
 imagePullSecrets:
-{{ toYaml .Values.global.images.envoyGateway.imagePullSecrets }}
+{{ toYaml .Values.global.images.envoyGateway.pullSecrets }}
 {{- else -}}
 imagePullSecrets: []
 {{- end }}
@@ -110,6 +110,22 @@ provider:
         {{- else }}
         image: "docker.io/envoyproxy/ratelimit:master"
         {{- end }}
+      {{- with .Values.global.images.ratelimit.pullSecrets }}
+      pod:
+        imagePullSecrets:
+        {{- toYaml . | nindent 10 }}
+      {{- end }}
+      {{- with .Values.global.images.ratelimit.pullPolicy }}
+      patch:
+        type: StrategicMerge
+        value:
+          spec:
+            template:
+              spec:
+                containers:
+                - name: envoy-ratelimit
+                  imagePullPolicy: {{ . }}
+      {{- end }}
     shutdownManager:
       image: {{ include "eg.image" . }}
 {{- end }}
diff --git a/charts/gateway-helm/values.tmpl.yaml b/charts/gateway-helm/values.tmpl.yaml
index 0513ff811..21fd1e02c 100644
--- a/charts/gateway-helm/values.tmpl.yaml
+++ b/charts/gateway-helm/values.tmpl.yaml
@@ -9,17 +9,15 @@ global:
       # Default behavior: latest images will be Always else IfNotPresent.
       pullPolicy: ${GatewayImagePullPolicy}
       # List of secrets in the same namespace of the component that can be used to pull images from private repositories.
-      imagePullSecrets: []
+      pullSecrets: []
     ratelimit:
       # This is the full image name including the hub, repo, and tag.
       image: "docker.io/envoyproxy/ratelimit:master"
-      # TODO: zhaohuabing add support for imagePullSecrets for ratelimit
       # Specify image pull policy if default behavior isn't desired.
       # Default behavior: latest images will be Always else IfNotPresent.
-      # pullPolicy: IfNotPresent
+      pullPolicy: IfNotPresent
       # List of secrets in the same namespace of the component that can be used to pull images from private repositories.
-      # imagePullSecrets: []
-    # statsd: TODO: zhaohuabing add custom image support for statsd image
+      pullSecrets: []
 
 deployment:
   envoyGateway:
diff --git a/go.mod b/go.mod
index 11297031e..fdf1e3ca2 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
 	github.com/evanphx/json-patch/v5 v5.9.0
 	github.com/fatih/color v1.17.0
 	github.com/go-logfmt/logfmt v0.6.0
-	github.com/go-logr/logr v1.4.1
+	github.com/go-logr/logr v1.4.2
 	github.com/go-logr/zapr v1.3.0
 	github.com/gogo/protobuf v1.3.2
 	github.com/golang/protobuf v1.5.4
@@ -42,7 +42,7 @@ require (
 	google.golang.org/grpc v1.64.0
 	google.golang.org/protobuf v1.34.1
 	gopkg.in/yaml.v3 v3.0.1
-	helm.sh/helm/v3 v3.15.0
+	helm.sh/helm/v3 v3.15.1
 	k8s.io/api v0.30.1
 	k8s.io/apiextensions-apiserver v0.30.1
 	k8s.io/apimachinery v0.30.1
diff --git a/go.sum b/go.sum
index 38c63d561..9e7d55259 100644
--- a/go.sum
+++ b/go.sum
@@ -216,8 +216,8 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi
 github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
-github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
@@ -920,8 +920,8 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
 gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
-helm.sh/helm/v3 v3.15.0 h1:gcLxHeFp0Hfo7lYi6KIZ84ZyvlAnfFRSJ8lTL3zvG5U=
-helm.sh/helm/v3 v3.15.0/go.mod h1:fvfoRcB8UKRUV5jrIfOTaN/pG1TPhuqSb56fjYdTKXg=
+helm.sh/helm/v3 v3.15.1 h1:22ztacHz4gMqhXNqCQ9NAg6BFWoRUryNLvnkz6OVyw0=
+helm.sh/helm/v3 v3.15.1/go.mod h1:fvfoRcB8UKRUV5jrIfOTaN/pG1TPhuqSb56fjYdTKXg=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go
index 0fae4b170..2c661ec5e 100644
--- a/internal/gatewayapi/backendtrafficpolicy.go
+++ b/internal/gatewayapi/backendtrafficpolicy.go
@@ -10,7 +10,6 @@ import (
 	"fmt"
 	"math"
 	"math/big"
-	"net"
 	"sort"
 	"strings"
 	"time"
@@ -758,18 +757,12 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) {
 				distinct = true
 			}
 
-			ip, ipn, err := net.ParseCIDR(sourceCIDR)
+			cidrMatch, err := parseCIDR(sourceCIDR)
 			if err != nil {
-				return nil, fmt.Errorf("unable to translate rateLimit")
-			}
-
-			mask, _ := ipn.Mask.Size()
-			irRule.CIDRMatch = &ir.CIDRMatch{
-				CIDR:     ipn.String(),
-				IPv6:     ip.To4() == nil,
-				MaskLen:  mask,
-				Distinct: distinct,
+				return nil, fmt.Errorf("unable to translate rateLimit: %w", err)
 			}
+			cidrMatch.Distinct = distinct
+			irRule.CIDRMatch = cidrMatch
 		}
 	}
 	return irRule, nil
diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go
index 2946591d4..c763d8068 100644
--- a/internal/gatewayapi/helpers.go
+++ b/internal/gatewayapi/helpers.go
@@ -8,6 +8,7 @@ package gatewayapi
 import (
 	"errors"
 	"fmt"
+	"net"
 	"strings"
 
 	v1 "k8s.io/api/core/v1"
@@ -466,3 +467,18 @@ func listenersWithSameHTTPPort(xdsIR *ir.Xds, listener *ir.HTTPListener) []strin
 	}
 	return res
 }
+
+func parseCIDR(cidr string) (*ir.CIDRMatch, error) {
+	ip, ipn, err := net.ParseCIDR(cidr)
+	if err != nil {
+		return nil, err
+	}
+
+	mask, _ := ipn.Mask.Size()
+	return &ir.CIDRMatch{
+		CIDR:    ipn.String(),
+		IP:      ip.String(),
+		MaskLen: uint32(mask),
+		IsIPv6:  ip.To4() == nil,
+	}, nil
+}
diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go
index 8cdca113b..8c459a8d1 100644
--- a/internal/gatewayapi/securitypolicy.go
+++ b/internal/gatewayapi/securitypolicy.go
@@ -332,12 +332,13 @@ func (t *Translator) translateSecurityPolicyForRoute(
 ) error {
 	// Build IR
 	var (
-		cors      *ir.CORS
-		jwt       *ir.JWT
-		oidc      *ir.OIDC
-		basicAuth *ir.BasicAuth
-		extAuth   *ir.ExtAuth
-		err, errs error
+		cors          *ir.CORS
+		jwt           *ir.JWT
+		oidc          *ir.OIDC
+		basicAuth     *ir.BasicAuth
+		extAuth       *ir.ExtAuth
+		authorization *ir.Authorization
+		err, errs     error
 	)
 
 	if policy.Spec.CORS != nil {
@@ -375,6 +376,12 @@ func (t *Translator) translateSecurityPolicyForRoute(
 		}
 	}
 
+	if policy.Spec.Authorization != nil {
+		if authorization, err = t.buildAuthorization(policy); err != nil {
+			errs = errors.Join(errs, err)
+		}
+	}
+
 	// Apply IR to all relevant routes
 	// Note: there are multiple features in a security policy, even if some of them
 	// are invalid, we still want to apply the valid ones.
@@ -388,11 +395,12 @@ func (t *Translator) translateSecurityPolicyForRoute(
 					// This security policy matches the current route.
 					// It should only be accepted if it doesn't match any other route
 					r.Security = &ir.SecurityFeatures{
-						CORS:      cors,
-						JWT:       jwt,
-						OIDC:      oidc,
-						BasicAuth: basicAuth,
-						ExtAuth:   extAuth,
+						CORS:          cors,
+						JWT:           jwt,
+						OIDC:          oidc,
+						BasicAuth:     basicAuth,
+						ExtAuth:       extAuth,
+						Authorization: authorization,
 					}
 				}
 			}
@@ -407,12 +415,13 @@ func (t *Translator) translateSecurityPolicyForGateway(
 ) error {
 	// Build IR
 	var (
-		cors      *ir.CORS
-		jwt       *ir.JWT
-		oidc      *ir.OIDC
-		basicAuth *ir.BasicAuth
-		extAuth   *ir.ExtAuth
-		err, errs error
+		cors          *ir.CORS
+		jwt           *ir.JWT
+		oidc          *ir.OIDC
+		basicAuth     *ir.BasicAuth
+		extAuth       *ir.ExtAuth
+		authorization *ir.Authorization
+		err, errs     error
 	)
 
 	if policy.Spec.CORS != nil {
@@ -450,6 +459,11 @@ func (t *Translator) translateSecurityPolicyForGateway(
 		}
 	}
 
+	if policy.Spec.Authorization != nil {
+		if authorization, err = t.buildAuthorization(policy); err != nil {
+			errs = errors.Join(errs, err)
+		}
+	}
 	// Apply IR to all the routes within the specific Gateway that originated
 	// from the gateway to which this security policy was attached.
 	// If the feature is already set, then skip it, since it must have be
@@ -477,11 +491,12 @@ func (t *Translator) translateSecurityPolicyForGateway(
 			}
 
 			r.Security = &ir.SecurityFeatures{
-				CORS:      cors,
-				JWT:       jwt,
-				OIDC:      oidc,
-				BasicAuth: basicAuth,
-				ExtAuth:   extAuth,
+				CORS:          cors,
+				JWT:           jwt,
+				OIDC:          oidc,
+				BasicAuth:     basicAuth,
+				ExtAuth:       extAuth,
+				Authorization: authorization,
 			}
 		}
 	}
@@ -758,10 +773,7 @@ func (t *Translator) buildBasicAuth(
 	}, nil
 }
 
-func (t *Translator) buildExtAuth(
-	policy *egv1a1.SecurityPolicy,
-	resources *Resources,
-) (*ir.ExtAuth, error) {
+func (t *Translator) buildExtAuth(policy *egv1a1.SecurityPolicy, resources *Resources) (*ir.ExtAuth, error) {
 	var (
 		http       = policy.Spec.ExtAuth.HTTP
 		grpc       = policy.Spec.ExtAuth.GRPC
@@ -775,16 +787,24 @@ func (t *Translator) buildExtAuth(
 	switch {
 	// These are sanity checks, they should never happen because the API server
 	// should have caught them
-	case http == nil && grpc == nil:
-		return nil, errors.New("one of grpc or http must be specified")
 	case http != nil && grpc != nil:
 		return nil, errors.New("only one of grpc or http can be specified")
 	case http != nil:
-		backendRef = &http.BackendRef
+		backendRef = http.BackendRef
+		if len(http.BackendRefs) != 0 {
+			backendRef = egv1a1.ToBackendObjectReference(http.BackendRefs[0])
+		}
 		protocol = ir.HTTP
 	case grpc != nil:
-		backendRef = &grpc.BackendRef
+		backendRef = grpc.BackendRef
+		if len(grpc.BackendRefs) != 0 {
+			backendRef = egv1a1.ToBackendObjectReference(grpc.BackendRefs[0])
+		}
 		protocol = ir.GRPC
+	// These are sanity checks, they should never happen because the API server
+	// should have caught them
+	default: // http == nil && grpc == nil:
+		return nil, errors.New("one of grpc or http must be specified")
 	}
 
 	if err = t.validateExtServiceBackendReference(
@@ -794,8 +814,7 @@ func (t *Translator) buildExtAuth(
 		resources); err != nil {
 		return nil, err
 	}
-	authority = fmt.Sprintf(
-		"%s.%s:%d",
+	authority = fmt.Sprintf("%s.%s:%d",
 		backendRef.Name,
 		NamespaceDerefOr(backendRef.Namespace, policy.Namespace),
 		*backendRef.Port)
@@ -854,3 +873,51 @@ func irConfigName(policy *egv1a1.SecurityPolicy) string {
 		strings.ToLower(KindSecurityPolicy),
 		utils.NamespacedName(policy).String())
 }
+
+func (t *Translator) buildAuthorization(policy *egv1a1.SecurityPolicy) (*ir.Authorization, error) {
+	var (
+		authorization = policy.Spec.Authorization
+		irAuth        = &ir.Authorization{}
+		// The default action is Deny if not specified
+		defaultAction = egv1a1.AuthorizationActionDeny
+	)
+
+	if authorization.DefaultAction != nil {
+		defaultAction = *authorization.DefaultAction
+	}
+	irAuth.DefaultAction = defaultAction
+
+	for i, rule := range authorization.Rules {
+		principal := ir.Principal{}
+
+		for _, cidr := range rule.Principal.ClientCIDRs {
+			cidrMatch, err := parseCIDR(string(cidr))
+			if err != nil {
+				return nil, fmt.Errorf("unable to translate authorization rule: %w", err)
+			}
+
+			principal.ClientCIDRs = append(principal.ClientCIDRs, cidrMatch)
+		}
+
+		var name string
+		if rule.Name != nil && *rule.Name != "" {
+			name = *rule.Name
+		} else {
+			name = defaultAuthorizationRuleName(policy, i)
+		}
+		irAuth.Rules = append(irAuth.Rules, &ir.AuthorizationRule{
+			Name:      name,
+			Action:    rule.Action,
+			Principal: principal,
+		})
+	}
+
+	return irAuth, nil
+}
+
+func defaultAuthorizationRuleName(policy *egv1a1.SecurityPolicy, index int) string {
+	return fmt.Sprintf(
+		"%s/authorization/rule/%s",
+		irConfigName(policy),
+		strconv.Itoa(index))
+}
diff --git a/internal/infrastructure/kubernetes/infra_resource.go b/internal/infrastructure/kubernetes/infra_resource.go
index 91b880d5d..9dcb9cb33 100644
--- a/internal/infrastructure/kubernetes/infra_resource.go
+++ b/internal/infrastructure/kubernetes/infra_resource.go
@@ -7,29 +7,67 @@ package kubernetes
 
 import (
 	"context"
+	"time"
 
 	appsv1 "k8s.io/api/apps/v1"
 	autoscalingv2 "k8s.io/api/autoscaling/v2"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/envoyproxy/gateway/internal/metrics"
 )
 
 // createOrUpdateServiceAccount creates a ServiceAccount in the kube api server based on the
 // provided ResourceRender, if it doesn't exist and updates it if it does.
-func (i *Infra) createOrUpdateServiceAccount(ctx context.Context, r ResourceRender) error {
-	sa, err := r.ServiceAccount()
-	if err != nil {
+func (i *Infra) createOrUpdateServiceAccount(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		sa        *corev1.ServiceAccount
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("ServiceAccount"),
+			nameLabel.Value(r.Name()),
+			namespaceLabel.Value(i.Namespace),
+		}
+	)
+
+	resourceApplyTotal.With(labels...).Increment()
+
+	if sa, err = r.ServiceAccount(); err != nil {
+		resourceApplyFailed.With(labels...).Increment()
+
 		return err
 	}
 
+	defer func() {
+		if err == nil {
+			resourceApplyDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceApplySuccess.With(labels...).Increment()
+		} else {
+			resourceApplyFailed.With(labels...).Increment()
+		}
+	}()
+
 	return i.Client.ServerSideApply(ctx, sa)
 }
 
 // createOrUpdateConfigMap creates a ConfigMap in the Kube api server based on the provided
 // ResourceRender, if it doesn't exist and updates it if it does.
-func (i *Infra) createOrUpdateConfigMap(ctx context.Context, r ResourceRender) error {
-	cm, err := r.ConfigMap()
-	if err != nil {
+func (i *Infra) createOrUpdateConfigMap(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		cm        *corev1.ConfigMap
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("ConfigMap"),
+			nameLabel.Value(r.Name()),
+			namespaceLabel.Value(i.Namespace),
+		}
+	)
+
+	resourceApplyTotal.With(labels...).Increment()
+
+	if cm, err = r.ConfigMap(); err != nil {
+		resourceApplyFailed.With(labels...).Increment()
+
 		return err
 	}
 
@@ -37,142 +75,359 @@ func (i *Infra) createOrUpdateConfigMap(ctx context.Context, r ResourceRender) e
 		return nil
 	}
 
+	defer func() {
+		if err == nil {
+			resourceApplyDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceApplySuccess.With(labels...).Increment()
+		} else {
+			resourceApplyFailed.With(labels...).Increment()
+		}
+	}()
+
 	return i.Client.ServerSideApply(ctx, cm)
 }
 
 // createOrUpdateDeployment creates a Deployment in the kube api server based on the provided
 // ResourceRender, if it doesn't exist and updates it if it does.
-func (i *Infra) createOrUpdateDeployment(ctx context.Context, r ResourceRender) error {
-	deployment, err := r.Deployment()
-	if err != nil {
+func (i *Infra) createOrUpdateDeployment(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		deployment *appsv1.Deployment
+		startTime  = time.Now()
+		labels     = []metrics.LabelValue{
+			kindLabel.Value("Deployment"),
+			nameLabel.Value(r.Name()),
+			namespaceLabel.Value(i.Namespace),
+		}
+	)
+
+	resourceApplyTotal.With(labels...).Increment()
+
+	if deployment, err = r.Deployment(); err != nil {
+		resourceApplyFailed.With(labels...).Increment()
+
 		return err
 	}
 
 	// delete the deployment and return early
 	// this handles the case where a daemonset has been
-	// congured
+	// configured.
 	if deployment == nil {
 		return i.deleteDeployment(ctx, r)
 	}
 
+	defer func() {
+		if err == nil {
+			resourceApplyDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceApplySuccess.With(labels...).Increment()
+		} else {
+			resourceApplyFailed.With(labels...).Increment()
+		}
+	}()
+
 	return i.Client.ServerSideApply(ctx, deployment)
 }
 
 // createOrUpdateDaemonSet creates a DaemonSet in the kube api server based on the provided
 // ResourceRender, if it doesn't exist and updates it if it does.
-func (i *Infra) createOrUpdateDaemonSet(ctx context.Context, r ResourceRender) error {
-	daemonSet, err := r.DaemonSet()
-	if err != nil {
+func (i *Infra) createOrUpdateDaemonSet(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		daemonSet *appsv1.DaemonSet
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("DaemonSet"),
+			nameLabel.Value(r.Name()),
+			namespaceLabel.Value(i.Namespace),
+		}
+	)
+
+	resourceApplyTotal.With(labels...).Increment()
+
+	if daemonSet, err = r.DaemonSet(); err != nil {
+		resourceApplyFailed.With(labels...).Increment()
+
 		return err
 	}
 
 	// delete the daemonset and return early
 	// this handles the case where a deployment has been
-	// congured
+	// configured.
 	if daemonSet == nil {
 		return i.deleteDaemonSet(ctx, r)
 	}
 
+	defer func() {
+		if err == nil {
+			resourceApplyDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceApplySuccess.With(labels...).Increment()
+		} else {
+			resourceApplyFailed.With(labels...).Increment()
+		}
+	}()
+
 	return i.Client.ServerSideApply(ctx, daemonSet)
 }
 
 // createOrUpdateHPA creates HorizontalPodAutoscaler object in the kube api server based on
 // the provided ResourceRender, if it doesn't exist and updates it if it does,
 // and delete hpa if not set.
-func (i *Infra) createOrUpdateHPA(ctx context.Context, r ResourceRender) error {
-	hpa, err := r.HorizontalPodAutoscaler()
-	if err != nil {
+func (i *Infra) createOrUpdateHPA(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		hpa       *autoscalingv2.HorizontalPodAutoscaler
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("HPA"),
+			nameLabel.Value(r.Name()),
+			namespaceLabel.Value(i.Namespace),
+		}
+	)
+
+	resourceApplyTotal.With(labels...).Increment()
+
+	if hpa, err = r.HorizontalPodAutoscaler(); err != nil {
+		resourceApplyFailed.With(labels...).Increment()
+
 		return err
 	}
 
 	// when HorizontalPodAutoscaler is not set,
-	// then delete the object in the kube api server if any.
+	// then delete the object in the kube api server if got any.
 	if hpa == nil {
 		return i.deleteHPA(ctx, r)
 	}
 
+	defer func() {
+		if err == nil {
+			resourceApplyDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceApplySuccess.With(labels...).Increment()
+		} else {
+			resourceApplyFailed.With(labels...).Increment()
+		}
+	}()
+
 	return i.Client.ServerSideApply(ctx, hpa)
 }
 
 // createOrUpdateRateLimitService creates a Service in the kube api server based on the provided ResourceRender,
 // if it doesn't exist or updates it if it does.
-func (i *Infra) createOrUpdateService(ctx context.Context, r ResourceRender) error {
-	svc, err := r.Service()
-	if err != nil {
+func (i *Infra) createOrUpdateService(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		svc       *corev1.Service
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("Service"),
+			nameLabel.Value(r.Name()),
+			namespaceLabel.Value(i.Namespace),
+		}
+	)
+
+	resourceApplyTotal.With(labels...).Increment()
+
+	if svc, err = r.Service(); err != nil {
+		resourceApplyFailed.With(labels...).Increment()
+
 		return err
 	}
 
+	defer func() {
+		if err == nil {
+			resourceApplyDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceApplySuccess.With(labels...).Increment()
+		} else {
+			resourceApplyFailed.With(labels...).Increment()
+		}
+	}()
+
 	return i.Client.ServerSideApply(ctx, svc)
 }
 
 // deleteServiceAccount deletes the ServiceAccount in the kube api server, if it exists.
-func (i *Infra) deleteServiceAccount(ctx context.Context, r ResourceRender) error {
-	sa := &corev1.ServiceAccount{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: i.Namespace,
-			Name:      r.Name(),
-		},
-	}
+func (i *Infra) deleteServiceAccount(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		name, ns = r.Name(), i.Namespace
+		sa       = &corev1.ServiceAccount{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: ns,
+				Name:      name,
+			},
+		}
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("ServiceAccount"),
+			nameLabel.Value(name),
+			namespaceLabel.Value(ns),
+		}
+	)
+
+	resourceDeleteTotal.With(labels...).Increment()
+
+	defer func() {
+		if err == nil {
+			resourceDeleteDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceDeleteSuccess.With(labels...).Increment()
+		} else {
+			resourceDeleteFailed.With(labels...).Increment()
+		}
+	}()
 
 	return i.Client.Delete(ctx, sa)
 }
 
 // deleteDeployment deletes the Envoy Deployment in the kube api server, if it exists.
-func (i *Infra) deleteDeployment(ctx context.Context, r ResourceRender) error {
-	deployment := &appsv1.Deployment{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: i.Namespace,
-			Name:      r.Name(),
-		},
-	}
+func (i *Infra) deleteDeployment(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		name, ns   = r.Name(), i.Namespace
+		deployment = &appsv1.Deployment{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: ns,
+				Name:      name,
+			},
+		}
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("Deployment"),
+			nameLabel.Value(name),
+			namespaceLabel.Value(ns),
+		}
+	)
+
+	resourceDeleteTotal.With(labels...).Increment()
+
+	defer func() {
+		if err == nil {
+			resourceDeleteDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceDeleteSuccess.With(labels...).Increment()
+		} else {
+			resourceDeleteFailed.With(labels...).Increment()
+		}
+	}()
 
 	return i.Client.Delete(ctx, deployment)
 }
 
 // deleteDaemonSet deletes the Envoy DaemonSet in the kube api server, if it exists.
-func (i *Infra) deleteDaemonSet(ctx context.Context, r ResourceRender) error {
-	daemonSet := &appsv1.DaemonSet{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: i.Namespace,
-			Name:      r.Name(),
-		},
-	}
+func (i *Infra) deleteDaemonSet(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		name, ns  = r.Name(), i.Namespace
+		daemonSet = &appsv1.DaemonSet{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: ns,
+				Name:      name,
+			},
+		}
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("DaemonSet"),
+			nameLabel.Value(name),
+			namespaceLabel.Value(ns),
+		}
+	)
+
+	resourceDeleteTotal.With(labels...).Increment()
+
+	defer func() {
+		if err == nil {
+			resourceDeleteDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceDeleteSuccess.With(labels...).Increment()
+		} else {
+			resourceDeleteFailed.With(labels...).Increment()
+		}
+	}()
 
 	return i.Client.Delete(ctx, daemonSet)
 }
 
 // deleteConfigMap deletes the ConfigMap in the kube api server, if it exists.
-func (i *Infra) deleteConfigMap(ctx context.Context, r ResourceRender) error {
-	cm := &corev1.ConfigMap{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: i.Namespace,
-			Name:      r.Name(),
-		},
-	}
+func (i *Infra) deleteConfigMap(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		name, ns = r.Name(), i.Namespace
+		cm       = &corev1.ConfigMap{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: ns,
+				Name:      name,
+			},
+		}
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("ConfigMap"),
+			nameLabel.Value(name),
+			namespaceLabel.Value(ns),
+		}
+	)
+
+	resourceDeleteTotal.With(labels...).Increment()
+
+	defer func() {
+		if err == nil {
+			resourceDeleteDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceDeleteSuccess.With(labels...).Increment()
+		} else {
+			resourceDeleteFailed.With(labels...).Increment()
+		}
+	}()
 
 	return i.Client.Delete(ctx, cm)
 }
 
 // deleteService deletes the Service in the kube api server, if it exists.
-func (i *Infra) deleteService(ctx context.Context, r ResourceRender) error {
-	svc := &corev1.Service{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: i.Namespace,
-			Name:      r.Name(),
-		},
-	}
+func (i *Infra) deleteService(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		name, ns = r.Name(), i.Namespace
+		svc      = &corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: ns,
+				Name:      name,
+			},
+		}
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("Service"),
+			nameLabel.Value(name),
+			namespaceLabel.Value(ns),
+		}
+	)
+
+	resourceDeleteTotal.With(labels...).Increment()
+
+	defer func() {
+		if err == nil {
+			resourceDeleteDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceDeleteSuccess.With(labels...).Increment()
+		} else {
+			resourceDeleteFailed.With(labels...).Increment()
+		}
+	}()
 
 	return i.Client.Delete(ctx, svc)
 }
 
 // deleteHpa deletes the Horizontal Pod Autoscaler associated to its renderer, if it exists.
-func (i *Infra) deleteHPA(ctx context.Context, r ResourceRender) error {
-	hpa := &autoscalingv2.HorizontalPodAutoscaler{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: i.Namespace,
-			Name:      r.Name(),
-		},
-	}
+func (i *Infra) deleteHPA(ctx context.Context, r ResourceRender) (err error) {
+	var (
+		name, ns = r.Name(), i.Namespace
+		hpa      = &autoscalingv2.HorizontalPodAutoscaler{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: ns,
+				Name:      name,
+			},
+		}
+		startTime = time.Now()
+		labels    = []metrics.LabelValue{
+			kindLabel.Value("HPA"),
+			nameLabel.Value(name),
+			namespaceLabel.Value(ns),
+		}
+	)
+
+	resourceDeleteTotal.With(labels...).Increment()
+
+	defer func() {
+		if err == nil {
+			resourceDeleteDurationSeconds.With(labels...).Record(time.Since(startTime).Seconds())
+			resourceDeleteSuccess.With(labels...).Increment()
+		} else {
+			resourceDeleteFailed.With(labels...).Increment()
+		}
+	}()
 
 	return i.Client.Delete(ctx, hpa)
 }
diff --git a/internal/infrastructure/kubernetes/metrics.go b/internal/infrastructure/kubernetes/metrics.go
new file mode 100644
index 000000000..d417845b5
--- /dev/null
+++ b/internal/infrastructure/kubernetes/metrics.go
@@ -0,0 +1,24 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package kubernetes
+
+import "github.com/envoyproxy/gateway/internal/metrics"
+
+var (
+	resourceApplyTotal           = metrics.NewCounter("resource_apply_total", "Total number of applied resources.")
+	resourceApplyFailed          = metrics.NewCounter("resource_apply_failed", "Number of applied resources that failed.")
+	resourceApplySuccess         = metrics.NewCounter("resource_apply_success", "Number of applied resources that succeed.")
+	resourceApplyDurationSeconds = metrics.NewHistogram("resource_apply_duration_seconds", "How long in seconds a resource be applied successfully.", []float64{0.001, 0.01, 0.1, 1, 5, 10})
+
+	resourceDeleteTotal           = metrics.NewCounter("resource_delete_total", "Total number of deleted resources.")
+	resourceDeleteFailed          = metrics.NewCounter("resource_delete_failed", "Number of deleted resources that failed.")
+	resourceDeleteSuccess         = metrics.NewCounter("resource_delete_success", "Number of deleted resources that succeed.")
+	resourceDeleteDurationSeconds = metrics.NewHistogram("resource_delete_duration_seconds", "How long in seconds a resource be deleted successfully.", []float64{0.001, 0.01, 0.1, 1, 5, 10})
+
+	kindLabel      = metrics.NewLabel("kind")
+	nameLabel      = metrics.NewLabel("name")
+	namespaceLabel = metrics.NewLabel("namespace")
+)
diff --git a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go
index 62d8e3df4..90b6eba8f 100644
--- a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go
+++ b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go
@@ -255,8 +255,9 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) {
 	}
 
 	// apply merge patch to deployment
-	if merged, err := r.rateLimitDeployment.ApplyMergePatch(deployment); err == nil {
-		deployment = merged
+	var err error
+	if deployment, err = r.rateLimitDeployment.ApplyMergePatch(deployment); err != nil {
+		return nil, err
 	}
 
 	return deployment, nil
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index 77505d575..7437712c0 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -575,6 +575,8 @@ type SecurityFeatures struct {
 	BasicAuth *BasicAuth `json:"basicAuth,omitempty" yaml:"basicAuth,omitempty"`
 	// ExtAuth defines the schema for the external authorization.
 	ExtAuth *ExtAuth `json:"extAuth,omitempty" yaml:"extAuth,omitempty"`
+	// Authorization defines the schema for the authorization.
+	Authorization *Authorization `json:"authorization,omitempty" yaml:"authorization,omitempty"`
 }
 
 func (s *SecurityFeatures) Printable() *SecurityFeatures {
@@ -784,6 +786,40 @@ type GRPCExtAuthService struct {
 	Authority string `json:"authority"`
 }
 
+// Authorization defines the schema for the authorization.
+//
+// +k8s:deepcopy-gen=true
+type Authorization struct {
+	// Rules defines the authorization rules.
+	Rules []*AuthorizationRule `json:"rules,omitempty"`
+
+	// DefaultAction defines the default action to be taken if no rules match.
+	DefaultAction egv1a1.AuthorizationAction `json:"defaultAction"`
+}
+
+// AuthorizationRule defines the schema for the authorization rule.
+//
+// +k8s:deepcopy-gen=true
+type AuthorizationRule struct {
+	// Name is a user-defined name for the rule.
+	// If not specified, a name will be generated by EG.
+	Name string `json:"name"`
+
+	// Action defines the action to be taken if the rule matches.
+	Action egv1a1.AuthorizationAction `json:"action"`
+
+	// Principal defines the principal to be matched.
+	Principal Principal `json:"principal"`
+}
+
+// Principal defines the schema for the principal.
+//
+// +k8s:deepcopy-gen=true
+type Principal struct {
+	// ClientCIDRs defines the client CIDRs to be matched.
+	ClientCIDRs []*CIDRMatch `json:"clientCIDRs,omitempty"`
+}
+
 // FaultInjection defines the schema for injecting faults into requests.
 //
 // +k8s:deepcopy-gen=true
@@ -1468,8 +1504,9 @@ type RateLimitRule struct {
 
 type CIDRMatch struct {
 	CIDR    string `json:"cidr" yaml:"cidr"`
-	IPv6    bool   `json:"ipv6" yaml:"ipv6"`
-	MaskLen int    `json:"maskLen" yaml:"maskLen"`
+	IP      string `json:"ip" yaml:"ip"`
+	MaskLen uint32 `json:"maskLen" yaml:"maskLen"`
+	IsIPv6  bool   `json:"isIPv6" yaml:"isIPv6"`
 	// Distinct means that each IP Address within the specified Source IP CIDR is treated as a distinct client selector
 	// and uses a separate rate limit bucket/counter.
 	Distinct bool `json:"distinct" yaml:"distinct"`
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index 7d93ad057..355c79407 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -124,6 +124,48 @@ func (in *AddHeader) DeepCopy() *AddHeader {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Authorization) DeepCopyInto(out *Authorization) {
+	*out = *in
+	if in.Rules != nil {
+		in, out := &in.Rules, &out.Rules
+		*out = make([]*AuthorizationRule, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(AuthorizationRule)
+				(*in).DeepCopyInto(*out)
+			}
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authorization.
+func (in *Authorization) DeepCopy() *Authorization {
+	if in == nil {
+		return nil
+	}
+	out := new(Authorization)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AuthorizationRule) DeepCopyInto(out *AuthorizationRule) {
+	*out = *in
+	in.Principal.DeepCopyInto(&out.Principal)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationRule.
+func (in *AuthorizationRule) DeepCopy() *AuthorizationRule {
+	if in == nil {
+		return nil
+	}
+	out := new(AuthorizationRule)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *BackOffPolicy) DeepCopyInto(out *BackOffPolicy) {
 	*out = *in
@@ -1629,6 +1671,32 @@ func (in *PerRetryPolicy) DeepCopy() *PerRetryPolicy {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Principal) DeepCopyInto(out *Principal) {
+	*out = *in
+	if in.ClientCIDRs != nil {
+		in, out := &in.ClientCIDRs, &out.ClientCIDRs
+		*out = make([]*CIDRMatch, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(CIDRMatch)
+				**out = **in
+			}
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Principal.
+func (in *Principal) DeepCopy() *Principal {
+	if in == nil {
+		return nil
+	}
+	out := new(Principal)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ProxyInfra) DeepCopyInto(out *ProxyInfra) {
 	*out = *in
@@ -1971,6 +2039,11 @@ func (in *SecurityFeatures) DeepCopyInto(out *SecurityFeatures) {
 		*out = new(ExtAuth)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Authorization != nil {
+		in, out := &in.Authorization, &out.Authorization
+		*out = new(Authorization)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityFeatures.
diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go
index 7051d21f4..791b7fcbf 100644
--- a/internal/provider/kubernetes/controller.go
+++ b/internal/provider/kubernetes/controller.go
@@ -464,11 +464,21 @@ func (r *gatewayAPIReconciler) processSecurityPolicyObjectRefs(
 		// Add the referenced BackendRefs and ReferenceGrants in ExtAuth to Maps for later processing
 		extAuth := policy.Spec.ExtAuth
 		if extAuth != nil {
-			var backendRef gwapiv1.BackendObjectReference
+			var backendRef *gwapiv1.BackendObjectReference
 			if extAuth.GRPC != nil {
 				backendRef = extAuth.GRPC.BackendRef
+				if len(extAuth.GRPC.BackendRefs) > 0 {
+					if len(extAuth.GRPC.BackendRefs) != 0 {
+						backendRef = egv1a1.ToBackendObjectReference(extAuth.GRPC.BackendRefs[0])
+					}
+				}
 			} else {
 				backendRef = extAuth.HTTP.BackendRef
+				if len(extAuth.HTTP.BackendRefs) > 0 {
+					if len(extAuth.HTTP.BackendRefs) != 0 {
+						backendRef = egv1a1.ToBackendObjectReference(extAuth.HTTP.BackendRefs[0])
+					}
+				}
 			}
 
 			backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, policy.Namespace)
diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go
index 57c13997f..d2abc6a25 100644
--- a/internal/provider/kubernetes/indexers.go
+++ b/internal/provider/kubernetes/indexers.go
@@ -507,9 +507,17 @@ func backendSecurityPolicyIndexFunc(rawObj client.Object) []string {
 
 	if securityPolicy.Spec.ExtAuth != nil {
 		if securityPolicy.Spec.ExtAuth.HTTP != nil {
-			backendRef = &securityPolicy.Spec.ExtAuth.HTTP.BackendRef
+			http := securityPolicy.Spec.ExtAuth.HTTP
+			backendRef = http.BackendRef
+			if len(http.BackendRefs) > 0 {
+				backendRef = v1alpha1.ToBackendObjectReference(http.BackendRefs[0])
+			}
 		} else if securityPolicy.Spec.ExtAuth.GRPC != nil {
-			backendRef = &securityPolicy.Spec.ExtAuth.GRPC.BackendRef
+			grpc := securityPolicy.Spec.ExtAuth.GRPC
+			backendRef = grpc.BackendRef
+			if len(grpc.BackendRefs) > 0 {
+				backendRef = v1alpha1.ToBackendObjectReference(grpc.BackendRefs[0])
+			}
 		}
 	}
 
diff --git a/internal/xds/translator/authorization.go b/internal/xds/translator/authorization.go
new file mode 100644
index 000000000..cfafb2725
--- /dev/null
+++ b/internal/xds/translator/authorization.go
@@ -0,0 +1,267 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package translator
+
+import (
+	"errors"
+	"fmt"
+
+	cncfv3 "github.com/cncf/xds/go/xds/core/v3"
+	matcherv3 "github.com/cncf/xds/go/xds/type/matcher/v3"
+	configv3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+	rbacconfigv3 "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
+	routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
+	rbacv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+	networkinput "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/network/v3"
+	ipmatcherv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/ip/v3"
+	"google.golang.org/protobuf/types/known/anypb"
+	"google.golang.org/protobuf/types/known/wrapperspb"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/ir"
+	"github.com/envoyproxy/gateway/internal/xds/types"
+)
+
+func init() {
+	registerHTTPFilter(&rbac{})
+}
+
+type rbac struct{}
+
+var _ httpFilter = &rbac{}
+
+// patchHCM builds and appends the RBAC Filter to the HTTP Connection Manager if
+// applicable.
+func (*rbac) patchHCM(
+	mgr *hcmv3.HttpConnectionManager,
+	irListener *ir.HTTPListener,
+) error {
+	if mgr == nil {
+		return errors.New("hcm is nil")
+	}
+
+	if irListener == nil {
+		return errors.New("ir listener is nil")
+	}
+
+	if !listenerContainsRBAC(irListener) {
+		return nil
+	}
+
+	// Return early if filter already exists.
+	for _, f := range mgr.HttpFilters {
+		if f.Name == string(egv1a1.EnvoyFilterRBAC) {
+			return nil
+		}
+	}
+
+	rbacFilter, err := buildHCMRBACFilter()
+	if err != nil {
+		return err
+	}
+
+	mgr.HttpFilters = append([]*hcmv3.HttpFilter{rbacFilter}, mgr.HttpFilters...)
+
+	return nil
+}
+
+// buildHCMRBACFilter returns a RBAC filter from the provided IR listener.
+func buildHCMRBACFilter() (*hcmv3.HttpFilter, error) {
+	rbacProto := &rbacv3.RBAC{}
+	rbacAny, err := anypb.New(rbacProto)
+	if err != nil {
+		return nil, err
+	}
+
+	return &hcmv3.HttpFilter{
+		Name: string(egv1a1.EnvoyFilterRBAC),
+		ConfigType: &hcmv3.HttpFilter_TypedConfig{
+			TypedConfig: rbacAny,
+		},
+	}, nil
+}
+
+// listenerContainsRBAC returns true if the provided listener has RBAC
+// policies attached to its routes.
+func listenerContainsRBAC(irListener *ir.HTTPListener) bool {
+	if irListener == nil {
+		return false
+	}
+
+	for _, route := range irListener.Routes {
+		if route.Security != nil && route.Security.Authorization != nil {
+			return true
+		}
+	}
+
+	return false
+}
+
+// patchRoute patches the provided route with the RBAC config if applicable.
+func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
+	if route == nil {
+		return errors.New("xds route is nil")
+	}
+	if irRoute == nil {
+		return errors.New("ir route is nil")
+	}
+	if irRoute.Security == nil || irRoute.Security.Authorization == nil {
+		return nil
+	}
+
+	filterCfg := route.GetTypedPerFilterConfig()
+	if _, ok := filterCfg[string(egv1a1.EnvoyFilterRBAC)]; ok {
+		// This should not happen since this is the only place where the RBAC
+		// filter is added in a route.
+		return fmt.Errorf("route already contains rbac config: %+v", route)
+	}
+
+	var (
+		authorization = irRoute.Security.Authorization
+		allowAction   *anypb.Any
+		denyAction    *anypb.Any
+		sourceIPInput *anypb.Any
+		ipMatcher     *anypb.Any
+		matcherList   []*matcherv3.Matcher_MatcherList_FieldMatcher
+		err           error
+	)
+
+	allow := &rbacconfigv3.Action{
+		Name:   "ALLOW",
+		Action: rbacconfigv3.RBAC_ALLOW,
+	}
+	if allowAction, err = anypb.New(allow); err != nil {
+		return err
+	}
+
+	deny := &rbacconfigv3.Action{
+		Name:   "DENY",
+		Action: rbacconfigv3.RBAC_DENY,
+	}
+	if denyAction, err = anypb.New(deny); err != nil {
+		return err
+	}
+
+	// Build a list of matchers based on the rules.
+	// The matchers will be evaluated in order, and the first one that matches
+	// will be used to determine the action, the rest of the matchers will be
+	// skipped.
+	// If no matcher matches, the default action will be used.
+	for _, rule := range authorization.Rules {
+		// Build the IPMatcher based on the client CIDRs.
+		ipRangeMatcher := &ipmatcherv3.Ip{
+			StatPrefix: "client_ip",
+		}
+
+		for _, cidr := range rule.Principal.ClientCIDRs {
+			ipRangeMatcher.CidrRanges = append(ipRangeMatcher.CidrRanges, &configv3.CidrRange{
+				AddressPrefix: cidr.IP,
+				PrefixLen: &wrapperspb.UInt32Value{
+					Value: cidr.MaskLen,
+				},
+			})
+		}
+
+		if ipMatcher, err = anypb.New(ipRangeMatcher); err != nil {
+			return err
+		}
+
+		if sourceIPInput, err = anypb.New(&networkinput.SourceIPInput{}); err != nil {
+			return err
+		}
+
+		// Determine the action for the current rule.
+		ruleAction := allowAction
+		if rule.Action == egv1a1.AuthorizationActionDeny {
+			ruleAction = denyAction
+		}
+
+		// Add the matcher generated with the current rule to the matcher list.
+		matcherList = append(matcherList, &matcherv3.Matcher_MatcherList_FieldMatcher{
+			Predicate: &matcherv3.Matcher_MatcherList_Predicate{
+				MatchType: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_{
+					SinglePredicate: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate{
+						Input: &cncfv3.TypedExtensionConfig{
+							Name:        "client_ip",
+							TypedConfig: sourceIPInput,
+						},
+						Matcher: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_CustomMatch{
+							CustomMatch: &cncfv3.TypedExtensionConfig{
+								Name:        "ip_matcher",
+								TypedConfig: ipMatcher,
+							},
+						},
+					},
+				},
+			},
+			OnMatch: &matcherv3.Matcher_OnMatch{
+				OnMatch: &matcherv3.Matcher_OnMatch_Action{
+					Action: &cncfv3.TypedExtensionConfig{
+						Name:        rule.Name,
+						TypedConfig: ruleAction,
+					},
+				},
+			},
+		})
+	}
+
+	// Set the default action.
+	defaultAction := denyAction
+	if authorization.DefaultAction == egv1a1.AuthorizationActionAllow {
+		defaultAction = allowAction
+	}
+
+	routeCfgProto := &rbacv3.RBACPerRoute{
+		Rbac: &rbacv3.RBAC{
+			Matcher: &matcherv3.Matcher{
+				MatcherType: &matcherv3.Matcher_MatcherList_{
+					MatcherList: &matcherv3.Matcher_MatcherList{
+						Matchers: matcherList,
+					},
+				},
+				// If no matcher matches, the default action will be used.
+				OnNoMatch: &matcherv3.Matcher_OnMatch{
+					OnMatch: &matcherv3.Matcher_OnMatch_Action{
+						Action: &cncfv3.TypedExtensionConfig{
+							Name:        "default",
+							TypedConfig: defaultAction,
+						},
+					},
+				},
+			},
+		},
+	}
+
+	// If there are no matchers, the default action will be used for all requests.
+	// Setting the matcher type to nil since Proto validation will fail if the list
+	// is empty.
+	if len(matcherList) == 0 {
+		routeCfgProto.Rbac.Matcher.MatcherType = nil
+	}
+
+	// We need to validate the RBACPerRoute message before converting it to an Any.
+	if err = routeCfgProto.ValidateAll(); err != nil {
+		return err
+	}
+
+	routeCfgAny, err := anypb.New(routeCfgProto)
+	if err != nil {
+		return err
+	}
+
+	if filterCfg == nil {
+		route.TypedPerFilterConfig = make(map[string]*anypb.Any)
+	}
+
+	route.TypedPerFilterConfig[string(egv1a1.EnvoyFilterRBAC)] = routeCfgAny
+
+	return nil
+}
+
+func (c *rbac) patchResources(*types.ResourceVersionTable, []*ir.HTTPRoute) error {
+	return nil
+}
diff --git a/internal/xds/translator/cors.go b/internal/xds/translator/cors.go
index f5a833087..cda5ae8a4 100644
--- a/internal/xds/translator/cors.go
+++ b/internal/xds/translator/cors.go
@@ -61,7 +61,6 @@ func (*cors) patchHCM(
 		return err
 	}
 
-	// Ensure the CORS filter is the first one in the filter chain.
 	mgr.HttpFilters = append([]*hcmv3.HttpFilter{corsFilter}, mgr.HttpFilters...)
 
 	return nil
diff --git a/internal/xds/translator/extauth.go b/internal/xds/translator/extauth.go
index 46ce5df80..5da500ddf 100644
--- a/internal/xds/translator/extauth.go
+++ b/internal/xds/translator/extauth.go
@@ -37,7 +37,6 @@ var _ httpFilter = &extAuth{}
 // if applicable, and it does not already exist.
 // Note: this method creates an ext_authz filter for each route that contains an ExtAuthz config.
 // The filter is disabled by default. It is enabled on the route level.
-// TODO: zhaohuabing avoid duplicated HTTP filters
 func (*extAuth) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error {
 	var errs error
 
diff --git a/internal/xds/translator/httpfilters.go b/internal/xds/translator/httpfilters.go
index 22c351f81..4bcdd9d56 100644
--- a/internal/xds/translator/httpfilters.go
+++ b/internal/xds/translator/httpfilters.go
@@ -17,7 +17,7 @@ import (
 	"github.com/envoyproxy/go-control-plane/pkg/wellknown"
 	"k8s.io/utils/ptr"
 
-	"github.com/envoyproxy/gateway/api/v1alpha1"
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 	"github.com/envoyproxy/gateway/internal/ir"
 	"github.com/envoyproxy/gateway/internal/xds/filters"
 	"github.com/envoyproxy/gateway/internal/xds/types"
@@ -93,6 +93,7 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter {
 	// Set a rational order for all the filters.
 	// When the fault filter is configured to be at the first, the computation of
 	// the remaining filters is skipped when rejected early
+	// TODO (zhaohuabing): remove duplicate filter type constants and replace them with the type constants in the api package
 	switch {
 	case isFilterType(filter, wellknown.Fault):
 		order = 1
@@ -110,12 +111,14 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter {
 		order = 7 + mustGetFilterIndex(filter.Name)
 	case isFilterType(filter, wasmFilter):
 		order = 100 + mustGetFilterIndex(filter.Name)
-	case isFilterType(filter, localRateLimitFilter):
+	case isFilterType(filter, string(egv1a1.EnvoyFilterRBAC)):
 		order = 201
-	case isFilterType(filter, wellknown.HTTPRateLimit):
+	case isFilterType(filter, localRateLimitFilter):
 		order = 202
-	case isFilterType(filter, wellknown.Router):
+	case isFilterType(filter, wellknown.HTTPRateLimit):
 		order = 203
+	case isFilterType(filter, wellknown.Router):
+		order = 204
 	}
 
 	return &OrderedHTTPFilter{
@@ -143,7 +146,7 @@ func (o OrderedHTTPFilters) Swap(i, j int) {
 // For example, the cors filter should be put at the first to avoid unnecessary
 // processing of other filters for unauthorized cross-region access.
 // The router filter must be the last one since it's a terminal filter.
-func sortHTTPFilters(filters []*hcmv3.HttpFilter, filterOrder []v1alpha1.FilterPosition) []*hcmv3.HttpFilter {
+func sortHTTPFilters(filters []*hcmv3.HttpFilter, filterOrder []egv1a1.FilterPosition) []*hcmv3.HttpFilter {
 	// Sort the filters in the default order.
 	orderedFilters := make(OrderedHTTPFilters, len(filters))
 	for i := 0; i < len(filters); i++ {
diff --git a/internal/xds/translator/jwt.go b/internal/xds/translator/jwt.go
index 27697043a..5a2b35ca6 100644
--- a/internal/xds/translator/jwt.go
+++ b/internal/xds/translator/jwt.go
@@ -65,7 +65,6 @@ func (*jwt) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen
 		return err
 	}
 
-	// Ensure the authn filter is the first and the terminal filter is the last in the chain.
 	mgr.HttpFilters = append([]*hcmv3.HttpFilter{jwtFilter}, mgr.HttpFilters...)
 
 	return nil
diff --git a/internal/xds/translator/local_ratelimit.go b/internal/xds/translator/local_ratelimit.go
index 1a0d88d5d..231bda44f 100644
--- a/internal/xds/translator/local_ratelimit.go
+++ b/internal/xds/translator/local_ratelimit.go
@@ -249,8 +249,8 @@ func buildRouteLocalRateLimits(local *ir.LocalRateLimit) (
 
 			// Setup MaskedRemoteAddress action
 			mra := &routev3.RateLimit_Action_MaskedRemoteAddress{}
-			maskLen := &wrapperspb.UInt32Value{Value: uint32(rule.CIDRMatch.MaskLen)}
-			if rule.CIDRMatch.IPv6 {
+			maskLen := &wrapperspb.UInt32Value{Value: rule.CIDRMatch.MaskLen}
+			if rule.CIDRMatch.IsIPv6 {
 				mra.V6PrefixMaskLen = maskLen
 			} else {
 				mra.V4PrefixMaskLen = maskLen
diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go
index 4534f303f..86a558b29 100644
--- a/internal/xds/translator/ratelimit.go
+++ b/internal/xds/translator/ratelimit.go
@@ -69,7 +69,6 @@ func (t *Translator) patchHCMWithRateLimit(mgr *hcmv3.HttpConnectionManager, irL
 	}
 
 	rateLimitFilter := t.buildRateLimitFilter(irListener)
-	// Make sure the router filter is the terminal filter in the chain.
 	mgr.HttpFilters = append([]*hcmv3.HttpFilter{rateLimitFilter}, mgr.HttpFilters...)
 }
 
@@ -225,8 +224,8 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [
 		if rule.CIDRMatch != nil {
 			// Setup MaskedRemoteAddress action
 			mra := &routev3.RateLimit_Action_MaskedRemoteAddress{}
-			maskLen := &wrapperspb.UInt32Value{Value: uint32(rule.CIDRMatch.MaskLen)}
-			if rule.CIDRMatch.IPv6 {
+			maskLen := &wrapperspb.UInt32Value{Value: rule.CIDRMatch.MaskLen}
+			if rule.CIDRMatch.IsIPv6 {
 				mra.V6PrefixMaskLen = maskLen
 			} else {
 				mra.V4PrefixMaskLen = maskLen


Test patch:
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml
index 7e3d794b7..695ab6bd7 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml
@@ -196,7 +196,8 @@ xdsIR:
               - cidrMatch:
                   cidr: 192.168.0.0/16
                   distinct: false
-                  ipv6: false
+                  ip: 192.168.0.0
+                  isIPv6: false
                   maskLen: 16
                 headerMatches:
                 - distinct: false
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml
index 9ebb100f6..b7508bc79 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml
@@ -199,7 +199,8 @@ xdsIR:
               - cidrMatch:
                   cidr: 192.168.0.0/16
                   distinct: false
-                  ipv6: false
+                  ip: 192.168.0.0
+                  isIPv6: false
                   maskLen: 16
                 headerMatches:
                 - distinct: false
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml
index 390752796..bdb547ffe 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml
@@ -338,7 +338,8 @@ xdsIR:
               - cidrMatch:
                   cidr: 192.168.0.0/16
                   distinct: true
-                  ipv6: false
+                  ip: 192.168.0.0
+                  isIPv6: false
                   maskLen: 16
                 headerMatches: []
                 limit:
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml
new file mode 100644
index 000000000..fe89f0dd8
--- /dev/null
+++ b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml
@@ -0,0 +1,128 @@
+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
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-1
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/foo"
+      backendRefs:
+      - name: service-1
+        port: 8080
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-2
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/bar"
+      backendRefs:
+      - name: service-1
+        port: 8080
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-3
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/test"
+      backendRefs:
+      - name: service-1
+        port: 8080
+securityPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: SecurityPolicy
+  metadata:
+    namespace: envoy-gateway
+    name: policy-for-gateway  # This policy should attach httproute-2
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: gateway-1
+    authorization:
+      defaultAction: Deny
+      rules:
+      - action: Allow
+        principal:
+          clientCIDRs:
+          - 10.0.1.0/24
+          - 10.0.2.0/24
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: SecurityPolicy
+  metadata:
+    namespace: default
+    name: policy-for-http-route-1   # This policy should attach httproute-1
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+    authorization:
+      defaultAction: Allow
+      rules:
+      - name: "deny-location-1"
+        action: Deny
+        principal:
+          clientCIDRs:
+          - 192.168.1.0/24
+          - 192.168.2.0/24
+      - name: "deny-location-2"
+        action: Deny
+        principal:
+          clientCIDRs:
+          - 10.75.1.0/24
+          - 10.75.2.0/24
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: SecurityPolicy
+  metadata:
+    namespace: default
+    name: policy-for-http-route-3   # This policy should attach httproute-3
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-3
+    authorization: {}
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml
new file mode 100755
index 000000000..1f6103bb2
--- /dev/null
+++ b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml
@@ -0,0 +1,398 @@
+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: 3
+      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
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /foo
+  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
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-2
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /bar
+  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
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-3
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /test
+  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
+      name: envoy-gateway/gateway-1
+securityPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: SecurityPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-http-route-1
+    namespace: default
+  spec:
+    authorization:
+      defaultAction: Allow
+      rules:
+      - action: Deny
+        name: deny-location-1
+        principal:
+          clientCIDRs:
+          - 192.168.1.0/24
+          - 192.168.2.0/24
+      - action: Deny
+        name: deny-location-2
+        principal:
+          clientCIDRs:
+          - 10.75.1.0/24
+          - 10.75.2.0/24
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: SecurityPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-http-route-3
+    namespace: default
+  spec:
+    authorization:
+      defaultAction: null
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-3
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: SecurityPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-gateway
+    namespace: envoy-gateway
+  spec:
+    authorization:
+      defaultAction: Deny
+      rules:
+      - action: Allow
+        principal:
+          clientCIDRs:
+          - 10.0.1.0/24
+          - 10.0.2.0/24
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: gateway-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: 'This policy is being overridden by other securityPolicies for these
+          routes: [default/httproute-1 default/httproute-3]'
+        reason: Overridden
+        status: "True"
+        type: Overridden
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-3/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: www.example.com
+        isHTTP2: false
+        name: httproute/default/httproute-3/rule/0/match/0/www_example_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /test
+        security:
+          authorization:
+            defaultAction: Deny
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: www.example.com
+        isHTTP2: false
+        name: httproute/default/httproute-1/rule/0/match/0/www_example_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /foo
+        security:
+          authorization:
+            defaultAction: Allow
+            rules:
+            - action: Deny
+              name: deny-location-1
+              principal:
+                clientCIDRs:
+                - cidr: 192.168.1.0/24
+                  distinct: false
+                  ip: 192.168.1.0
+                  isIPv6: false
+                  maskLen: 24
+                - cidr: 192.168.2.0/24
+                  distinct: false
+                  ip: 192.168.2.0
+                  isIPv6: false
+                  maskLen: 24
+            - action: Deny
+              name: deny-location-2
+              principal:
+                clientCIDRs:
+                - cidr: 10.75.1.0/24
+                  distinct: false
+                  ip: 10.75.1.0
+                  isIPv6: false
+                  maskLen: 24
+                - cidr: 10.75.2.0/24
+                  distinct: false
+                  ip: 10.75.2.0
+                  isIPv6: false
+                  maskLen: 24
+      - destination:
+          name: httproute/default/httproute-2/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: www.example.com
+        isHTTP2: false
+        name: httproute/default/httproute-2/rule/0/match/0/www_example_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /bar
+        security:
+          authorization:
+            defaultAction: Deny
+            rules:
+            - action: Allow
+              name: securitypolicy/envoy-gateway/policy-for-gateway/authorization/rule/0
+              principal:
+                clientCIDRs:
+                - cidr: 10.0.1.0/24
+                  distinct: false
+                  ip: 10.0.1.0
+                  isIPv6: false
+                  maskLen: 24
+                - cidr: 10.0.2.0/24
+                  distinct: false
+                  ip: 10.0.2.0
+                  isIPv6: false
+                  maskLen: 24
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-backendref.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-backendref.in.yaml
new file mode 100644
index 000000000..e36258312
--- /dev/null
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-backendref.in.yaml
@@ -0,0 +1,171 @@
+gateways:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: default
+      name: gateway-1
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+httpRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      namespace: default
+      name: httproute-1
+    spec:
+      hostnames:
+        - www.foo.com
+      parentRefs:
+        - namespace: default
+          name: gateway-1
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                value: /foo1
+          backendRefs:
+            - name: service-1
+              port: 8080
+        - matches:
+            - path:
+                value: /foo2
+          backendRefs:
+            - name: service-2
+              port: 8080
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      namespace: default
+      name: httproute-2
+    spec:
+      hostnames:
+        - www.bar.com
+      parentRefs:
+        - namespace: default
+          name: gateway-1
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                value: /bar
+          backendRefs:
+            - name: service-3
+              port: 8080
+services:
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      namespace: envoy-gateway
+      name: http-backend
+    spec:
+      ports:
+        - port: 80
+          name: http
+          protocol: TCP
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      namespace: default
+      name: grpc-backend
+    spec:
+      ports:
+        - port: 9000
+          name: grpc
+          protocol: TCP
+endpointSlices:
+  - apiVersion: discovery.k8s.io/v1
+    kind: EndpointSlice
+    metadata:
+      name: endpointslice-http-backend
+      namespace: envoy-gateway
+      labels:
+        kubernetes.io/service-name: http-backend
+    addressType: IPv4
+    ports:
+      - name: http
+        protocol: TCP
+        port: 80
+    endpoints:
+      - addresses:
+          - 7.7.7.7
+        conditions:
+          ready: true
+  - apiVersion: discovery.k8s.io/v1
+    kind: EndpointSlice
+    metadata:
+      name: endpointslice-grpc-backend
+      namespace: default
+      labels:
+        kubernetes.io/service-name: grpc-backend
+    addressType: IPv4
+    ports:
+      - name: grpc
+        protocol: TCP
+        port: 9000
+    endpoints:
+      - addresses:
+          - 8.8.8.8
+        conditions:
+          ready: true
+referenceGrants:
+  - apiVersion: gateway.networking.k8s.io/v1alpha2
+    kind: ReferenceGrant
+    metadata:
+      namespace: envoy-gateway
+      name: referencegrant-1
+    spec:
+      from:
+        - group: gateway.envoyproxy.io
+          kind: SecurityPolicy
+          namespace: default
+      to:
+        - group: ''
+          kind: Service
+securityPolicies:
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: SecurityPolicy
+    metadata:
+      namespace: default
+      name: policy-for-http-route-1
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: HTTPRoute
+        name: httproute-1
+      extAuth:
+        failOpen: true
+        headersToExtAuth:
+          - header1
+          - header2
+        grpc:
+          backendRef:
+            name: grpc-backend
+            port: 9000
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: SecurityPolicy
+    metadata:
+      namespace: default
+      name: policy-for-gateway-1          # This will only apply to the httproute-2
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+      extAuth:
+        failOpen: false
+        http:
+          backendRef:
+            Name: http-backend
+            Namespace: envoy-gateway
+            Port: 80
+          Path: /auth
+          headersToBackend:
+            - header1
+            - header2
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-backendref.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-backendref.out.yaml
new file mode 100755
index 000000000..4d7686f8f
--- /dev/null
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-backendref.out.yaml
@@ -0,0 +1,337 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: default
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 2
+      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
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - www.foo.com
+    parentRefs:
+    - name: gateway-1
+      namespace: default
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /foo1
+    - backendRefs:
+      - name: service-2
+        port: 8080
+      matches:
+      - path:
+          value: /foo2
+  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: default
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-2
+    namespace: default
+  spec:
+    hostnames:
+    - www.bar.com
+    parentRefs:
+    - name: gateway-1
+      namespace: default
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-3
+        port: 8080
+      matches:
+      - path:
+          value: /bar
+  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: default
+        sectionName: http
+infraIR:
+  default/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: default/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: default
+      name: default/gateway-1
+securityPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: SecurityPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-http-route-1
+    namespace: default
+  spec:
+    extAuth:
+      failOpen: true
+      grpc:
+        backendRef:
+          name: grpc-backend
+          port: 9000
+      headersToExtAuth:
+      - header1
+      - header2
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: default
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: SecurityPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-gateway-1
+    namespace: default
+  spec:
+    extAuth:
+      failOpen: false
+      http:
+        backendRef:
+          name: http-backend
+          namespace: envoy-gateway
+          port: 80
+        headersToBackend:
+        - header1
+        - header2
+        path: /auth
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: gateway-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: default
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: 'This policy is being overridden by other securityPolicies for these
+          routes: [default/httproute-1]'
+        reason: Overridden
+        status: "True"
+        type: Overridden
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+xdsIR:
+  default/gateway-1:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      name: default/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: www.foo.com
+        isHTTP2: false
+        name: httproute/default/httproute-1/rule/0/match/0/www_foo_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /foo1
+        security:
+          extAuth:
+            failOpen: true
+            grpc:
+              authority: grpc-backend.default:9000
+              destination:
+                name: securitypolicy/default/policy-for-http-route-1/default/grpc-backend
+                settings:
+                - addressType: IP
+                  endpoints:
+                  - host: 8.8.8.8
+                    port: 9000
+                  protocol: GRPC
+                  weight: 1
+            headersToExtAuth:
+            - header1
+            - header2
+            name: securitypolicy/default/policy-for-http-route-1
+      - destination:
+          name: httproute/default/httproute-1/rule/1
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: www.foo.com
+        isHTTP2: false
+        name: httproute/default/httproute-1/rule/1/match/0/www_foo_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /foo2
+        security:
+          extAuth:
+            failOpen: true
+            grpc:
+              authority: grpc-backend.default:9000
+              destination:
+                name: securitypolicy/default/policy-for-http-route-1/default/grpc-backend
+                settings:
+                - addressType: IP
+                  endpoints:
+                  - host: 8.8.8.8
+                    port: 9000
+                  protocol: GRPC
+                  weight: 1
+            headersToExtAuth:
+            - header1
+            - header2
+            name: securitypolicy/default/policy-for-http-route-1
+      - destination:
+          name: httproute/default/httproute-2/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: www.bar.com
+        isHTTP2: false
+        name: httproute/default/httproute-2/rule/0/match/0/www_bar_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /bar
+        security:
+          extAuth:
+            failOpen: false
+            http:
+              authority: http-backend.envoy-gateway:80
+              destination:
+                name: securitypolicy/default/policy-for-gateway-1/envoy-gateway/http-backend
+                settings:
+                - addressType: IP
+                  endpoints:
+                  - host: 7.7.7.7
+                    port: 80
+                  protocol: HTTP
+                  weight: 1
+              headersToBackend:
+              - header1
+              - header2
+              path: /auth
+            name: securitypolicy/default/policy-for-gateway-1
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-matching-port.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-matching-port.in.yaml
index 1ffcb3027..d562b1c7e 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-matching-port.in.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-matching-port.in.yaml
@@ -55,10 +55,10 @@ securityPolicies:
         name: gateway-1
       extAuth:
         http:
-          backendRef:
-            name: http-backend
-            namespace: default
-            port: 80
+          backendRefs:
+            - name: http-backend
+              namespace: default
+              port: 80
           headersToBackend:
             - header1
             - header2
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-matching-port.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-matching-port.out.yaml
index 582db7cf2..14bbc4ea0 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-matching-port.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-matching-port.out.yaml
@@ -105,8 +105,8 @@ securityPolicies:
     extAuth:
       failOpen: false
       http:
-        backendRef:
-          name: http-backend
+        backendRefs:
+        - name: http-backend
           namespace: default
           port: 80
         headersToBackend:
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-port.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-port.in.yaml
index 75c777585..3f743d180 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-port.in.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-port.in.yaml
@@ -55,9 +55,9 @@ securityPolicies:
         name: gateway-1
       extAuth:
         http:
-          backendRef:
-            name: http-backend
-            namespace: default
+          backendRefs:
+            - name: http-backend
+              namespace: default
           headersToBackend:
             - header1
             - header2
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-port.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-port.out.yaml
index fa9d38b25..f7cbe8322 100755
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-port.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-port.out.yaml
@@ -105,8 +105,8 @@ securityPolicies:
     extAuth:
       failOpen: false
       http:
-        backendRef:
-          name: http-backend
+        backendRefs:
+        - name: http-backend
           namespace: default
         headersToBackend:
         - header1
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-reference-grant.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-reference-grant.in.yaml
index c0397d678..06cb84324 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-reference-grant.in.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-reference-grant.in.yaml
@@ -55,10 +55,10 @@ securityPolicies:
         name: gateway-1
       extAuth:
         http:
-          backendRef:
-            name: http-backend
-            namespace: envoy-gateway
-            port: 80
+          backendRefs:
+            - name: http-backend
+              namespace: envoy-gateway
+              port: 80
           headersToBackend:
             - header1
             - header2
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-reference-grant.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-reference-grant.out.yaml
index 9afc90b09..e1317710c 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-reference-grant.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-reference-grant.out.yaml
@@ -105,8 +105,8 @@ securityPolicies:
     extAuth:
       failOpen: false
       http:
-        backendRef:
-          name: http-backend
+        backendRefs:
+        - name: http-backend
           namespace: envoy-gateway
           port: 80
         headersToBackend:
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-service.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-service.in.yaml
index 3c2fba3e7..e47a1b0f5 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-service.in.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-service.in.yaml
@@ -46,10 +46,10 @@ securityPolicies:
         name: gateway-1
       extAuth:
         http:
-          backendRef:
-            name: http-backend
-            namespace: default
-            port: 80
+          backendRefs:
+            - name: http-backend
+              namespace: default
+              port: 80
           headersToBackend:
             - header1
             - header2
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-service.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-service.out.yaml
index cc079ddf6..fba4578b3 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-service.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-invalid-no-service.out.yaml
@@ -105,8 +105,8 @@ securityPolicies:
     extAuth:
       failOpen: false
       http:
-        backendRef:
-          name: http-backend
+        backendRefs:
+        - name: http-backend
           namespace: default
           port: 80
         headersToBackend:
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-with-backendtlspolicy.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-with-backendtlspolicy.in.yaml
index 669e9443f..d2aee51b2 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-with-backendtlspolicy.in.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-with-backendtlspolicy.in.yaml
@@ -197,10 +197,10 @@ securityPolicies:
         name: gateway-1
       extAuth:
         http:
-          backendRef:
-            Name: http-backend
-            Namespace: envoy-gateway
-            Port: 80
+          backendRefs:
+            - Name: http-backend
+              Namespace: envoy-gateway
+              Port: 80
           Path: /auth
           headersToBackend:
             - header1
@@ -221,7 +221,7 @@ securityPolicies:
           - header1
           - header2
         grpc:
-          backendRef:
-            name: grpc-backend
-            port: 9000
+          backendRefs:
+            - name: grpc-backend
+              port: 9000
         failOpen: true
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth-with-backendtlspolicy.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth-with-backendtlspolicy.out.yaml
index 6ac00a0e5..b750c2d61 100755
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth-with-backendtlspolicy.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth-with-backendtlspolicy.out.yaml
@@ -208,8 +208,8 @@ securityPolicies:
     extAuth:
       failOpen: true
       grpc:
-        backendRef:
-          name: grpc-backend
+        backendRefs:
+        - name: grpc-backend
           port: 9000
       headersToExtAuth:
       - header1
@@ -243,8 +243,8 @@ securityPolicies:
     extAuth:
       failOpen: false
       http:
-        backendRef:
-          name: http-backend
+        backendRefs:
+        - name: http-backend
           namespace: envoy-gateway
           port: 80
         headersToBackend:
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth.in.yaml
index e36258312..ada3900ed 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth.in.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth.in.yaml
@@ -126,7 +126,7 @@ referenceGrants:
           kind: SecurityPolicy
           namespace: default
       to:
-        - group: ''
+        - group: ""
           kind: Service
 securityPolicies:
   - apiVersion: gateway.envoyproxy.io/v1alpha1
@@ -145,14 +145,15 @@ securityPolicies:
           - header1
           - header2
         grpc:
-          backendRef:
-            name: grpc-backend
-            port: 9000
+          backendRefs:
+            - name: grpc-backend
+              port: 9000
   - apiVersion: gateway.envoyproxy.io/v1alpha1
     kind: SecurityPolicy
     metadata:
       namespace: default
-      name: policy-for-gateway-1          # This will only apply to the httproute-2
+      # This will only apply to the httproute-2
+      name: policy-for-gateway-1
     spec:
       targetRef:
         group: gateway.networking.k8s.io
@@ -161,10 +162,10 @@ securityPolicies:
       extAuth:
         failOpen: false
         http:
-          backendRef:
-            Name: http-backend
-            Namespace: envoy-gateway
-            Port: 80
+          backendRefs:
+            - Name: http-backend
+              Namespace: envoy-gateway
+              Port: 80
           Path: /auth
           headersToBackend:
             - header1
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-extauth.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-extauth.out.yaml
index 4d7686f8f..2e0ca0067 100644
--- a/internal/gatewayapi/testdata/securitypolicy-with-extauth.out.yaml
+++ b/internal/gatewayapi/testdata/securitypolicy-with-extauth.out.yaml
@@ -149,8 +149,8 @@ securityPolicies:
     extAuth:
       failOpen: true
       grpc:
-        backendRef:
-          name: grpc-backend
+        backendRefs:
+        - name: grpc-backend
           port: 9000
       headersToExtAuth:
       - header1
@@ -184,8 +184,8 @@ securityPolicies:
     extAuth:
       failOpen: false
       http:
-        backendRef:
-          name: http-backend
+        backendRefs:
+        - name: http-backend
           namespace: envoy-gateway
           port: 80
         headersToBackend:
diff --git a/internal/provider/kubernetes/predicates_test.go b/internal/provider/kubernetes/predicates_test.go
index f5416c198..c7b6054e5 100644
--- a/internal/provider/kubernetes/predicates_test.go
+++ b/internal/provider/kubernetes/predicates_test.go
@@ -595,8 +595,12 @@ func TestValidateServiceForReconcile(t *testing.T) {
 						},
 						ExtAuth: &v1alpha1.ExtAuth{
 							HTTP: &v1alpha1.HTTPExtAuthService{
-								BackendRef: gwapiv1.BackendObjectReference{
-									Name: "ext-auth-http-service",
+								BackendRefs: []v1alpha1.BackendRef{
+									{
+										BackendObjectReference: gwapiv1.BackendObjectReference{
+											Name: "ext-auth-http-service",
+										},
+									},
 								},
 							},
 						},
@@ -606,6 +610,37 @@ func TestValidateServiceForReconcile(t *testing.T) {
 			service: test.GetService(types.NamespacedName{Name: "ext-auth-http-service"}, nil, nil),
 			expect:  true,
 		},
+		{
+			name: "service referenced by SecurityPolicy ExtAuth GRPC service",
+			configs: []client.Object{
+				&v1alpha1.SecurityPolicy{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "ext-auth-http",
+					},
+					Spec: v1alpha1.SecurityPolicySpec{
+						TargetRef: gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Kind: "Gateway",
+								Name: "scheduled-status-test",
+							},
+						},
+						ExtAuth: &v1alpha1.ExtAuth{
+							GRPC: &v1alpha1.GRPCExtAuthService{
+								BackendRefs: []v1alpha1.BackendRef{
+									{
+										BackendObjectReference: gwapiv1.BackendObjectReference{
+											Name: "ext-auth-grpc-service",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			service: test.GetService(types.NamespacedName{Name: "ext-auth-grpc-service"}, nil, nil),
+			expect:  true,
+		},
 		{
 			name: "service referenced by EnvoyExtensionPolicy ExtPrc GRPC service",
 			configs: []client.Object{
diff --git a/internal/xds/translator/httpfilters_test.go b/internal/xds/translator/httpfilters_test.go
index 2f277aa96..5516dc0d9 100644
--- a/internal/xds/translator/httpfilters_test.go
+++ b/internal/xds/translator/httpfilters_test.go
@@ -13,14 +13,14 @@ import (
 	"github.com/stretchr/testify/assert"
 	"k8s.io/utils/ptr"
 
-	"github.com/envoyproxy/gateway/api/v1alpha1"
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 )
 
 func Test_sortHTTPFilters(t *testing.T) {
 	tests := []struct {
 		name        string
 		filters     []*hcmv3.HttpFilter
-		filterOrder []v1alpha1.FilterPosition
+		filterOrder []egv1a1.FilterPosition
 		want        []*hcmv3.HttpFilter
 	}{
 		{
@@ -40,6 +40,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 			},
 			want: []*hcmv3.HttpFilter{
 				httpFilterForTest(wellknown.Fault),
@@ -53,6 +54,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wellknown.HTTPRateLimit),
 				httpFilterForTest(wellknown.Router),
@@ -75,15 +77,16 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 			},
-			filterOrder: []v1alpha1.FilterPosition{
+			filterOrder: []egv1a1.FilterPosition{
 				{
-					Name:  v1alpha1.EnvoyFilterFault,
-					After: ptr.To(v1alpha1.EnvoyFilterCORS),
+					Name:  egv1a1.EnvoyFilterFault,
+					After: ptr.To(egv1a1.EnvoyFilterCORS),
 				},
 				{
-					Name:   v1alpha1.EnvoyFilterRateLimit,
-					Before: ptr.To(v1alpha1.EnvoyFilterJWTAuthn),
+					Name:   egv1a1.EnvoyFilterRateLimit,
+					Before: ptr.To(egv1a1.EnvoyFilterJWTAuthn),
 				},
 			},
 			want: []*hcmv3.HttpFilter{
@@ -99,6 +102,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wellknown.Router),
 			},
@@ -120,11 +124,12 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 			},
-			filterOrder: []v1alpha1.FilterPosition{
+			filterOrder: []egv1a1.FilterPosition{
 				{
-					Name:   v1alpha1.EnvoyFilterRateLimit,
-					Before: ptr.To(v1alpha1.EnvoyFilterWasm),
+					Name:   egv1a1.EnvoyFilterRateLimit,
+					Before: ptr.To(egv1a1.EnvoyFilterWasm),
 				},
 			},
 			want: []*hcmv3.HttpFilter{
@@ -140,6 +145,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wellknown.Router),
 			},
@@ -161,11 +167,12 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 			},
-			filterOrder: []v1alpha1.FilterPosition{
+			filterOrder: []egv1a1.FilterPosition{
 				{
-					Name:  v1alpha1.EnvoyFilterJWTAuthn,
-					After: ptr.To(v1alpha1.EnvoyFilterWasm),
+					Name:  egv1a1.EnvoyFilterJWTAuthn,
+					After: ptr.To(egv1a1.EnvoyFilterWasm),
 				},
 			},
 			want: []*hcmv3.HttpFilter{
@@ -180,6 +187,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"),
 				httpFilterForTest(jwtAuthn),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wellknown.HTTPRateLimit),
 				httpFilterForTest(wellknown.Router),
@@ -202,11 +210,12 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 			},
-			filterOrder: []v1alpha1.FilterPosition{
+			filterOrder: []egv1a1.FilterPosition{
 				{
-					Name:   v1alpha1.EnvoyFilterWasm,
-					Before: ptr.To(v1alpha1.EnvoyFilterJWTAuthn),
+					Name:   egv1a1.EnvoyFilterWasm,
+					Before: ptr.To(egv1a1.EnvoyFilterJWTAuthn),
 				},
 			},
 			want: []*hcmv3.HttpFilter{
@@ -221,6 +230,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(jwtAuthn),
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wellknown.HTTPRateLimit),
 				httpFilterForTest(wellknown.Router),
@@ -243,11 +253,12 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 			},
-			filterOrder: []v1alpha1.FilterPosition{
+			filterOrder: []egv1a1.FilterPosition{
 				{
-					Name:  v1alpha1.EnvoyFilterWasm,
-					After: ptr.To(v1alpha1.EnvoyFilterRateLimit),
+					Name:  egv1a1.EnvoyFilterWasm,
+					After: ptr.To(egv1a1.EnvoyFilterRateLimit),
 				},
 			},
 			want: []*hcmv3.HttpFilter{
@@ -259,6 +270,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(jwtAuthn),
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wellknown.HTTPRateLimit),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
@@ -284,11 +296,12 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 			},
-			filterOrder: []v1alpha1.FilterPosition{
+			filterOrder: []egv1a1.FilterPosition{
 				{
-					Name:   v1alpha1.EnvoyFilterWasm,
-					Before: ptr.To(v1alpha1.EnvoyFilterExtProc),
+					Name:   egv1a1.EnvoyFilterWasm,
+					Before: ptr.To(egv1a1.EnvoyFilterExtProc),
 				},
 			},
 			want: []*hcmv3.HttpFilter{
@@ -303,6 +316,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"),
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wellknown.HTTPRateLimit),
 				httpFilterForTest(wellknown.Router),
@@ -325,11 +339,12 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 			},
-			filterOrder: []v1alpha1.FilterPosition{
+			filterOrder: []egv1a1.FilterPosition{
 				{
-					Name:  v1alpha1.EnvoyFilterExtProc,
-					After: ptr.To(v1alpha1.EnvoyFilterWasm),
+					Name:  egv1a1.EnvoyFilterExtProc,
+					After: ptr.To(egv1a1.EnvoyFilterWasm),
 				},
 			},
 			want: []*hcmv3.HttpFilter{
@@ -344,6 +359,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"),
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wellknown.HTTPRateLimit),
 				httpFilterForTest(wellknown.Router),
@@ -366,23 +382,24 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(extProcFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/0"),
 				httpFilterForTest(localRateLimitFilter),
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/1"),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 			},
-			filterOrder: []v1alpha1.FilterPosition{
+			filterOrder: []egv1a1.FilterPosition{
 				{
-					Name:   v1alpha1.EnvoyFilterLocalRateLimit,
-					Before: ptr.To(v1alpha1.EnvoyFilterJWTAuthn),
+					Name:   egv1a1.EnvoyFilterLocalRateLimit,
+					Before: ptr.To(egv1a1.EnvoyFilterJWTAuthn),
 				},
 				{
-					Name:  v1alpha1.EnvoyFilterLocalRateLimit,
-					After: ptr.To(v1alpha1.EnvoyFilterCORS),
+					Name:  egv1a1.EnvoyFilterLocalRateLimit,
+					After: ptr.To(egv1a1.EnvoyFilterCORS),
 				},
 				{
-					Name:   v1alpha1.EnvoyFilterWasm,
-					Before: ptr.To(v1alpha1.EnvoyFilterOAuth2),
+					Name:   egv1a1.EnvoyFilterWasm,
+					Before: ptr.To(egv1a1.EnvoyFilterOAuth2),
 				},
 				{
-					Name:   v1alpha1.EnvoyFilterExtProc,
-					Before: ptr.To(v1alpha1.EnvoyFilterWasm),
+					Name:   egv1a1.EnvoyFilterExtProc,
+					Before: ptr.To(egv1a1.EnvoyFilterWasm),
 				},
 			},
 			want: []*hcmv3.HttpFilter{
@@ -398,6 +415,7 @@ func Test_sortHTTPFilters(t *testing.T) {
 				httpFilterForTest(wasmFilter + "/envoyextensionpolicy/default/policy-for-http-route-1/2"),
 				httpFilterForTest(oauth2Filter + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(jwtAuthn),
+				httpFilterForTest(string(egv1a1.EnvoyFilterRBAC) + "/securitypolicy/default/policy-for-http-route-1"),
 				httpFilterForTest(wellknown.HTTPRateLimit),
 				httpFilterForTest(wellknown.Router),
 			},
diff --git a/internal/xds/translator/testdata/in/xds-ir/authorization.yaml b/internal/xds/translator/testdata/in/xds-ir/authorization.yaml
new file mode 100644
index 000000000..d36a738a7
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/authorization.yaml
@@ -0,0 +1,112 @@
+http:
+- address: 0.0.0.0
+  hostnames:
+  - '*'
+  isHTTP2: false
+  name: envoy-gateway/gateway-1/http
+  path:
+    escapedSlashesAction: UnescapeAndRedirect
+    mergeSlashes: true
+  port: 10080
+  routes:
+  - destination:
+      name: httproute/default/httproute-3/rule/0
+      settings:
+      - addressType: IP
+        endpoints:
+        - host: 7.7.7.7
+          port: 8080
+        protocol: HTTP
+        weight: 1
+    hostname: www.example.com
+    isHTTP2: false
+    name: httproute/default/httproute-3/rule/0/match/0/www_example_com
+    pathMatch:
+      distinct: false
+      name: ""
+      prefix: /test
+    security:
+      authorization:
+        defaultAction: Deny
+  - destination:
+      name: httproute/default/httproute-1/rule/0
+      settings:
+      - addressType: IP
+        endpoints:
+        - host: 7.7.7.7
+          port: 8080
+        protocol: HTTP
+        weight: 1
+    hostname: www.example.com
+    isHTTP2: false
+    name: httproute/default/httproute-1/rule/0/match/0/www_example_com
+    pathMatch:
+      distinct: false
+      name: ""
+      prefix: /foo
+    security:
+      authorization:
+        defaultAction: Allow
+        rules:
+        - action: Deny
+          name: deny-location-1
+          principal:
+            clientCIDRs:
+            - cidr: 192.168.1.0/24
+              distinct: false
+              ip: 192.168.1.0
+              isIPv6: false
+              maskLen: 24
+            - cidr: 192.168.2.0/24
+              distinct: false
+              ip: 192.168.2.0
+              isIPv6: false
+              maskLen: 24
+        - action: Deny
+          name: deny-location-2
+          principal:
+            clientCIDRs:
+            - cidr: 10.75.1.0/24
+              distinct: false
+              ip: 10.75.1.0
+              isIPv6: false
+              maskLen: 24
+            - cidr: 10.75.2.0/24
+              distinct: false
+              ip: 10.75.2.0
+              isIPv6: false
+              maskLen: 24
+  - destination:
+      name: httproute/default/httproute-2/rule/0
+      settings:
+      - addressType: IP
+        endpoints:
+        - host: 7.7.7.7
+          port: 8080
+        protocol: HTTP
+        weight: 1
+    hostname: www.example.com
+    isHTTP2: false
+    name: httproute/default/httproute-2/rule/0/match/0/www_example_com
+    pathMatch:
+      distinct: false
+      name: ""
+      prefix: /bar
+    security:
+      authorization:
+        defaultAction: Deny
+        rules:
+        - action: Allow
+          name: securitypolicy/envoy-gateway/policy-for-gateway/authorization/rule/0
+          principal:
+            clientCIDRs:
+            - cidr: 10.0.1.0/24
+              distinct: false
+              ip: 10.0.1.0
+              isIPv6: false
+              maskLen: 24
+            - cidr: 10.0.2.0/24
+              distinct: false
+              ip: 10.0.2.0
+              isIPv6: false
+              maskLen: 24
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization.clusters.yaml
new file mode 100644
index 000000000..b3f75f0e0
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization.clusters.yaml
@@ -0,0 +1,51 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-3/rule/0
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-3/rule/0
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-1/rule/0
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-1/rule/0
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-2/rule/0
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-2/rule/0
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization.endpoints.yaml
new file mode 100644
index 000000000..24596d841
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization.endpoints.yaml
@@ -0,0 +1,36 @@
+- clusterName: httproute/default/httproute-3/rule/0
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 7.7.7.7
+            portValue: 8080
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: httproute/default/httproute-3/rule/0/backend/0
+- clusterName: httproute/default/httproute-1/rule/0
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 7.7.7.7
+            portValue: 8080
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: httproute/default/httproute-1/rule/0/backend/0
+- clusterName: httproute/default/httproute-2/rule/0
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 7.7.7.7
+            portValue: 8080
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: httproute/default/httproute-2/rule/0/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization.listeners.yaml
new file mode 100644
index 000000000..746999960
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization.listeners.yaml
@@ -0,0 +1,37 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  defaultFilterChain:
+    filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.rbac
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        mergeSlashes: true
+        normalizePath: true
+        pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: envoy-gateway/gateway-1/http
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http
+        useRemoteAddress: true
+  drainType: MODIFY_ONLY
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml
new file mode 100644
index 000000000..141d60a15
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization.routes.yaml
@@ -0,0 +1,135 @@
+- ignorePortInHostMatching: true
+  name: envoy-gateway/gateway-1/http
+  virtualHosts:
+  - domains:
+    - www.example.com
+    name: envoy-gateway/gateway-1/http/www_example_com
+    routes:
+    - match:
+        pathSeparatedPrefix: /test
+      name: httproute/default/httproute-3/rule/0/match/0/www_example_com
+      route:
+        cluster: httproute/default/httproute-3/rule/0
+        upgradeConfigs:
+        - upgradeType: websocket
+      typedPerFilterConfig:
+        envoy.filters.http.rbac:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
+          rbac:
+            matcher:
+              onNoMatch:
+                action:
+                  name: default
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    action: DENY
+                    name: DENY
+    - match:
+        pathSeparatedPrefix: /foo
+      name: httproute/default/httproute-1/rule/0/match/0/www_example_com
+      route:
+        cluster: httproute/default/httproute-1/rule/0
+        upgradeConfigs:
+        - upgradeType: websocket
+      typedPerFilterConfig:
+        envoy.filters.http.rbac:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
+          rbac:
+            matcher:
+              matcherList:
+                matchers:
+                - onMatch:
+                    action:
+                      name: deny-location-1
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                        action: DENY
+                        name: DENY
+                  predicate:
+                    singlePredicate:
+                      customMatch:
+                        name: ip_matcher
+                        typedConfig:
+                          '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip
+                          cidrRanges:
+                          - addressPrefix: 192.168.1.0
+                            prefixLen: 24
+                          - addressPrefix: 192.168.2.0
+                            prefixLen: 24
+                          statPrefix: client_ip
+                      input:
+                        name: client_ip
+                        typedConfig:
+                          '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput
+                - onMatch:
+                    action:
+                      name: deny-location-2
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                        action: DENY
+                        name: DENY
+                  predicate:
+                    singlePredicate:
+                      customMatch:
+                        name: ip_matcher
+                        typedConfig:
+                          '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip
+                          cidrRanges:
+                          - addressPrefix: 10.75.1.0
+                            prefixLen: 24
+                          - addressPrefix: 10.75.2.0
+                            prefixLen: 24
+                          statPrefix: client_ip
+                      input:
+                        name: client_ip
+                        typedConfig:
+                          '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput
+              onNoMatch:
+                action:
+                  name: default
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    name: ALLOW
+    - match:
+        pathSeparatedPrefix: /bar
+      name: httproute/default/httproute-2/rule/0/match/0/www_example_com
+      route:
+        cluster: httproute/default/httproute-2/rule/0
+        upgradeConfigs:
+        - upgradeType: websocket
+      typedPerFilterConfig:
+        envoy.filters.http.rbac:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
+          rbac:
+            matcher:
+              matcherList:
+                matchers:
+                - onMatch:
+                    action:
+                      name: securitypolicy/envoy-gateway/policy-for-gateway/authorization/rule/0
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                        name: ALLOW
+                  predicate:
+                    singlePredicate:
+                      customMatch:
+                        name: ip_matcher
+                        typedConfig:
+                          '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.ip.v3.Ip
+                          cidrRanges:
+                          - addressPrefix: 10.0.1.0
+                            prefixLen: 24
+                          - addressPrefix: 10.0.2.0
+                            prefixLen: 24
+                          statPrefix: client_ip
+                      input:
+                        name: client_ip
+                        typedConfig:
+                          '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.SourceIPInput
+              onNoMatch:
+                action:
+                  name: default
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    action: DENY
+                    name: DENY
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index b4bfec6a2..87767fc42 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -195,13 +195,48 @@ _Appears in:_
 
 Authorization defines the authorization configuration.
 
+
+Note: if neither `Rules` nor `DefaultAction` is specified, the default action is to deny all requests.
+
 _Appears in:_
 - [SecurityPolicySpec](#securitypolicyspec)
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `rules` | _[Rule](#rule) array_ |  false  | Rules defines a list of authorization rules.<br />These rules are evaluated in order, the first matching rule will be applied,<br />and the rest will be skipped.<br /><br />For example, if there are two rules: the first rule allows the request<br />and the second rule denies it, when a request matches both rules, it will be allowed. |
-| `defaultAction` | _[RuleActionType](#ruleactiontype)_ |  false  | DefaultAction defines the default action to be taken if no rules match.<br />If not specified, the default action is Deny. |
+| `rules` | _[AuthorizationRule](#authorizationrule) array_ |  false  | Rules defines a list of authorization rules.<br />These rules are evaluated in order, the first matching rule will be applied,<br />and the rest will be skipped.<br /><br />For example, if there are two rules: the first rule allows the request<br />and the second rule denies it, when a request matches both rules, it will be allowed. |
+| `defaultAction` | _[AuthorizationAction](#authorizationaction)_ |  false  | DefaultAction defines the default action to be taken if no rules match.<br />If not specified, the default action is Deny. |
+
+
+#### AuthorizationAction
+
+_Underlying type:_ _string_
+
+AuthorizationAction defines the action to be taken if a rule matches.
+
+_Appears in:_
+- [Authorization](#authorization)
+- [AuthorizationRule](#authorizationrule)
+
+| Value | Description |
+| ----- | ----------- |
+| `Allow` | AuthorizationActionAllow is the action to allow the request.<br /> | 
+| `Deny` | AuthorizationActionDeny is the action to deny the request.<br /> | 
+
+
+#### AuthorizationRule
+
+
+
+AuthorizationRule defines a single authorization rule.
+
+_Appears in:_
+- [Authorization](#authorization)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `name` | _string_ |  false  | Name is a user-friendly name for the rule. It's just for display purposes. |
+| `action` | _[AuthorizationAction](#authorizationaction)_ |  true  | Action defines the action to be taken if the rule matches. |
+| `principal` | _[Principal](#principal)_ |  true  | Principal specifies the client identity of a request. |
 
 
 #### BackOffPolicy
@@ -284,6 +319,8 @@ BackendRef defines how an ObjectReference that is specific to BackendRef.
 _Appears in:_
 - [ALSEnvoyProxyAccessLog](#alsenvoyproxyaccesslog)
 - [ExtProc](#extproc)
+- [GRPCExtAuthService](#grpcextauthservice)
+- [HTTPExtAuthService](#httpextauthservice)
 - [OpenTelemetryEnvoyProxyAccessLog](#opentelemetryenvoyproxyaccesslog)
 - [ProxyOpenTelemetrySink](#proxyopentelemetrysink)
 - [TracingProvider](#tracingprovider)
@@ -449,6 +486,18 @@ _Appears in:_
 | `Replace` | Replace replaces the default bootstrap with the provided one.<br /> | 
 
 
+#### CIDR
+
+_Underlying type:_ _string_
+
+CIDR defines a CIDR Address range.
+A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64".
+
+_Appears in:_
+- [Principal](#principal)
+
+
+
 #### CORS
 
 
@@ -855,6 +904,7 @@ _Appears in:_
 | `envoy.filters.http.wasm` | EnvoyFilterWasm defines the Envoy HTTP WebAssembly filter.<br /> | 
 | `envoy.filters.http.local_ratelimit` | EnvoyFilterLocalRateLimit defines the Envoy HTTP local rate limit filter.<br /> | 
 | `envoy.filters.http.ratelimit` | EnvoyFilterRateLimit defines the Envoy HTTP rate limit filter.<br /> | 
+| `envoy.filters.http.rbac` | EnvoyFilterRBAC defines the Envoy RBAC filter.<br /> | 
 | `envoy.filters.http.router` | EnvoyFilterRouter defines the Envoy HTTP router filter.<br /> | 
 
 
@@ -1306,7 +1356,7 @@ _Appears in:_
 | `extraArgs` | _string array_ |  false  | ExtraArgs defines additional command line options that are provided to Envoy.<br />More info: https://www.envoyproxy.io/docs/envoy/latest/operations/cli#command-line-options<br />Note: some command line options are used internally(e.g. --log-level) so they cannot be provided here. |
 | `mergeGateways` | _boolean_ |  false  | MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.<br />Setting this field to true would merge all Gateway Listeners under the parent Gateway Class.<br />This means that the port, protocol and hostname tuple must be unique for every listener.<br />If a duplicate listener is detected, the newer listener (based on timestamp) will be rejected and its status will be updated with a "Accepted=False" condition. |
 | `shutdown` | _[ShutdownConfig](#shutdownconfig)_ |  false  | Shutdown defines configuration for graceful envoy shutdown process. |
-| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_authn<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.router |
+| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_authn<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.rbac<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.router |
 | `backendTLS` | _[BackendTLSConfig](#backendtlsconfig)_ |  false  | BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends.<br />These settings are applied on backends for which TLS policies are specified. |
 
 
@@ -1589,7 +1639,8 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `backendRef` | _[BackendObjectReference](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.BackendObjectReference)_ |  true  | BackendRef references a Kubernetes object that represents the<br />backend server to which the authorization request will be sent.<br />Only service Kind is supported for now. |
+| `backendRef` | _[BackendObjectReference](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.BackendObjectReference)_ |  true  | BackendRef references a Kubernetes object that represents the<br />backend server to which the authorization request will be sent.<br />Only service Kind is supported for now.<br />Deprecated: Use BackendRefs instead. |
+| `backendRefs` | _[BackendRef](#backendref) array_ |  false  | BackendRefs references a Kubernetes object that represents the<br />backend server to which the authorization request will be sent.<br />Only service Kind is supported for now. |
 
 
 #### Gateway
@@ -1751,7 +1802,8 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `backendRef` | _[BackendObjectReference](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.BackendObjectReference)_ |  true  | BackendRef references a Kubernetes object that represents the<br />backend server to which the authorization request will be sent.<br />Only service Kind is supported for now. |
+| `backendRef` | _[BackendObjectReference](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.BackendObjectReference)_ |  true  | BackendRef references a Kubernetes object that represents the<br />backend server to which the authorization request will be sent.<br />Only service Kind is supported for now.<br />Deprecated: Use BackendRefs instead. |
+| `backendRefs` | _[BackendRef](#backendref) array_ |  false  | BackendRefs references a Kubernetes object that represents the<br />backend server to which the authorization request will be sent.<br />Only service Kind is supported for now. |
 | `path` | _string_ |  true  | Path is the path of the HTTP External Authorization service.<br />If path is specified, the authorization request will be sent to that path,<br />or else the authorization request will be sent to the root path. |
 | `headersToBackend` | _string array_ |  false  | HeadersToBackend are the authorization response headers that will be added<br />to the original client request before sending it to the backend server.<br />Note that coexisting headers will be overridden.<br />If not specified, no authorization response headers will be added to the<br />original client request. |
 
@@ -2446,13 +2498,16 @@ _Appears in:_
 
 
 Principal specifies the client identity of a request.
+A client identity can be a client IP, a JWT claim, username from the Authorization header,
+or any other identity that can be extracted from a custom header.
+Currently, only the client IP is supported.
 
 _Appears in:_
-- [Rule](#rule)
+- [AuthorizationRule](#authorizationrule)
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `clientCIDR` | _string array_ |  true  | ClientCIDR is the IP CIDR range of the client.<br />Valid examples are "192.168.1.0/24" or "2001:db8::/64"<br /><br />By default, the client IP is inferred from the x-forwarder-for header and proxy protocol.<br />You can use the `EnableProxyProtocol` and `ClientIPDetection` options in<br />the `ClientTrafficPolicy` to configure how the client IP is detected. |
+| `clientCIDRs` | _[CIDR](#cidr) array_ |  true  | ClientCIDRs are the IP CIDR ranges of the client.<br />Valid examples are "192.168.1.0/24" or "2001:db8::/64"<br /><br />The client IP is inferred from the X-Forwarded-For header, a custom header,<br />or the proxy protocol.<br />You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in<br />the `ClientTrafficPolicy` to configure how the client IP is detected. |
 
 
 #### ProcessingModeOptions
@@ -3086,37 +3141,6 @@ _Appears in:_
 | `httpStatusCodes` | _[HTTPStatus](#httpstatus) array_ |  false  | HttpStatusCodes specifies the http status codes to be retried.<br />The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. |
 
 
-#### Rule
-
-
-
-Rule defines the single authorization rule.
-
-_Appears in:_
-- [Authorization](#authorization)
-
-| Field | Type | Required | Description |
-| ---   | ---  | ---      | ---         |
-| `action` | _[RuleActionType](#ruleactiontype)_ |  true  | Action defines the action to be taken if the rule matches. |
-| `principal` | _[Principal](#principal)_ |  true  | Principal specifies the client identity of a request. |
-
-
-#### RuleActionType
-
-_Underlying type:_ _string_
-
-RuleActionType specifies the types of authorization rule action.
-
-_Appears in:_
-- [Authorization](#authorization)
-- [Rule](#rule)
-
-| Value | Description |
-| ----- | ----------- |
-| `Allow` | Allow is the action to allow the request.<br /> | 
-| `Deny` | Deny is the action to deny the request.<br /> | 
-
-
 #### SecurityPolicy
 
 
diff --git a/site/content/en/latest/install/gateway-helm-api.md b/site/content/en/latest/install/gateway-helm-api.md
index bb5a0f527..7dbf2a58a 100644
--- a/site/content/en/latest/install/gateway-helm-api.md
+++ b/site/content/en/latest/install/gateway-helm-api.md
@@ -55,8 +55,10 @@ The Helm chart for Envoy Gateway
 | deployment.ports[2].targetPort | int | `19001` |  |
 | deployment.replicas | int | `1` |  |
 | global.images.envoyGateway.image | string | `nil` |  |
-| global.images.envoyGateway.imagePullSecrets | list | `[]` |  |
 | global.images.envoyGateway.pullPolicy | string | `nil` |  |
+| global.images.envoyGateway.pullSecrets | list | `[]` |  |
 | global.images.ratelimit.image | string | `"docker.io/envoyproxy/ratelimit:master"` |  |
+| global.images.ratelimit.pullPolicy | string | `"IfNotPresent"` |  |
+| global.images.ratelimit.pullSecrets | list | `[]` |  |
 | kubernetesClusterDomain | string | `"cluster.local"` |  |
 
diff --git a/site/content/en/latest/tasks/observability/gateway-exported-metrics.md b/site/content/en/latest/tasks/observability/gateway-exported-metrics.md
index a06f83aee..9878775e1 100644
--- a/site/content/en/latest/tasks/observability/gateway-exported-metrics.md
+++ b/site/content/en/latest/tasks/observability/gateway-exported-metrics.md
@@ -71,7 +71,28 @@ Envoy Gateway collects the following metrics in xDS Server:
 | `xds_stream_duration_seconds`       | How long a xds stream takes to finish.                        |
 | `xds_delta_stream_duration_seconds` | How long a xds delta stream takes to finish.                  |
 
-For xDS snapshot cache update and xDS stream connection status, each metric includes `nodeID` label to identify the connection peer.
-For xDS stream connection status, each metric also includes `streamID` label to identify the connection stream.
+- For xDS snapshot cache update and xDS stream connection status, each metric includes `nodeID` label to identify the connection peer.
+- For xDS stream connection status, each metric also includes `streamID` label to identify the connection stream.
+
+## Infrastructure Manager
+
+Envoy Gateway monitors the `apply` (`create` or `update`) and `delete` operations in Infrastructure Manager.
+
+Envoy Gateway collects the following metrics in Infrastructure Manager:
+
+| Name                               | Description                                             |
+|------------------------------------|---------------------------------------------------------|
+| `resource_apply_total`             | Total number of applied resources.                      |
+| `resource_apply_failed`            | Number of applied resources that failed.                |
+| `resource_apply_success`           | Number of applied resources that succeed.               |
+| `resource_apply_duration_seconds`  | How long in seconds a resource be applied successfully. |
+| `resource_delete_total`            | Total number of deleted resources.                      |
+| `resource_delete_failed`           | Number of deleted resources that failed.                |
+| `resource_delete_success`          | Number of deleted resources that succeed.               |
+| `resource_delete_duration_seconds` | How long in seconds a resource be deleted successfully. |
+
+Each metric includes the `kind` label to identify the corresponding resources being applied or deleted by Infrastructure Manager.
+
+Metrics may also include `name` and `namespace` label to identify the name and namespace of corresponding Infrastructure Manager.
 
 [prom-format]: https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format
diff --git a/test/cel-validation/securitypolicy_test.go b/test/cel-validation/securitypolicy_test.go
index 984903cf7..9c2466565 100644
--- a/test/cel-validation/securitypolicy_test.go
+++ b/test/cel-validation/securitypolicy_test.go
@@ -354,7 +354,7 @@ func TestSecurityPolicyTarget(t *testing.T) {
 				sp.Spec = egv1a1.SecurityPolicySpec{
 					ExtAuth: &egv1a1.ExtAuth{
 						GRPC: &egv1a1.GRPCExtAuthService{
-							BackendRef: gwapiv1.BackendObjectReference{
+							BackendRef: &gwapiv1.BackendObjectReference{
 								Name: "grpc-auth-service",
 								Port: ptr.To(gwapiv1.PortNumber(80)),
 							},
@@ -371,13 +371,59 @@ func TestSecurityPolicyTarget(t *testing.T) {
 			},
 			wantErrors: []string{},
 		},
+		{
+			desc: "GRPC external auth service with backendRefs",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					ExtAuth: &egv1a1.ExtAuth{
+						GRPC: &egv1a1.GRPCExtAuthService{
+							BackendRefs: []egv1a1.BackendRef{
+								{
+									BackendObjectReference: gwapiv1.BackendObjectReference{
+										Name: "grpc-auth-service",
+										Kind: ptr.To(gwapiv1a2.Kind("Service")),
+										Port: ptr.To(gwapiv1.PortNumber(80)),
+									},
+								},
+							},
+						},
+					},
+					TargetRef: gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+						LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+							Group: "gateway.networking.k8s.io",
+							Kind:  "Gateway",
+							Name:  "eg",
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
+		{
+			desc: "empty GRPC external auth service",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					ExtAuth: &egv1a1.ExtAuth{
+						GRPC: &egv1a1.GRPCExtAuthService{},
+					},
+					TargetRef: gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+						LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+							Group: "gateway.networking.k8s.io",
+							Kind:  "Gateway",
+							Name:  "eg",
+						},
+					},
+				}
+			},
+			wantErrors: []string{" backendRef or backendRefs needs to be set"},
+		},
 		{
 			desc: "HTTP external auth service",
 			mutate: func(sp *egv1a1.SecurityPolicy) {
 				sp.Spec = egv1a1.SecurityPolicySpec{
 					ExtAuth: &egv1a1.ExtAuth{
 						HTTP: &egv1a1.HTTPExtAuthService{
-							BackendRef: gwapiv1.BackendObjectReference{
+							BackendRef: &gwapiv1.BackendObjectReference{
 								Name: "http-auth-service",
 								Port: ptr.To(gwapiv1.PortNumber(15001)),
 							},
@@ -394,6 +440,34 @@ func TestSecurityPolicyTarget(t *testing.T) {
 			},
 			wantErrors: []string{},
 		},
+		{
+			desc: "HTTP external auth service with backendRefs",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					ExtAuth: &egv1a1.ExtAuth{
+						HTTP: &egv1a1.HTTPExtAuthService{
+							BackendRefs: []egv1a1.BackendRef{
+								{
+									BackendObjectReference: gwapiv1.BackendObjectReference{
+										Name: "grpc-auth-service",
+										Kind: ptr.To(gwapiv1a2.Kind("Service")),
+										Port: ptr.To(gwapiv1.PortNumber(80)),
+									},
+								},
+							},
+						},
+					},
+					TargetRef: gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+						LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+							Group: "gateway.networking.k8s.io",
+							Kind:  "Gateway",
+							Name:  "eg",
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
 		{
 			desc: "no extAuth",
 			mutate: func(sp *egv1a1.SecurityPolicy) {
@@ -418,13 +492,13 @@ func TestSecurityPolicyTarget(t *testing.T) {
 				sp.Spec = egv1a1.SecurityPolicySpec{
 					ExtAuth: &egv1a1.ExtAuth{
 						GRPC: &egv1a1.GRPCExtAuthService{
-							BackendRef: gwapiv1.BackendObjectReference{
+							BackendRef: &gwapiv1.BackendObjectReference{
 								Name: "grpc-auth-service",
 								Port: ptr.To(gwapiv1.PortNumber(80)),
 							},
 						},
 						HTTP: &egv1a1.HTTPExtAuthService{
-							BackendRef: gwapiv1.BackendObjectReference{
+							BackendRef: &gwapiv1.BackendObjectReference{
 								Name: "http-auth-service",
 								Port: ptr.To(gwapiv1.PortNumber(15001)),
 							},
@@ -449,7 +523,7 @@ func TestSecurityPolicyTarget(t *testing.T) {
 				sp.Spec = egv1a1.SecurityPolicySpec{
 					ExtAuth: &egv1a1.ExtAuth{
 						HTTP: &egv1a1.HTTPExtAuthService{
-							BackendRef: gwapiv1.BackendObjectReference{
+							BackendRef: &gwapiv1.BackendObjectReference{
 								Group: ptr.To(gwapiv1.Group("unsupported")),
 								Name:  "http-auth-service",
 								Port:  ptr.To(gwapiv1.PortNumber(15001)),
@@ -475,7 +549,7 @@ func TestSecurityPolicyTarget(t *testing.T) {
 				sp.Spec = egv1a1.SecurityPolicySpec{
 					ExtAuth: &egv1a1.ExtAuth{
 						HTTP: &egv1a1.HTTPExtAuthService{
-							BackendRef: gwapiv1.BackendObjectReference{
+							BackendRef: &gwapiv1.BackendObjectReference{
 								Kind: ptr.To(gwapiv1.Kind("unsupported")),
 								Name: "http-auth-service",
 								Port: ptr.To(gwapiv1.PortNumber(15001)),
@@ -495,13 +569,41 @@ func TestSecurityPolicyTarget(t *testing.T) {
 				"spec.extAuth: Invalid value: \"object\": kind is invalid, only Service (specified by omitting the kind field or setting it to 'Service') is supported",
 			},
 		},
+		{
+			desc: "http extAuth service backendRefs invalid Kind",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					ExtAuth: &egv1a1.ExtAuth{
+						HTTP: &egv1a1.HTTPExtAuthService{
+							BackendRefs: []egv1a1.BackendRef{
+								{
+									BackendObjectReference: gwapiv1.BackendObjectReference{
+										Name: "grpc-auth-service",
+										Kind: ptr.To(gwapiv1a2.Kind("unsupported")),
+										Port: ptr.To(gwapiv1.PortNumber(80)),
+									},
+								},
+							},
+						},
+					},
+					TargetRef: gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+						LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+							Group: "gateway.networking.k8s.io",
+							Kind:  "Gateway",
+							Name:  "eg",
+						},
+					},
+				}
+			},
+			wantErrors: []string{" only support Service kind"},
+		},
 		{
 			desc: "grpc extAuth service invalid Group",
 			mutate: func(sp *egv1a1.SecurityPolicy) {
 				sp.Spec = egv1a1.SecurityPolicySpec{
 					ExtAuth: &egv1a1.ExtAuth{
 						GRPC: &egv1a1.GRPCExtAuthService{
-							BackendRef: gwapiv1.BackendObjectReference{
+							BackendRef: &gwapiv1.BackendObjectReference{
 								Group: ptr.To(gwapiv1.Group("unsupported")),
 								Name:  "http-auth-service",
 								Port:  ptr.To(gwapiv1.PortNumber(15001)),
@@ -527,7 +629,7 @@ func TestSecurityPolicyTarget(t *testing.T) {
 				sp.Spec = egv1a1.SecurityPolicySpec{
 					ExtAuth: &egv1a1.ExtAuth{
 						GRPC: &egv1a1.GRPCExtAuthService{
-							BackendRef: gwapiv1.BackendObjectReference{
+							BackendRef: &gwapiv1.BackendObjectReference{
 								Kind: ptr.To(gwapiv1.Kind("unsupported")),
 								Name: "http-auth-service",
 								Port: ptr.To(gwapiv1.PortNumber(15001)),
@@ -547,6 +649,35 @@ func TestSecurityPolicyTarget(t *testing.T) {
 				"spec.extAuth: Invalid value: \"object\": kind is invalid, only Service (specified by omitting the kind field or setting it to 'Service') is supported",
 			},
 		},
+		{
+			desc: "grpc extAuth service backendRefs invalid Kind",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					ExtAuth: &egv1a1.ExtAuth{
+						GRPC: &egv1a1.GRPCExtAuthService{
+							BackendRefs: []egv1a1.BackendRef{
+								{
+									BackendObjectReference: gwapiv1.BackendObjectReference{
+										Name: "grpc-auth-service",
+										Kind: ptr.To(gwapiv1a2.Kind("unsupported")),
+										Port: ptr.To(gwapiv1.PortNumber(80)),
+									},
+								},
+							},
+						},
+					},
+					TargetRef: gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+						LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+							Group: "gateway.networking.k8s.io",
+							Kind:  "Gateway",
+							Name:  "eg",
+						},
+					},
+				}
+			},
+			wantErrors: []string{" only support Service kind"},
+		},
+
 		// JWT
 		{
 			desc: "valid jwt",
diff --git a/test/e2e/testdata/authorization-client-ip.yaml b/test/e2e/testdata/authorization-client-ip.yaml
new file mode 100644
index 000000000..08ead703c
--- /dev/null
+++ b/test/e2e/testdata/authorization-client-ip.yaml
@@ -0,0 +1,98 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-with-authorization-client-ip-1
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - matches:
+    - path:
+        type: Exact
+        value: /protected1
+    backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-with-authorization-client-ip-2
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - matches:
+    - path:
+        type: Exact
+        value: /protected2
+    backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: authorization-client-ip-1
+  namespace: gateway-conformance-infra
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-with-authorization-client-ip-1
+  authorization:
+    defaultAction: Allow
+    rules:
+    - name: "deny-location-1"  # First matching rule is applied, so 192.168.1.0/24 will be denied
+      action: Deny
+      principal:
+        clientCIDRs:
+        - 192.168.1.0/24
+    - name: "allow-location-1"
+      action: Allow
+      principal:
+        clientCIDRs:
+        - 192.168.1.0/24
+        - 192.168.2.0/24     # First matching rule is applied, so 12.168.2.0/24 will be allowed
+    - name: "deny-location-2"
+      action: Allow
+      principal:
+        clientCIDRs:
+        - 192.168.2.0/24
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: authorization-client-ip-2
+  namespace: gateway-conformance-infra
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-with-authorization-client-ip-2
+  authorization:
+    defaultAction: Deny
+    rules:
+    - action: Allow
+      principal:
+        clientCIDRs:
+        - 10.0.1.0/24
+        - 10.0.2.0/24
+---
+# This is a client traffic policy that enables client IP detection using the XFF header.
+# So, the client IP can be detected from the XFF header and used for authorization.
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: ClientTrafficPolicy
+metadata:
+  name: enable-client-ip-detection
+  namespace: gateway-conformance-infra
+spec:
+  clientIPDetection:
+    xForwardedFor:
+      numTrustedHops: 1
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: Gateway
+    name: same-namespace
diff --git a/test/e2e/testdata/authorization-default-action.yaml b/test/e2e/testdata/authorization-default-action.yaml
new file mode 100644
index 000000000..bc35b2ba8
--- /dev/null
+++ b/test/e2e/testdata/authorization-default-action.yaml
@@ -0,0 +1,58 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-with-authorization-empty
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - matches:
+    - path:
+        type: Exact
+        value: /empty-authorization
+    backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-with-authorization-allow-all
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - matches:
+    - path:
+        type: Exact
+        value: /allow-all
+    backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: authorization-empty
+  namespace: gateway-conformance-infra
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-with-authorization-empty
+  authorization: {}  # An empty authorization policy means deny all since default action is deny
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: authorization-allow-all
+  namespace: gateway-conformance-infra
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-with-authorization-allow-all
+  authorization:  # Allow all since default action is allow and no rules are defined
+    defaultAction: Allow
diff --git a/test/e2e/testdata/basic-auth.yaml b/test/e2e/testdata/basic-auth.yaml
index 036bb788e..db99cf2cd 100644
--- a/test/e2e/testdata/basic-auth.yaml
+++ b/test/e2e/testdata/basic-auth.yaml
@@ -74,7 +74,6 @@ spec:
     group: gateway.networking.k8s.io
     kind: HTTPRoute
     name: http-with-basic-auth-2
-    namespace: gateway-conformance-infra
   basicAuth:
     users:
       name: "basic-auth-users-secret-2"
diff --git a/test/e2e/testdata/ext-auth-grpc-securitypolicy.yaml b/test/e2e/testdata/ext-auth-grpc-securitypolicy.yaml
index 840977aa6..3f40fc583 100644
--- a/test/e2e/testdata/ext-auth-grpc-securitypolicy.yaml
+++ b/test/e2e/testdata/ext-auth-grpc-securitypolicy.yaml
@@ -47,8 +47,8 @@ spec:
     name: http-with-ext-auth
   extAuth:
     grpc:
-      backendRef:
-        name: grpc-ext-auth
+      backendRefs:
+      - name: grpc-ext-auth
         namespace: gateway-conformance-infra
         port: 9002
 ---
diff --git a/test/e2e/testdata/ext-auth-http-securitypolicy.yaml b/test/e2e/testdata/ext-auth-http-securitypolicy.yaml
index 1ebcc9e34..e77dbde80 100644
--- a/test/e2e/testdata/ext-auth-http-securitypolicy.yaml
+++ b/test/e2e/testdata/ext-auth-http-securitypolicy.yaml
@@ -47,8 +47,8 @@ spec:
     name: http-with-ext-auth
   extAuth:
     http:
-      backendRef:
-        name: http-ext-auth
+      backendRefs:
+      - name: http-ext-auth
         namespace: gateway-conformance-infra
         port: 9002
       headersToBackend: ["x-current-user"]
diff --git a/test/e2e/tests/authorization-client-ip.go b/test/e2e/tests/authorization-client-ip.go
new file mode 100644
index 000000000..0a0d2056a
--- /dev/null
+++ b/test/e2e/tests/authorization-client-ip.go
@@ -0,0 +1,192 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+//go:build e2e
+// +build e2e
+
+package tests
+
+import (
+	"testing"
+
+	"k8s.io/apimachinery/pkg/types"
+	gwv1 "sigs.k8s.io/gateway-api/apis/v1"
+	gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+
+	"github.com/envoyproxy/gateway/internal/gatewayapi"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, AuthorizationClientIPTest)
+}
+
+var AuthorizationClientIPTest = suite.ConformanceTest{
+	ShortName:   "Authorization with client IP",
+	Description: "Authorization with client IP Allow/Deny list",
+	Manifests:   []string{"testdata/authorization-client-ip.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-infra"
+		route1NN := types.NamespacedName{Name: "http-with-authorization-client-ip-1", Namespace: ns}
+		route2NN := types.NamespacedName{Name: "http-with-authorization-client-ip-2", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), route1NN, route2NN)
+
+		ancestorRef := gwv1a2.ParentReference{
+			Group:     gatewayapi.GroupPtr(gwv1.GroupName),
+			Kind:      gatewayapi.KindPtr(gatewayapi.KindGateway),
+			Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+			Name:      gwv1.ObjectName(gwNN.Name),
+		}
+		SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-client-ip-1", Namespace: ns}, suite.ControllerName, ancestorRef)
+		SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-client-ip-2", Namespace: ns}, suite.ControllerName, ancestorRef)
+
+		t.Run("first route-denied IP", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/protected1",
+					Headers: map[string]string{
+						"X-Forwarded-For": "192.168.1.1", // in the denied list
+					},
+				},
+				Response: http.Response{
+					StatusCode: 403,
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+
+		t.Run("first route-allowed IP", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/protected1",
+					Headers: map[string]string{
+						"X-Forwarded-For": "192.168.2.1", // in the allowed list
+					},
+				},
+				ExpectedRequest: &http.ExpectedRequest{
+					Request: http.Request{
+						Path:    "/protected1",
+						Headers: nil, // don't check headers since Envoy will append the client IP to the X-Forwarded-For header
+					},
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+
+		t.Run("first route-default action: allow", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/protected1",
+					Headers: map[string]string{
+						"X-Forwarded-For": "192.168.3.1", // not in the denied list
+					},
+				},
+				ExpectedRequest: &http.ExpectedRequest{
+					Request: http.Request{
+						Path:    "/protected1",
+						Headers: nil, // don't check headers since Envoy will append the client IP to the X-Forwarded-For header
+					},
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+
+		// Test the second route
+		t.Run("second route-allowed IP", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/protected2",
+					Headers: map[string]string{
+						"X-Forwarded-For": "10.0.1.1", // in the allowed list
+					},
+				},
+				ExpectedRequest: &http.ExpectedRequest{
+					Request: http.Request{
+						Path:    "/protected2",
+						Headers: nil, // don't check headers since Envoy will append the client IP to the X-Forwarded-For header
+					},
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+
+		t.Run("second route-default action: deny", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/protected2",
+					Headers: map[string]string{
+						"X-Forwarded-For": "192.168.3.1", // not in the allowed list
+					},
+				},
+				Response: http.Response{
+					StatusCode: 403,
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+	},
+}
diff --git a/test/e2e/tests/authorization-default-action.go b/test/e2e/tests/authorization-default-action.go
new file mode 100644
index 000000000..1a58c32e3
--- /dev/null
+++ b/test/e2e/tests/authorization-default-action.go
@@ -0,0 +1,92 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+//go:build e2e
+// +build e2e
+
+package tests
+
+import (
+	"testing"
+
+	"k8s.io/apimachinery/pkg/types"
+	gwv1 "sigs.k8s.io/gateway-api/apis/v1"
+	gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+
+	"github.com/envoyproxy/gateway/internal/gatewayapi"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, AuthorizationDefaultActionTest)
+}
+
+var AuthorizationDefaultActionTest = suite.ConformanceTest{
+	ShortName:   "Authorization with default actions",
+	Description: "Authorization with default actions",
+	Manifests:   []string{"testdata/authorization-default-action.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-infra"
+		route1NN := types.NamespacedName{Name: "http-with-authorization-empty", Namespace: ns}
+		route2NN := types.NamespacedName{Name: "http-with-authorization-allow-all", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), route1NN, route2NN)
+
+		ancestorRef := gwv1a2.ParentReference{
+			Group:     gatewayapi.GroupPtr(gwv1.GroupName),
+			Kind:      gatewayapi.KindPtr(gatewayapi.KindGateway),
+			Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+			Name:      gwv1.ObjectName(gwNN.Name),
+		}
+		SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-empty", Namespace: ns}, suite.ControllerName, ancestorRef)
+		SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-allow-all", Namespace: ns}, suite.ControllerName, ancestorRef)
+
+		t.Run("Empty Authorization should deny all traffic", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/empty-authorization",
+				},
+				Response: http.Response{
+					StatusCode: 403,
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+
+		t.Run("Authorization with empty rules and Allow default action should allow all traffic", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/allow-all",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+	},
+}
diff --git a/test/helm/default-config.out.yaml b/test/helm/default-config.out.yaml
index ba4840ee0..25326c677 100644
--- a/test/helm/default-config.out.yaml
+++ b/test/helm/default-config.out.yaml
@@ -38,6 +38,15 @@ data:
         rateLimitDeployment:
           container:
             image: docker.io/envoyproxy/ratelimit:master
+          patch:
+            type: StrategicMerge
+            value:
+              spec:
+                template:
+                  spec:
+                    containers:
+                    - imagePullPolicy: IfNotPresent
+                      name: envoy-ratelimit
         shutdownManager:
           image: docker.io/envoyproxy/gateway-dev:latest
       type: Kubernetes
diff --git a/test/helm/deployment-images-config.out.yaml b/test/helm/deployment-images-config.out.yaml
index 78256bf94..b13dc2261 100644
--- a/test/helm/deployment-images-config.out.yaml
+++ b/test/helm/deployment-images-config.out.yaml
@@ -38,6 +38,15 @@ data:
         rateLimitDeployment:
           container:
             image: docker.io/envoyproxy/ratelimit:master
+          patch:
+            type: StrategicMerge
+            value:
+              spec:
+                template:
+                  spec:
+                    containers:
+                    - imagePullPolicy: IfNotPresent
+                      name: envoy-ratelimit
         shutdownManager:
           image: private-hub/envoyproxy/gateway:abcdef12
       type: Kubernetes
diff --git a/test/helm/envoy-gateway-config.out.yaml b/test/helm/envoy-gateway-config.out.yaml
index a2b051ea2..612306e1f 100644
--- a/test/helm/envoy-gateway-config.out.yaml
+++ b/test/helm/envoy-gateway-config.out.yaml
@@ -40,6 +40,15 @@ data:
         rateLimitDeployment:
           container:
             image: private-hub/envoyproxy/ratelimit:master
+          patch:
+            type: StrategicMerge
+            value:
+              spec:
+                template:
+                  spec:
+                    containers:
+                    - imagePullPolicy: IfNotPresent
+                      name: envoy-ratelimit
         shutdownManager:
           image: docker.io/envoyproxy/gateway-dev:latest
       type: Kubernetes
diff --git a/test/helm/global-images-config.in.yaml b/test/helm/global-images-config.in.yaml
index bbcb3a202..ee8250cbe 100644
--- a/test/helm/global-images-config.in.yaml
+++ b/test/helm/global-images-config.in.yaml
@@ -3,8 +3,12 @@ global:
     envoyGateway:
       image: "private-hub/envoyproxy/gateway:abcdef12"
       pullPolicy: Always
-      imagePullSecrets:
+      pullSecrets:
         - name: "secret1"
         - name: "secret2"
     ratelimit:
       image: "private-hub/envoyproxy/ratelimit:test"
+      pullPolicy: Always
+      pullSecrets:
+        - name: "secret3"
+        - name: "secret4"
diff --git a/test/helm/global-images-config.out.yaml b/test/helm/global-images-config.out.yaml
index 0d417c210..89788eaef 100644
--- a/test/helm/global-images-config.out.yaml
+++ b/test/helm/global-images-config.out.yaml
@@ -38,6 +38,19 @@ data:
         rateLimitDeployment:
           container:
             image: private-hub/envoyproxy/ratelimit:test
+          patch:
+            type: StrategicMerge
+            value:
+              spec:
+                template:
+                  spec:
+                    containers:
+                    - imagePullPolicy: Always
+                      name: envoy-ratelimit
+          pod:
+            imagePullSecrets:
+            - name: secret3
+            - name: secret4
         shutdownManager:
           image: private-hub/envoyproxy/gateway:abcdef12
       type: Kubernetes