Repository: envoyproxy/gateway
Pull request #4259 (2024-09-16 21:56:53)

api: custom error response
Relates to https://github.com/envoyproxy/gateway/issues/1400


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/4259?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 65.67%. Comparing base [(`84e348f`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/84e348f3b90c9f0d7c690b38e8d355eb9b30d7ba?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`61511aa`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/61511aa51d7fbb84d6c664b0184bbfdd61ce0f2e?dropdown=coverage&el=desc&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    #4259      +/-   ##
==========================================
+ Coverage   65.63%   65.67%   +0.03%     
==========================================
  Files         197      197              
  Lines       23540    23540              
==========================================
+ Hits        15451    15460       +9     
+ Misses       6978     6971       -7     
+ Partials     1111     1109       -2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/4259?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).

zirain: instead of using an range match:
```
match:
      status:
        type: range
        range:
          min: 400
          max: 499
```

what about use CEL, and it will looks like `http.statusCode >= 400 || http.statusCode <= 499`?
arkodg: thanks for the review and suggestions @AliceProxy & @zirain ! will try and incorporate these changes in 
arkodg: > instead of using an range match:
> 
> ```
> match:
>       status:
>         type: range
>         range:
>           min: 400
>           max: 499
> ```
> 
> what about use CEL, and it will looks like `http.statusCode >= 400 || http.statusCode <= 499`?

CEL is flexible and powerful as you've shown in the example, I fear we'll end up losing on simplicity 
zirain: > > instead of using an range match:
> > ```
> > match:
> >       status:
> >         type: range
> >         range:
> >           min: 400
> >           max: 499
> > ```
> > 
> > 
> >     
> >       
> >     
> > 
> >       
> >     
> > 
> >     
> >   
> > what about use CEL, and it will looks like `http.statusCode >= 400 || http.statusCode <= 499`?
> 
> CEL is flexible and powerful as you've shown in the example, I fear we'll end up losing on simplicity

let's use range right now.
arkodg: > I like where you're going with this API. Thoughts on the following? Not all the examples in my example below need to be added to the first iteration, just a suggestion around what we might eventually want to support.
> 
> ```yaml
> responseOverrides:
>   - match:
>       status:
>         type: value
>         value: 404
>     body:
>     ...
>   - match:
>       status:
>         type: range
>         range:
>           min: 400
>           max: 499
>       headers:
>       - name: "x-foo"
>         value: "bar" # no value means any value, same as providing "*"
>     headers: # no headers means don't change the headers
>     - type: "set"
>       name: "x-foo"
>       value: "baz"
>     - type: "append"
>       name: "x-bar"
>       value: "baz"
>     - type: "remove"
>       name: "x-bar"
>     body: # no balue means don't change the body
>     ...
> ```
> 
> I think we want to give people an easy way to change responses (and maybe headers) under certain responses from the upstream. One open question with the above is how we would want to handle the case where you supply a range then an individual specific status such as in my example. I think intuitively I would expect the exact `404` to take precedence over the `400-499` range, but curious what others think.
> 
> I think that pattern of having a generic fallback page for general 4xx/5xx errors will be very common and also wanting specific pages for more well-known cases like `404`/`500` with a little extra personality/info like many other large sites do.

@AliceProxy id say 1st match wins

Patch:
diff --git a/VERSION b/VERSION
index 56130fb3a..0f1acbd56 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-v1.1.1
+v1.1.2
diff --git a/api/v1alpha1/accesslogging_types.go b/api/v1alpha1/accesslogging_types.go
index 3a3628e98..edc19e975 100644
--- a/api/v1alpha1/accesslogging_types.go
+++ b/api/v1alpha1/accesslogging_types.go
@@ -30,8 +30,30 @@ type ProxyAccessLogSetting struct {
 	// +kubebuilder:validation:MinItems=1
 	// +kubebuilder:validation:MaxItems=50
 	Sinks []ProxyAccessLogSink `json:"sinks"`
+	// Type defines the component emitting the accesslog, such as Listener and Route.
+	// If type not defined, the setting would apply to:
+	// (1) All Routes.
+	// (2) Listeners if and only if Envoy does not find a matching route for a request.
+	// If type is defined, the accesslog settings would apply to the relevant component (as-is).
+	// +kubebuilder:validation:Enum=Listener;Route
+	// +optional
+	// +notImplementedHide
+	Type *ProxyAccessLogType `json:"type,omitempty"`
 }
 
+type ProxyAccessLogType string
+
+const (
+	// ProxyAccessLogTypeListener defines the accesslog for Listeners.
+	// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/listener/v3/listener.proto#envoy-v3-api-field-config-listener-v3-listener-access-log
+	ProxyAccessLogTypeListener ProxyAccessLogType = "Listener"
+	// ProxyAccessLogTypeRoute defines the accesslog for HTTP, GRPC, UDP and TCP Routes.
+	// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto#envoy-v3-api-field-extensions-filters-udp-udp-proxy-v3-udpproxyconfig-access-log
+	// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto#envoy-v3-api-field-extensions-filters-network-tcp-proxy-v3-tcpproxy-access-log
+	// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-access-log
+	ProxyAccessLogTypeRoute ProxyAccessLogType = "Route"
+)
+
 type ProxyAccessLogFormatType string
 
 const (
diff --git a/api/v1alpha1/authorization_types.go b/api/v1alpha1/authorization_types.go
index 2851cecf6..56e8d44e1 100644
--- a/api/v1alpha1/authorization_types.go
+++ b/api/v1alpha1/authorization_types.go
@@ -28,8 +28,11 @@ type Authorization struct {
 // AuthorizationRule defines a single authorization rule.
 type AuthorizationRule struct {
 	// Name is a user-friendly name for the rule.
-	// If not specified, Envoy Gateway will generate a unique name for the rule.n
+	// If not specified, Envoy Gateway will generate a unique name for the rule.
+	//
 	// +optional
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
 	Name *string `json:"name,omitempty"`
 
 	// Action defines the action to be taken if the rule matches.
@@ -45,7 +48,8 @@ type AuthorizationRule struct {
 // 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.
+
+// If there are multiple principal types, all principals must match for the rule to match.
 //
 // +kubebuilder:validation:XValidation:rule="(has(self.clientCIDRs) || has(self.jwt))",message="at least one of clientCIDRs or jwt must be specified"
 type Principal struct {
@@ -60,7 +64,8 @@ type Principal struct {
 	// You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
 	// the `ClientTrafficPolicy` to configure how the client IP is detected.
 	// +optional
-	ClientCIDRs []CIDR `json:"clientCIDRs"`
+	// +kubebuilder:validation:MinItems=1
+	ClientCIDRs []CIDR `json:"clientCIDRs,omitempty"`
 
 	// JWT authorize the request based on the JWT claims and scopes.
 	// Note: in order to use JWT claims for authorization, you must configure the
@@ -76,12 +81,23 @@ type Principal struct {
 //
 // +kubebuilder:validation:XValidation:rule="(has(self.claims) || has(self.scopes))",message="at least one of claims or scopes must be specified"
 type JWTPrincipal struct {
+	// Provider is the name of the JWT provider that used to verify the JWT token.
+	// In order to use JWT claims for authorization, you must configure the JWT
+	// authentication with the same provider in the same `SecurityPolicy`.
+	//
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
+	Provider string `json:"provider"`
+
 	// Claims are the claims in a JWT token.
 	//
 	// If multiple claims are specified, all claims must match for the rule to match.
 	// For example, if there are two claims: one for the audience and one for the issuer,
 	// the rule will match only if both the audience and the issuer match.
+	//
 	// +optional
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=16
 	Claims []JWTClaim `json:"claims,omitempty"`
 
 	// Scopes are a special type of claim in a JWT token that represents the permissions of the client.
@@ -90,8 +106,11 @@ type JWTPrincipal struct {
 	// as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749#page-23.
 	//
 	// If multiple scopes are specified, all scopes must match for the rule to match.
+	//
 	// +optional
-	Scopes []string `json:"scopes,omitempty"`
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=16
+	Scopes []JWTScope `json:"scopes,omitempty"`
 }
 
 // JWTClaim specifies a claim in a JWT token.
@@ -101,10 +120,14 @@ type JWTClaim struct {
 	// represent the full path to the claim.
 	// For example, if the claim is in the "department" field in the "organization" field,
 	// the name should be "organization.department".
+	//
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
 	Name string `json:"name"`
 
 	// ValueType is the type of the claim value.
 	// Only String and StringArray types are supported for now.
+	//
 	// +kubebuilder:validation:Enum=String;StringArray
 	// +kubebuilder:default=String
 	// +unionDiscriminator
@@ -115,9 +138,16 @@ type JWTClaim struct {
 	// If the claim is a string type, the specified value must match exactly.
 	// If the claim is a string array type, the specified value must match one of the values in the array.
 	// If multiple values are specified, one of the values must match for the rule to match.
+	//
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=16
 	Values []string `json:"values"`
 }
 
+// +kubebuilder:validation:MinLength=1
+// +kubebuilder:validation:MaxLength=253
+type JWTScope string
+
 type JWTClaimValueType string
 
 const (
diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go
index 70a400015..4e6118e70 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -69,6 +69,13 @@ type BackendTrafficPolicySpec struct {
 	// +optional
 	// +notImplementedHide
 	Compression []*Compression `json:"compression,omitempty"`
+
+	// ResponseOverride defines the configuration to override specific responses with a custom one.
+	// If multiple configurations are specified, the first one to match wins.
+	//
+	// +optional
+	// +notImplementedHide
+	ResponseOverride []*ResponseOverride `json:"responseOverride,omitempty"`
 }
 
 // +kubebuilder:object:root=true
diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go
index 56440f096..48e3471c7 100644
--- a/api/v1alpha1/shared_types.go
+++ b/api/v1alpha1/shared_types.go
@@ -609,3 +609,82 @@ type HTTP2Settings struct {
 	// +optional
 	OnInvalidMessage *InvalidMessageAction `json:"onInvalidMessage,omitempty"`
 }
+
+// ResponseOverride defines the configuration to override specific responses with a custom one.
+type ResponseOverride struct {
+	// Match configuration.
+	Match CustomResponseMatch `json:"match"`
+	// Response configuration.
+	Response CustomResponse `json:"response"`
+}
+
+// CustomResponseMatch defines the configuration for matching a user response to return a custom one.
+type CustomResponseMatch struct {
+	// Status code to match on. The match evaluates to true if any of the matches are successful.
+	StatusCode []StatusCodeMatch `json:"statusCode"`
+}
+
+// StatusCodeValueType defines the types of values for the status code match supported by Envoy Gateway.
+// +kubebuilder:validation:Enum=Value;Range
+type StatusCodeValueType string
+
+type StatusCodeMatch struct {
+	// Type is the type of value.
+	//
+	// +kubebuilder:default=Value
+	// +unionDiscriminator
+	Type *StatusCodeValueType `json:"type"`
+
+	// Value contains the value of the status code.
+	//
+	// +optional
+	Value *string `json:"value,omitempty"`
+	// ValueRef contains the contents of the body
+	// specified as a local object reference.
+	// Only a reference to ConfigMap is supported.
+	//
+	// +optional
+	Range *StatusCodeRange `json:"range,omitempty"`
+}
+
+// StatusCodeRange defines the configuration for define a range of status codes.
+type StatusCodeRange struct {
+	// Start of the range, including the start value.
+	Start int `json:"start"`
+	// End of the range, including the end value.
+	End int `json:"end"`
+}
+
+// CustomResponse defines the configuration for returning a custom response.
+type CustomResponse struct {
+	// Content Type of the response. This will be set in the Content-Type header.
+	//
+	// +optional
+	ContentType *string `json:"contentType,omitempty"`
+
+	// Body of the Custom Response
+	Body CustomResponseBody `json:"body"`
+}
+
+// ResponseValueType defines the types of values for the response body supported by Envoy Gateway.
+// +kubebuilder:validation:Enum=Inline;ValueRef
+type ResponseValueType string
+
+// CustomResponseBody
+type CustomResponseBody struct {
+	// Type is the type of method to use to read the body value.
+	//
+	// +unionDiscriminator
+	Type *ResponseValueType `json:"type"`
+
+	// Inline contains the value as an inline string.
+	//
+	// +optional
+	Inline *string `json:"inline,omitempty"`
+	// ValueRef contains the contents of the body
+	// specified as a local object reference.
+	// Only a reference to ConfigMap is supported.
+	//
+	// +optional
+	ValueRef *gwapiv1.LocalObjectReference `json:"valueRef,omitempty"`
+}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index d1c423760..b07978e60 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -556,6 +556,17 @@ func (in *BackendTrafficPolicySpec) DeepCopyInto(out *BackendTrafficPolicySpec)
 			}
 		}
 	}
+	if in.ResponseOverride != nil {
+		in, out := &in.ResponseOverride, &out.ResponseOverride
+		*out = make([]*ResponseOverride, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(ResponseOverride)
+				(*in).DeepCopyInto(*out)
+			}
+		}
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendTrafficPolicySpec.
@@ -1124,6 +1135,79 @@ func (in *CustomHeaderExtensionSettings) DeepCopy() *CustomHeaderExtensionSettin
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CustomResponse) DeepCopyInto(out *CustomResponse) {
+	*out = *in
+	if in.ContentType != nil {
+		in, out := &in.ContentType, &out.ContentType
+		*out = new(string)
+		**out = **in
+	}
+	in.Body.DeepCopyInto(&out.Body)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResponse.
+func (in *CustomResponse) DeepCopy() *CustomResponse {
+	if in == nil {
+		return nil
+	}
+	out := new(CustomResponse)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CustomResponseBody) DeepCopyInto(out *CustomResponseBody) {
+	*out = *in
+	if in.Type != nil {
+		in, out := &in.Type, &out.Type
+		*out = new(ResponseValueType)
+		**out = **in
+	}
+	if in.Inline != nil {
+		in, out := &in.Inline, &out.Inline
+		*out = new(string)
+		**out = **in
+	}
+	if in.ValueRef != nil {
+		in, out := &in.ValueRef, &out.ValueRef
+		*out = new(apisv1.LocalObjectReference)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResponseBody.
+func (in *CustomResponseBody) DeepCopy() *CustomResponseBody {
+	if in == nil {
+		return nil
+	}
+	out := new(CustomResponseBody)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CustomResponseMatch) DeepCopyInto(out *CustomResponseMatch) {
+	*out = *in
+	if in.StatusCode != nil {
+		in, out := &in.StatusCode, &out.StatusCode
+		*out = make([]StatusCodeMatch, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResponseMatch.
+func (in *CustomResponseMatch) DeepCopy() *CustomResponseMatch {
+	if in == nil {
+		return nil
+	}
+	out := new(CustomResponseMatch)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *CustomTag) DeepCopyInto(out *CustomTag) {
 	*out = *in
@@ -3109,7 +3193,7 @@ func (in *JWTPrincipal) DeepCopyInto(out *JWTPrincipal) {
 	}
 	if in.Scopes != nil {
 		in, out := &in.Scopes, &out.Scopes
-		*out = make([]string, len(*in))
+		*out = make([]JWTScope, len(*in))
 		copy(*out, *in)
 	}
 }
@@ -4039,6 +4123,11 @@ func (in *ProxyAccessLogSetting) DeepCopyInto(out *ProxyAccessLogSetting) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.Type != nil {
+		in, out := &in.Type, &out.Type
+		*out = new(ProxyAccessLogType)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyAccessLogSetting.
@@ -4634,6 +4723,23 @@ func (in *RequestHeaderCustomTag) DeepCopy() *RequestHeaderCustomTag {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ResponseOverride) DeepCopyInto(out *ResponseOverride) {
+	*out = *in
+	in.Match.DeepCopyInto(&out.Match)
+	in.Response.DeepCopyInto(&out.Response)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseOverride.
+func (in *ResponseOverride) DeepCopy() *ResponseOverride {
+	if in == nil {
+		return nil
+	}
+	out := new(ResponseOverride)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Retry) DeepCopyInto(out *Retry) {
 	*out = *in
@@ -4884,6 +4990,51 @@ func (in *SourceMatch) DeepCopy() *SourceMatch {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *StatusCodeMatch) DeepCopyInto(out *StatusCodeMatch) {
+	*out = *in
+	if in.Type != nil {
+		in, out := &in.Type, &out.Type
+		*out = new(StatusCodeValueType)
+		**out = **in
+	}
+	if in.Value != nil {
+		in, out := &in.Value, &out.Value
+		*out = new(string)
+		**out = **in
+	}
+	if in.Range != nil {
+		in, out := &in.Range, &out.Range
+		*out = new(StatusCodeRange)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusCodeMatch.
+func (in *StatusCodeMatch) DeepCopy() *StatusCodeMatch {
+	if in == nil {
+		return nil
+	}
+	out := new(StatusCodeMatch)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *StatusCodeRange) DeepCopyInto(out *StatusCodeRange) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusCodeRange.
+func (in *StatusCodeRange) DeepCopy() *StatusCodeRange {
+	if in == nil {
+		return nil
+	}
+	out := new(StatusCodeRange)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *StringMatch) DeepCopyInto(out *StringMatch) {
 	*out = *in
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index 77133a803..7b13e2a12 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -945,6 +945,120 @@ spec:
                 required:
                 - type
                 type: object
+              responseOverride:
+                description: |-
+                  ResponseOverride defines the configuration to override specific responses with a custom one.
+                  If multiple configurations are specified, the first one to match wins.
+                items:
+                  description: ResponseOverride defines the configuration to override
+                    specific responses with a custom one.
+                  properties:
+                    match:
+                      description: Match configuration.
+                      properties:
+                        statusCode:
+                          description: Status code to match on. The match evaluates
+                            to true if any of the matches are successful.
+                          items:
+                            properties:
+                              range:
+                                description: |-
+                                  ValueRef contains the contents of the body
+                                  specified as a local object reference.
+                                  Only a reference to ConfigMap is supported.
+                                properties:
+                                  end:
+                                    description: End of the range, including the end
+                                      value.
+                                    type: integer
+                                  start:
+                                    description: Start of the range, including the
+                                      start value.
+                                    type: integer
+                                required:
+                                - end
+                                - start
+                                type: object
+                              type:
+                                default: Value
+                                description: Type is the type of value.
+                                enum:
+                                - Value
+                                - Range
+                                type: string
+                              value:
+                                description: Value contains the value of the status
+                                  code.
+                                type: string
+                            required:
+                            - type
+                            type: object
+                          type: array
+                      required:
+                      - statusCode
+                      type: object
+                    response:
+                      description: Response configuration.
+                      properties:
+                        body:
+                          description: Body of the Custom Response
+                          properties:
+                            inline:
+                              description: Inline contains the value as an inline
+                                string.
+                              type: string
+                            type:
+                              description: Type is the type of method to use to read
+                                the body value.
+                              enum:
+                              - Inline
+                              - ValueRef
+                              type: string
+                            valueRef:
+                              description: |-
+                                ValueRef contains the contents of the body
+                                specified as a local object reference.
+                                Only a reference to ConfigMap is supported.
+                              properties:
+                                group:
+                                  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:
+                                  description: Kind is kind of the referent. For example
+                                    "HTTPRoute" or "Service".
+                                  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
+                              required:
+                              - group
+                              - kind
+                              - name
+                              type: object
+                          required:
+                          - type
+                          type: object
+                        contentType:
+                          description: Content Type of the response. This will be
+                            set in the Content-Type header.
+                          type: string
+                      required:
+                      - body
+                      type: object
+                  required:
+                  - match
+                  - response
+                  type: object
+                type: array
               retry:
                 description: |-
                   Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions.
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 e5488fd63..03fa274ad 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
@@ -12255,6 +12255,17 @@ spec:
                               maxItems: 50
                               minItems: 1
                               type: array
+                            type:
+                              description: |-
+                                Type defines the component emitting the accesslog, such as Listener and Route.
+                                If type not defined, the setting would apply to:
+                                (1) All Routes.
+                                (2) Listeners if and only if Envoy does not find a matching route for a request.
+                                If type is defined, the accesslog settings would apply to the relevant component (as-is).
+                              enum:
+                              - Listener
+                              - Route
+                              type: string
                           required:
                           - sinks
                           type: object
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 d3d18a008..8c4debcef 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
@@ -82,7 +82,9 @@ spec:
                         name:
                           description: |-
                             Name is a user-friendly name for the rule.
-                            If not specified, Envoy Gateway will generate a unique name for the rule.n
+                            If not specified, Envoy Gateway will generate a unique name for the rule.
+                          maxLength: 253
+                          minLength: 1
                           type: string
                         principal:
                           description: |-
@@ -109,6 +111,7 @@ spec:
                                   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
                             jwt:
                               description: |-
@@ -134,6 +137,8 @@ spec:
                                           represent the full path to the claim.
                                           For example, if the claim is in the "department" field in the "organization" field,
                                           the name should be "organization.department".
+                                        maxLength: 253
+                                        minLength: 1
                                         type: string
                                       valueType:
                                         default: String
@@ -152,12 +157,24 @@ spec:
                                           If multiple values are specified, one of the values must match for the rule to match.
                                         items:
                                           type: string
+                                        maxItems: 16
+                                        minItems: 1
                                         type: array
                                     required:
                                     - name
                                     - values
                                     type: object
+                                  maxItems: 16
+                                  minItems: 1
                                   type: array
+                                provider:
+                                  description: |-
+                                    Provider is the name of the JWT provider that used to verify the JWT token.
+                                    In order to use JWT claims for authorization, you must configure the JWT
+                                    authentication with the same provider in the same `SecurityPolicy`.
+                                  maxLength: 253
+                                  minLength: 1
+                                  type: string
                                 scopes:
                                   description: |-
                                     Scopes are a special type of claim in a JWT token that represents the permissions of the client.
@@ -167,8 +184,14 @@ spec:
 
                                     If multiple scopes are specified, all scopes must match for the rule to match.
                                   items:
+                                    maxLength: 253
+                                    minLength: 1
                                     type: string
+                                  maxItems: 16
+                                  minItems: 1
                                   type: array
+                              required:
+                              - provider
                               type: object
                               x-kubernetes-validations:
                               - message: at least one of claims or scopes must be
diff --git a/charts/gateway-helm/templates/envoy-gateway-deployment.yaml b/charts/gateway-helm/templates/envoy-gateway-deployment.yaml
index 7d631f172..af5cd1169 100644
--- a/charts/gateway-helm/templates/envoy-gateway-deployment.yaml
+++ b/charts/gateway-helm/templates/envoy-gateway-deployment.yaml
@@ -82,6 +82,8 @@ spec:
             - ALL
           privileged: false
           runAsNonRoot: true
+          runAsGroup: 65532
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         volumeMounts:
diff --git a/examples/extension-server/go.mod b/examples/extension-server/go.mod
index c4d2cbcf7..5ca8291fb 100644
--- a/examples/extension-server/go.mod
+++ b/examples/extension-server/go.mod
@@ -34,8 +34,8 @@ require (
 	golang.org/x/net v0.29.0 // indirect
 	golang.org/x/sys v0.25.0 // indirect
 	golang.org/x/text v0.18.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	k8s.io/klog/v2 v2.130.1 // indirect
diff --git a/examples/extension-server/go.sum b/examples/extension-server/go.sum
index 99b3fb50e..f3cec6685 100644
--- a/examples/extension-server/go.sum
+++ b/examples/extension-server/go.sum
@@ -105,10 +105,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
-google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61 h1:pAjq8XSSzXoP9ya73v/w+9QEAAJNluLrpmMq5qFJQNY=
+google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:O6rP0uBq4k0mdi/b4ZEMAZjkhYWhS815kCvaMha4VN8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 h1:N9BgCIAUvn/M+p4NJccWPWb3BWh88+zyL0ll9HgbEeM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
 google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
 google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
 google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
diff --git a/go.mod b/go.mod
index e699b776d..14123a45a 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	fortio.org/fortio v1.66.3
 	fortio.org/log v1.16.0
 	github.com/Masterminds/semver/v3 v3.3.0
-	github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b
+	github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
 	github.com/docker/cli v27.3.1+incompatible
 	github.com/dominikbraun/graph v0.23.0
@@ -27,7 +27,7 @@ require (
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/miekg/dns v1.1.62
 	github.com/ohler55/ojg v1.24.1
-	github.com/prometheus/client_golang v1.20.3
+	github.com/prometheus/client_golang v1.20.4
 	github.com/prometheus/common v0.59.1
 	github.com/spf13/cobra v1.8.1
 	github.com/spf13/pflag v1.0.5
@@ -35,8 +35,8 @@ require (
 	github.com/telepresenceio/watchable v0.0.0-20220726211108-9bb86f92afa7
 	github.com/tsaarni/certyaml v0.9.3
 	go.opentelemetry.io/otel v1.30.0
-	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0
-	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0
+	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0
+	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0
 	go.opentelemetry.io/otel/exporters/prometheus v0.52.0
 	go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0
 	go.opentelemetry.io/otel/metric v1.30.0
@@ -62,13 +62,13 @@ require (
 )
 
 require (
-	github.com/docker/docker v27.2.0+incompatible
+	github.com/docker/docker v27.3.1+incompatible
 	github.com/replicatedhq/troubleshoot v0.102.0
 	google.golang.org/grpc v1.66.2
 )
 
 require (
-	cel.dev/expr v0.15.0 // indirect
+	cel.dev/expr v0.16.0 // indirect
 	dario.cat/mergo v1.0.1 // indirect
 	filippo.io/edwards25519 v1.1.0 // indirect
 	fortio.org/cli v1.9.0 // indirect
@@ -268,8 +268,8 @@ require (
 	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.24.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	k8s.io/component-base v0.31.1 // indirect
diff --git a/go.sum b/go.sum
index 0ed7d5b1a..d82408060 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w=
-cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
+cel.dev/expr v0.16.0 h1:yloc84fytn4zmJX2GU3TkXGsaieaV7dQ057Qs4sIG2Y=
+cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -119,8 +119,8 @@ github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
 github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
-github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg=
+github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
 github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
@@ -184,8 +184,8 @@ github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvM
 github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
 github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
-github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
+github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
 github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
 github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
@@ -672,8 +672,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
-github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
-github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
+github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -849,10 +849,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIX
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
 go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
 go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
-go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 h1:WypxHH02KX2poqqbaadmkMYalGyy/vil4HE4PM4nRJc=
-go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0/go.mod h1:U79SV99vtvGSEBeeHnpgGJfTsnsdkWLpPN/CcHAzBSI=
-go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 h1:VrMAbeJz4gnVDg2zEzjHG4dEH86j4jO6VYB+NgtGD8s=
-go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0/go.mod h1:qqN/uFdpeitTvm+JDqqnjm517pmQRYxTORbETHq5tOc=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
@@ -1039,10 +1039,10 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
-google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo=
+google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 h1:N9BgCIAUvn/M+p4NJccWPWb3BWh88+zyL0ll9HgbEeM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go
index 86d39602f..2ab9670d5 100644
--- a/internal/gatewayapi/securitypolicy.go
+++ b/internal/gatewayapi/securitypolicy.go
@@ -942,6 +942,8 @@ func (t *Translator) buildAuthorization(policy *egv1a1.SecurityPolicy) (*ir.Auth
 			principal.ClientCIDRs = append(principal.ClientCIDRs, cidrMatch)
 		}
 
+		principal.JWT = rule.Principal.JWT
+
 		var name string
 		if rule.Name != nil && *rule.Name != "" {
 			name = *rule.Name
diff --git a/internal/infrastructure/kubernetes/proxy/resource.go b/internal/infrastructure/kubernetes/proxy/resource.go
index 32ca695e5..d4d210418 100644
--- a/internal/infrastructure/kubernetes/proxy/resource.go
+++ b/internal/infrastructure/kubernetes/proxy/resource.go
@@ -447,6 +447,11 @@ func expectedEnvoySecurityContext(containerSpec *egv1a1.KubernetesContainerSpec)
 	}
 
 	sc := resource.DefaultSecurityContext()
+
+	// run as non-root user
+	sc.RunAsGroup = ptr.To(int64(65532))
+	sc.RunAsUser = ptr.To(int64(65532))
+
 	// Envoy container needs to write to the log file/UDS socket.
 	sc.ReadOnlyRootFilesystem = nil
 	return sc
@@ -454,6 +459,11 @@ func expectedEnvoySecurityContext(containerSpec *egv1a1.KubernetesContainerSpec)
 
 func expectedShutdownManagerSecurityContext() *corev1.SecurityContext {
 	sc := resource.DefaultSecurityContext()
+
+	// run as non-root user
+	sc.RunAsGroup = ptr.To(int64(65532))
+	sc.RunAsUser = ptr.To(int64(65532))
+
 	// ShutdownManger creates a file to indicate the connection drain process is completed,
 	// so it needs file write permission.
 	sc.ReadOnlyRootFilesystem = nil
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index 86a2d3956..399d1f0db 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -978,6 +978,8 @@ type AuthorizationRule struct {
 type Principal struct {
 	// ClientCIDRs defines the client CIDRs to be matched.
 	ClientCIDRs []*CIDRMatch `json:"clientCIDRs,omitempty"`
+	// JWT defines the JWT principal to be matched.
+	JWT *egv1a1.JWTPrincipal `json:"jwt,omitempty"`
 }
 
 // FaultInjection defines the schema for injecting faults into requests.
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index 0b964d5fc..5afb29d12 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -2092,6 +2092,11 @@ func (in *Principal) DeepCopyInto(out *Principal) {
 			}
 		}
 	}
+	if in.JWT != nil {
+		in, out := &in.JWT, &out.JWT
+		*out = new(v1alpha1.JWTPrincipal)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Principal.
diff --git a/internal/xds/translator/authorization.go b/internal/xds/translator/authorization.go
index e3a61b164..0d2d19dc5 100644
--- a/internal/xds/translator/authorization.go
+++ b/internal/xds/translator/authorization.go
@@ -8,6 +8,7 @@ package translator
 import (
 	"errors"
 	"fmt"
+	"strings"
 
 	cncfv3 "github.com/cncf/xds/go/xds/core/v3"
 	matcherv3 "github.com/cncf/xds/go/xds/type/matcher/v3"
@@ -18,6 +19,8 @@ import (
 	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"
+	metadatav3 "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/metadata/v3"
+	envoymatcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
@@ -121,13 +124,35 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
 	}
 
 	var (
-		authorization = irRoute.Security.Authorization
-		allowAction   *anypb.Any
-		denyAction    *anypb.Any
-		sourceIPInput *anypb.Any
-		ipMatcher     *anypb.Any
-		matcherList   []*matcherv3.Matcher_MatcherList_FieldMatcher
-		err           error
+		rbacPerRoute *rbacv3.RBACPerRoute
+		cfgAny       *anypb.Any
+		err          error
+	)
+
+	if rbacPerRoute, err = buildRBACPerRoute(irRoute.Security.Authorization); err != nil {
+		return err
+	}
+
+	if cfgAny, err = anypb.New(rbacPerRoute); err != nil {
+		return err
+	}
+
+	if filterCfg == nil {
+		route.TypedPerFilterConfig = make(map[string]*anypb.Any)
+	}
+
+	route.TypedPerFilterConfig[egv1a1.EnvoyFilterRBAC.String()] = cfgAny
+
+	return nil
+}
+
+func buildRBACPerRoute(authorization *ir.Authorization) (*rbacv3.RBACPerRoute, error) {
+	var (
+		rbac        *rbacv3.RBACPerRoute
+		allowAction *anypb.Any
+		denyAction  *anypb.Any
+		matcherList []*matcherv3.Matcher_MatcherList_FieldMatcher
+		err         error
 	)
 
 	allow := &rbacconfigv3.Action{
@@ -135,7 +160,7 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
 		Action: rbacconfigv3.RBAC_ALLOW,
 	}
 	if allowAction, err = anypb.New(allow); err != nil {
-		return err
+		return nil, err
 	}
 
 	deny := &rbacconfigv3.Action{
@@ -143,7 +168,7 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
 		Action: rbacconfigv3.RBAC_DENY,
 	}
 	if denyAction, err = anypb.New(deny); err != nil {
-		return err
+		return nil, err
 	}
 
 	// Build a list of matchers based on the rules.
@@ -152,52 +177,71 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
 	// 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",
-		}
+		var (
+			ipPredicate  *matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_
+			jwtPredicate []*matcherv3.Matcher_MatcherList_Predicate
+			predicate    *matcherv3.Matcher_MatcherList_Predicate
+		)
 
-		for _, cidr := range rule.Principal.ClientCIDRs {
-			ipRangeMatcher.CidrRanges = append(ipRangeMatcher.CidrRanges, &configv3.CidrRange{
-				AddressPrefix: cidr.IP,
-				PrefixLen: &wrapperspb.UInt32Value{
-					Value: cidr.MaskLen,
-				},
-			})
+		// Determine the action for the current rule.
+		ruleAction := allowAction
+		if rule.Action == egv1a1.AuthorizationActionDeny {
+			ruleAction = denyAction
 		}
 
-		if ipMatcher, err = anypb.New(ipRangeMatcher); err != nil {
-			return err
+		if len(rule.Principal.ClientCIDRs) > 0 {
+			if ipPredicate, err = buildIPPredicate(rule.Principal.ClientCIDRs); err != nil {
+				return nil, err
+			}
 		}
 
-		if sourceIPInput, err = anypb.New(&networkinput.SourceIPInput{}); err != nil {
-			return err
+		if rule.Principal.JWT != nil {
+			if jwtPredicate, err = buildJWTPredicate(*rule.Principal.JWT); err != nil {
+				return nil, err
+			}
 		}
 
-		// Determine the action for the current rule.
-		ruleAction := allowAction
-		if rule.Action == egv1a1.AuthorizationActionDeny {
-			ruleAction = denyAction
+		// Build the predicate for the current rule.
+		switch {
+		// If both IP and JWT predicates are present, AND them together.
+		case ipPredicate != nil && jwtPredicate != nil:
+			predicates := []*matcherv3.Matcher_MatcherList_Predicate{
+				{
+					MatchType: ipPredicate,
+				},
+			}
+			predicates = append(predicates, jwtPredicate...)
+
+			predicate = &matcherv3.Matcher_MatcherList_Predicate{
+				MatchType: &matcherv3.Matcher_MatcherList_Predicate_AndMatcher{
+					AndMatcher: &matcherv3.Matcher_MatcherList_Predicate_PredicateList{
+						Predicate: predicates,
+					},
+				},
+			}
+		case ipPredicate != nil:
+			predicate = &matcherv3.Matcher_MatcherList_Predicate{
+				MatchType: ipPredicate,
+			}
+		case jwtPredicate != nil:
+			// If there are multiple JWT predicates, AND them together.
+			if len(jwtPredicate) > 1 {
+				predicate = &matcherv3.Matcher_MatcherList_Predicate{
+					MatchType: &matcherv3.Matcher_MatcherList_Predicate_AndMatcher{
+						AndMatcher: &matcherv3.Matcher_MatcherList_Predicate_PredicateList{
+							Predicate: jwtPredicate,
+						},
+					},
+				}
+			} else if len(jwtPredicate) == 1 {
+				predicate = jwtPredicate[0]
+			}
 		}
 
 		// Add the matcher generated with the current rule to the matcher list.
+		// The first matcher that matches will be used to determine the action.
 		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,
-							},
-						},
-					},
-				},
-			},
+			Predicate: predicate,
 			OnMatch: &matcherv3.Matcher_OnMatch{
 				OnMatch: &matcherv3.Matcher_OnMatch_Action{
 					Action: &cncfv3.TypedExtensionConfig{
@@ -215,7 +259,7 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
 		defaultAction = allowAction
 	}
 
-	routeCfgProto := &rbacv3.RBACPerRoute{
+	rbac = &rbacv3.RBACPerRoute{
 		Rbac: &rbacv3.RBAC{
 			Matcher: &matcherv3.Matcher{
 				MatcherType: &matcherv3.Matcher_MatcherList_{
@@ -240,26 +284,260 @@ func (*rbac) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
 	// 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
+		rbac.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
+	if err = rbac.ValidateAll(); err != nil {
+		return nil, err
 	}
 
-	routeCfgAny, err := anypb.New(routeCfgProto)
-	if err != nil {
-		return err
+	return rbac, nil
+}
+
+func buildIPPredicate(clientCIDRs []*ir.CIDRMatch) (*matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_, error) {
+	var (
+		sourceIPInput *anypb.Any
+		ipMatcher     *anypb.Any
+		err           error
+	)
+
+	// Build the IPMatcher based on the client CIDRs.
+	ipRangeMatcher := &ipmatcherv3.Ip{
+		StatPrefix: "client_ip",
 	}
 
-	if filterCfg == nil {
-		route.TypedPerFilterConfig = make(map[string]*anypb.Any)
+	for _, cidr := range clientCIDRs {
+		ipRangeMatcher.CidrRanges = append(ipRangeMatcher.CidrRanges, &configv3.CidrRange{
+			AddressPrefix: cidr.IP,
+			PrefixLen: &wrapperspb.UInt32Value{
+				Value: cidr.MaskLen,
+			},
+		})
 	}
 
-	route.TypedPerFilterConfig[egv1a1.EnvoyFilterRBAC.String()] = routeCfgAny
+	if ipMatcher, err = anypb.New(ipRangeMatcher); err != nil {
+		return nil, err
+	}
 
-	return nil
+	if sourceIPInput, err = anypb.New(&networkinput.SourceIPInput{}); err != nil {
+		return nil, err
+	}
+
+	return &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,
+				},
+			},
+		},
+	}, nil
+}
+
+func buildJWTPredicate(jwt egv1a1.JWTPrincipal) ([]*matcherv3.Matcher_MatcherList_Predicate, error) {
+	jwtPredicate := []*matcherv3.Matcher_MatcherList_Predicate{}
+
+	// Build the scope matchers.
+	// Multiple scopes are ANDed together.
+	for _, scope := range jwt.Scopes {
+		var (
+			inputPb   *anypb.Any
+			matcherPb *anypb.Any
+			err       error
+		)
+
+		input := &networkinput.DynamicMetadataInput{
+			Filter: "envoy.filters.http.jwt_authn",
+			Path: []*networkinput.DynamicMetadataInput_PathSegment{
+				{
+					Segment: &networkinput.DynamicMetadataInput_PathSegment_Key{
+						Key: jwt.Provider, // The name of the jwt provider is used as the `payload_in_metadata` in the JWT Authn filter.
+					},
+				},
+				{
+					Segment: &networkinput.DynamicMetadataInput_PathSegment_Key{
+						Key: "scope",
+					},
+				},
+			},
+		}
+
+		// The scope has already been normalized to a string array in the JWT Authn filter.
+		scopeMatcher := &metadatav3.Metadata{
+			Value: &envoymatcherv3.ValueMatcher{
+				MatchPattern: &envoymatcherv3.ValueMatcher_ListMatch{
+					ListMatch: &envoymatcherv3.ListMatcher{
+						MatchPattern: &envoymatcherv3.ListMatcher_OneOf{
+							OneOf: &envoymatcherv3.ValueMatcher{
+								MatchPattern: &envoymatcherv3.ValueMatcher_StringMatch{
+									StringMatch: &envoymatcherv3.StringMatcher{
+										MatchPattern: &envoymatcherv3.StringMatcher_Exact{
+											Exact: string(scope),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+
+		if inputPb, err = anypb.New(input); err != nil {
+			return nil, err
+		}
+
+		if matcherPb, err = anypb.New(scopeMatcher); err != nil {
+			return nil, err
+		}
+
+		scopePredicate := matcherv3.Matcher_MatcherList_Predicate_SinglePredicate{
+			Input: &cncfv3.TypedExtensionConfig{
+				Name:        "scope",
+				TypedConfig: inputPb,
+			},
+			Matcher: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_CustomMatch{
+				CustomMatch: &cncfv3.TypedExtensionConfig{
+					Name:        "scope_matcher",
+					TypedConfig: matcherPb,
+				},
+			},
+		}
+
+		jwtPredicate = append(jwtPredicate,
+			&matcherv3.Matcher_MatcherList_Predicate{
+				MatchType: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_{
+					SinglePredicate: &scopePredicate,
+				},
+			},
+		)
+	}
+
+	// Build the claim matchers.
+	// Multiple claims are ANDed together.
+	// Multiple values for a claim are ORed together.
+	// For example, if we have two claims: "claim1" with values ["value1", "value2"], and "claim2" with values ["value3", "value4"],
+	// the resulting matcher will be: (claim1 == value1 OR claim1 == value2) AND (claim2 == value3 OR claim2 == value4).
+	predicateForAllClaims := []*matcherv3.Matcher_MatcherList_Predicate{}
+	for _, claim := range jwt.Claims {
+		var (
+			inputPb   *anypb.Any
+			matcherPb *anypb.Any
+			err       error
+		)
+
+		path := []*networkinput.DynamicMetadataInput_PathSegment{
+			{
+				Segment: &networkinput.DynamicMetadataInput_PathSegment_Key{
+					Key: jwt.Provider, // The name of the jwt provider is used as the `payload_in_metadata` in the JWT Authn filter.
+				},
+			},
+		}
+
+		// A nested claim is represented as a dot-separated string, e.g., "user.email".
+		for _, segment := range strings.Split(claim.Name, ".") {
+			path = append(path, &networkinput.DynamicMetadataInput_PathSegment{
+				Segment: &networkinput.DynamicMetadataInput_PathSegment_Key{
+					Key: segment,
+				},
+			})
+		}
+
+		input := &networkinput.DynamicMetadataInput{
+			Filter: "envoy.filters.http.jwt_authn",
+			Path:   path,
+		}
+
+		if inputPb, err = anypb.New(input); err != nil {
+			return nil, err
+		}
+
+		predicateForOneClaim := []*matcherv3.Matcher_MatcherList_Predicate{}
+		for _, value := range claim.Values {
+			var valueMatcher *envoymatcherv3.ValueMatcher
+
+			if claim.ValueType != nil && *claim.ValueType == egv1a1.JWTClaimValueTypeStringArray {
+				valueMatcher = &envoymatcherv3.ValueMatcher{
+					MatchPattern: &envoymatcherv3.ValueMatcher_ListMatch{
+						ListMatch: &envoymatcherv3.ListMatcher{
+							MatchPattern: &envoymatcherv3.ListMatcher_OneOf{
+								OneOf: &envoymatcherv3.ValueMatcher{
+									MatchPattern: &envoymatcherv3.ValueMatcher_StringMatch{
+										StringMatch: &envoymatcherv3.StringMatcher{
+											MatchPattern: &envoymatcherv3.StringMatcher_Exact{
+												Exact: value,
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				}
+			} else {
+				valueMatcher = &envoymatcherv3.ValueMatcher{
+					MatchPattern: &envoymatcherv3.ValueMatcher_StringMatch{
+						StringMatch: &envoymatcherv3.StringMatcher{
+							MatchPattern: &envoymatcherv3.StringMatcher_Exact{
+								Exact: value,
+							},
+						},
+					},
+				}
+			}
+
+			if matcherPb, err = anypb.New(&metadatav3.Metadata{
+				Value: valueMatcher,
+			}); err != nil {
+				return nil, err
+			}
+
+			predicateForOneClaim = append(predicateForOneClaim, &matcherv3.Matcher_MatcherList_Predicate{
+				MatchType: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_{
+					SinglePredicate: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate{
+						Input: &cncfv3.TypedExtensionConfig{
+							Name:        "claim",
+							TypedConfig: inputPb,
+						},
+						Matcher: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_CustomMatch{
+							CustomMatch: &cncfv3.TypedExtensionConfig{
+								Name:        "claim_matcher",
+								TypedConfig: matcherPb,
+							},
+						},
+					},
+				},
+			})
+		}
+
+		// For a claim to match, one of the values must match.
+		// If there are multiple values for a claim, OR them together.
+		if len(predicateForOneClaim) > 1 {
+			predicateForAllClaims = append(predicateForAllClaims, &matcherv3.Matcher_MatcherList_Predicate{
+				MatchType: &matcherv3.Matcher_MatcherList_Predicate_OrMatcher{
+					OrMatcher: &matcherv3.Matcher_MatcherList_Predicate_PredicateList{
+						Predicate: predicateForOneClaim,
+					},
+				},
+			})
+		} else if len(predicateForOneClaim) == 1 {
+			predicateForAllClaims = append(predicateForAllClaims, &matcherv3.Matcher_MatcherList_Predicate{
+				MatchType: predicateForOneClaim[0].MatchType.(*matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_),
+			})
+		}
+	}
+
+	// For a JWT principal to match, all the specified claims and scopes must match.
+	// And all the claims and scopes together.
+	jwtPredicate = append(jwtPredicate, predicateForAllClaims...)
+
+	return jwtPredicate, nil
 }
 
 func (c *rbac) patchResources(*types.ResourceVersionTable, []*ir.HTTPRoute) error {
diff --git a/internal/xds/translator/jwt.go b/internal/xds/translator/jwt.go
index 4d3a85837..53a20808f 100644
--- a/internal/xds/translator/jwt.go
+++ b/internal/xds/translator/jwt.go
@@ -139,9 +139,13 @@ func buildJWTAuthn(irListener *ir.HTTPListener) (*jwtauthnv3.JwtAuthentication,
 				Issuer:              irProvider.Issuer,
 				Audiences:           irProvider.Audiences,
 				JwksSourceSpecifier: remote,
-				PayloadInMetadata:   irProvider.Issuer,
+				PayloadInMetadata:   irProvider.Name,
 				ClaimToHeaders:      claimToHeaders,
 				Forward:             true,
+				NormalizePayloadInMetadata: &jwtauthnv3.JwtProvider_NormalizePayload{
+					// Normalize the scopes to facilitate matching in Authorization.
+					SpaceDelimitedClaims: []string{"scope"},
+				},
 			}
 
 			if irProvider.RecomputeRoute != nil {
diff --git a/release-notes/v1.1.2.yaml b/release-notes/v1.1.2.yaml
new file mode 100644
index 000000000..a257ea677
--- /dev/null
+++ b/release-notes/v1.1.2.yaml
@@ -0,0 +1,16 @@
+date: September 24, 2024
+
+changes:
+  - area: translator
+    change: |
+      Fixed handling of sectionName in BackendTLSPolicy for Backend resource
+
+
+  - area: infra-manager
+    change: |
+      Pin Envoy Proxy version to v1.32.2
+      Change Envoy listener drain strategy from gradual to immediate
+
+  - area: providers
+    change: |
+      Fixed reconciliation of HTTPRoutes when labels change
diff --git a/site/content/en/contributions/roadmap.md b/site/content/en/contributions/roadmap.md
index 955af2a96..54b808587 100644
--- a/site/content/en/contributions/roadmap.md
+++ b/site/content/en/contributions/roadmap.md
@@ -63,6 +63,10 @@ contributing to the project.
 - Add TrafficPolicy APIs for advanced features [Issue #1492][1492].
 - Envoy Gateway meets readiness criteria [Issue #1160][1160]. 
 
+### [v1.0.0][v1.0.0]: Envoy Gateway goes GA
+
+Visit the [milestones][milestones] page to learn more about the future roadmap.
+
 [issue]: https://github.com/envoyproxy/gateway/issues
 [meeting]: https://docs.google.com/document/d/1leqwsHX8N-XxNEyTflYjRur462ukFxd19Rnk3Uzy55I/edit?usp=sharing
 [pr]: https://github.com/envoyproxy/gateway/compare
@@ -72,6 +76,7 @@ contributing to the project.
 [v0.4.0]: https://github.com/envoyproxy/gateway/milestone/12
 [v0.5.0]: https://github.com/envoyproxy/gateway/milestone/13
 [v0.6.0]: https://github.com/envoyproxy/gateway/milestone/15
+[v1.0.0]: https://github.com/envoyproxy/gateway/milestone/18
 [17]: https://github.com/envoyproxy/gateway/issues/17
 [20]: https://github.com/envoyproxy/gateway/issues/20
 [24]: https://github.com/envoyproxy/gateway/issues/24
diff --git a/site/content/en/docs/tasks/quickstart.md b/site/content/en/docs/tasks/quickstart.md
index 802b7989a..e1943c21e 100644
--- a/site/content/en/docs/tasks/quickstart.md
+++ b/site/content/en/docs/tasks/quickstart.md
@@ -12,7 +12,7 @@ A Kubernetes cluster.
 
 __Note:__ Refer to the [Compatibility Matrix](/news/releases/matrix) for supported Kubernetes versions.
 
-__Note:__ In case your Kubernetes cluster, does not have a LoadBalancer implementation, we recommend installing one
+__Note:__ In case your Kubernetes cluster does not have a LoadBalancer implementation, we recommend installing one
 so the `Gateway` resource has an Address associated with it. We recommend using [MetalLB](https://metallb.universe.tf/installation/).
 
 __Note:__ For Mac user, you need install and run [Docker Mac Net Connect](https://github.com/chipmk/docker-mac-net-connect) to make the Docker network work.
diff --git a/site/content/en/news/releases/_index.md b/site/content/en/news/releases/_index.md
index e8282654b..71ff48fd3 100644
--- a/site/content/en/news/releases/_index.md
+++ b/site/content/en/news/releases/_index.md
@@ -8,8 +8,7 @@ type = "docs"
 +++
 
 This document provides details for Envoy Gateway releases. Envoy Gateway follows the Semantic Versioning [v2.0.0 spec][]
-for release versioning. Since Envoy Gateway is a new project, minor releases are the only defined releases. Envoy
-Gateway maintainers will establish additional release details, e.g. patch releases, at a future date.
+for release versioning.
 
 ## Stable Releases
 
@@ -37,6 +36,7 @@ communications with the Envoy Gateway community, and the mechanics of the releas
 | 2023 Q4 |    Arko Dasgupta ([arkodg](https://github.com/arkodg))         |
 | 2024 Q1 |    Xunzhuo Liu ([Xunzhuo](https://github.com/Xunzhuo))         |
 | 2024 Q2 |    Guy Daich ([guydc](https://github.com/guydc))               |
+| 2024 Q3 |    Huabing Zhao ([zhaohuabing](https://github.com/zhaohuabing))|
 
 ## Release Schedule
 
@@ -52,6 +52,7 @@ In order to align with the Envoy Proxy [release schedule][], Envoy Gateway relea
 |  0.6.0  | 2023/10/22  | 2023/11/02  |   +10 days  |  2024/05/02 |
 |  1.0.0  | 2024/03/06  | 2023/03/13  |   +7 days   |  2024/09/13 |
 |  1.1.0  | 2024/07/16	| 2024/07/22  |   +6 days   |  2024/01/22 |
+|  1.2.0  | 2024/10/22	|             |             |             |
 
 [v2.0.0 spec]: https://semver.org/spec/v2.0.0.html
 [release guide]: ../../contributions/releasing
diff --git a/site/content/en/news/releases/notes/v1.1.2.md b/site/content/en/news/releases/notes/v1.1.2.md
new file mode 100644
index 000000000..b47371add
--- /dev/null
+++ b/site/content/en/news/releases/notes/v1.1.2.md
@@ -0,0 +1,17 @@
+---
+title: "v1.1.2"
+publishdate: 2024-09-24
+---
+
+Date: September 24, 2024
+
+## Translator
+- Fixed handling of sectionName in BackendTLSPolicy for Backend resource
+
+## Infra-manager
+- Pin Envoy Proxy version to v1.32.2
+- Change Envoy listener drain strategy from gradual to immediate
+
+## Providers
+- Fixed reconciliation of HTTPRoutes when labels change
+
diff --git a/site/content/en/v1.1/tasks/quickstart.md b/site/content/en/v1.1/tasks/quickstart.md
index 802b7989a..e1943c21e 100644
--- a/site/content/en/v1.1/tasks/quickstart.md
+++ b/site/content/en/v1.1/tasks/quickstart.md
@@ -12,7 +12,7 @@ A Kubernetes cluster.
 
 __Note:__ Refer to the [Compatibility Matrix](/news/releases/matrix) for supported Kubernetes versions.
 
-__Note:__ In case your Kubernetes cluster, does not have a LoadBalancer implementation, we recommend installing one
+__Note:__ In case your Kubernetes cluster does not have a LoadBalancer implementation, we recommend installing one
 so the `Gateway` resource has an Address associated with it. We recommend using [MetalLB](https://metallb.universe.tf/installation/).
 
 __Note:__ For Mac user, you need install and run [Docker Mac Net Connect](https://github.com/chipmk/docker-mac-net-connect) to make the Docker network work.
diff --git a/site/content/zh/news/releases/_index.md b/site/content/zh/news/releases/_index.md
index 9512bbc72..8afc5916c 100644
--- a/site/content/zh/news/releases/_index.md
+++ b/site/content/zh/news/releases/_index.md
@@ -36,6 +36,8 @@ Envoy Gateway 的稳定版本包括：
 | 2023 Q3 |    Arko Dasgupta ([arkodg](https://github.com/arkodg))         |
 | 2023 Q4 |    Arko Dasgupta ([arkodg](https://github.com/arkodg))         |
 | 2024 Q1 |    Xunzhuo Liu ([Xunzhuo](https://github.com/Xunzhuo))         |
+| 2024 Q2 |    Guy Daich ([guydc](https://github.com/guydc))               |
+| 2024 Q3 |    Huabing Zhao ([zhaohuabing](https://github.com/zhaohuabing))|
 
 ## 发布时间表 {#release-schedule}
 
@@ -50,6 +52,9 @@ Envoy Gateway 版本按固定时间表（每个季度的第 22 天）生成，
 |  0.4.0  | 2023/04/22  | 2023/04/24  |   +2 天   |  2023/10/24 |
 |  0.5.0  | 2023/07/22  | 2023/08/02  |   +10 天  |  2024/01/02 |
 |  0.6.0  | 2023/10/22  | 2023/11/02  |   +10 天  |  2024/05/02 |
+|  1.0.0  | 2024/03/06  | 2023/03/13  |   +7 天   |  2024/09/13 |
+|  1.1.0  | 2024/07/16	| 2024/07/22  |   +6 天   |  2024/01/22 |
+|  1.2.0  | 2024/10/22	|             |           |             | 
 
 [v2.0.0 规范]: https://semver.org/lang/zh-CN/
 [发布指南]: ../../contributions/releasing
diff --git a/site/layouts/shortcodes/helm-version.html b/site/layouts/shortcodes/helm-version.html
index 7e0b44a81..704c3dfde 100644
--- a/site/layouts/shortcodes/helm-version.html
+++ b/site/layouts/shortcodes/helm-version.html
@@ -3,8 +3,8 @@
 {{- "v0.0.0-latest" -}}
 {{- end -}}
 {{- with (strings.HasPrefix $pagePrefix "v1.1") -}}
-{{- "v1.1.1" -}}
+{{- "v1.1.2" -}}
 {{- end -}}
 {{- with (strings.HasPrefix $pagePrefix "doc") -}}
-{{- "v1.1.1" -}}
+{{- "v1.1.2" -}}
 {{- end -}}
diff --git a/site/layouts/shortcodes/yaml-version.html b/site/layouts/shortcodes/yaml-version.html
index a033e6ce1..eced90281 100644
--- a/site/layouts/shortcodes/yaml-version.html
+++ b/site/layouts/shortcodes/yaml-version.html
@@ -3,8 +3,8 @@
 {{- "latest" -}}
 {{- end -}}
 {{- with (strings.HasPrefix $pagePrefix "v1.1") -}}
-{{- "v1.1.1" -}}
+{{- "v1.1.2" -}}
 {{- end -}}
 {{- with (strings.HasPrefix $pagePrefix "doc") -}}
-{{- "v1.1.1" -}}
+{{- "v1.1.2" -}}
 {{- end -}}
diff --git a/tools/hack/create-cluster.sh b/tools/hack/create-cluster.sh
index 16e1477b5..ad3cff1cd 100755
--- a/tools/hack/create-cluster.sh
+++ b/tools/hack/create-cluster.sh
@@ -7,11 +7,13 @@ CLUSTER_NAME=${CLUSTER_NAME:-"envoy-gateway"}
 METALLB_VERSION=${METALLB_VERSION:-"v0.13.10"}
 KIND_NODE_TAG=${KIND_NODE_TAG:-"v1.31.0"}
 NUM_WORKERS=${NUM_WORKERS:-""}
-
+IP_FAMILY=${IP_FAMILY:-"ipv4"}
 
 KIND_CFG=$(cat <<-EOM
 kind: Cluster
 apiVersion: kind.x-k8s.io/v1alpha4
+networking:
+  ipFamily: ${IP_FAMILY}
 nodes:
 - role: control-plane
 EOM
@@ -53,10 +55,31 @@ kubectl rollout status -n metallb-system deployment/controller --timeout 5m
 kubectl rollout status -n metallb-system daemonset/speaker --timeout 5m
 
 # Apply config with addresses based on docker network IPAM.
-subnet=$(docker network inspect kind | jq -r '.[].IPAM.Config[].Subnet | select(contains(":") | not)')
-# Assume default kind network subnet prefix of 16, and choose addresses in that range.
-address_first_octets=$(echo "${subnet}" | awk -F. '{printf "%s.%s",$1,$2}')
-address_range="${address_first_octets}.255.200-${address_first_octets}.255.250"
+address_ranges=""
+
+if [ "${IP_FAMILY}" = "ipv4" ] || [ "${IP_FAMILY}" = "dual" ]; then
+    subnet_v4=$(docker network inspect kind | jq -r '.[].IPAM.Config[] | select(.Subnet | contains(":") | not) | .Subnet')
+    address_prefix_v4=$(echo "${subnet_v4}" | awk -F. '{print $1"."$2"."$3}')
+    address_range_v4="${address_prefix_v4}.200-${address_prefix_v4}.250"
+    echo "IPv4 address range: ${address_range_v4}"
+    address_ranges+="- ${address_range_v4}"
+fi
+
+if [ "${IP_FAMILY}" = "ipv6" ] || [ "${IP_FAMILY}" = "dual" ]; then
+    subnet_v6=$(docker network inspect kind | jq -r '.[].IPAM.Config[] | select(.Subnet | contains(":")) | .Subnet')
+    ipv6_prefix="${subnet_v6%::*}"
+    address_range_v6="${ipv6_prefix}::c8-${ipv6_prefix}::fa"
+    echo "IPv6 address range: ${address_range_v6}"
+    [ -n "${address_ranges}" ] && address_ranges+="\n"
+    address_ranges+="- ${address_range_v6}"
+fi
+
+if [ -z "${address_ranges}" ]; then
+    echo "Error: No valid IP ranges found for IP_FAMILY=${IP_FAMILY}"
+    exit 1
+fi
+
+# Apply MetalLB IPAddressPool and L2Advertisement
 kubectl apply -f - <<EOF
 apiVersion: metallb.io/v1beta1
 kind: IPAddressPool
@@ -65,7 +88,7 @@ metadata:
   name: kube-services
 spec:
   addresses:
-  - ${address_range}
+$(echo -e "${address_ranges}" | sed 's/^/    /')
 ---
 apiVersion: metallb.io/v1beta1
 kind: L2Advertisement


Test patch:
diff --git a/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.all.json b/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.all.json
index bac479d41..d79048a75 100644
--- a/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.all.json
+++ b/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.all.json
@@ -499,6 +499,12 @@
                                 "providers": {
                                   "httproute/envoy-gateway-system/backend/rule/0/match/0/www_example_com/example": {
                                     "forward": true,
+                                    "normalizePayloadInMetadata": {
+                                      "spaceDelimitedClaims": [
+                                        "scope"
+                                      ]
+                                    },
+                                    "payloadInMetadata": "example",
                                     "remoteJwks": {
                                       "asyncFetch": {},
                                       "cacheDuration": "300s",
diff --git a/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.all.yaml b/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.all.yaml
index 651c46f8f..bed4d0036 100644
--- a/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.all.yaml
@@ -293,6 +293,10 @@ xds:
                       providers:
                         httproute/envoy-gateway-system/backend/rule/0/match/0/www_example_com/example:
                           forward: true
+                          normalizePayloadInMetadata:
+                            spaceDelimitedClaims:
+                            - scope
+                          payloadInMetadata: example
                           remoteJwks:
                             asyncFetch: {}
                             cacheDuration: 300s
diff --git a/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.listener.yaml b/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.listener.yaml
index 7fdd4a247..347f33021 100644
--- a/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.listener.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/jwt-single-route-single-match-to-xds.listener.yaml
@@ -49,6 +49,10 @@ xds:
                     providers:
                       httproute/envoy-gateway-system/backend/rule/0/match/0/www_example_com/example:
                         forward: true
+                        normalizePayloadInMetadata:
+                          spaceDelimitedClaims:
+                          - scope
+                        payloadInMetadata: example
                         remoteJwks:
                           asyncFetch: {}
                           cacheDuration: 300s
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion-client-cidr.in.yaml
similarity index 100%
rename from internal/gatewayapi/testdata/securitypolicy-with-authoriztion.in.yaml
rename to internal/gatewayapi/testdata/securitypolicy-with-authoriztion-client-cidr.in.yaml
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion-client-cidr.out.yaml
similarity index 100%
rename from internal/gatewayapi/testdata/securitypolicy-with-authoriztion.out.yaml
rename to internal/gatewayapi/testdata/securitypolicy-with-authoriztion-client-cidr.out.yaml
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion-jwt-claim.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion-jwt-claim.in.yaml
new file mode 100644
index 000000000..b9aec9c8f
--- /dev/null
+++ b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion-jwt-claim.in.yaml
@@ -0,0 +1,74 @@
+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
+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
+    jwt:
+      providers:
+      - name: example1
+        issuer: https://two.example.com
+        audiences:
+        - two.foo.com
+        remoteJWKS:
+          uri: https://two.example.com/jwt/public-key/jwks.json
+    authorization:
+      defaultAction: Deny
+      rules:
+      - name: "allow-jwt-claim"
+        action: Deny
+        principal:
+          jwt:
+            provider: example1
+            scopes:
+            - "foo"
+            - "bar"
+            claims:
+            - name: "sub"
+              values:
+              - "1234567890"
+            - name: "roles"
+              valueType: "StringArray"
+              values:
+              - "admin"
+              - "superuser"
diff --git a/internal/gatewayapi/testdata/securitypolicy-with-authoriztion-jwt-claim.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion-jwt-claim.out.yaml
new file mode 100644
index 000000000..ed422e700
--- /dev/null
+++ b/internal/gatewayapi/testdata/securitypolicy-with-authoriztion-jwt-claim.out.yaml
@@ -0,0 +1,220 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+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
+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-gateway
+    namespace: envoy-gateway
+  spec:
+    authorization:
+      defaultAction: Deny
+      rules:
+      - action: Deny
+        name: allow-jwt-claim
+        principal:
+          jwt:
+            claims:
+            - name: sub
+              values:
+              - "1234567890"
+            - name: roles
+              valueType: StringArray
+              values:
+              - admin
+              - superuser
+            provider: example1
+            scopes:
+            - foo
+            - bar
+    jwt:
+      providers:
+      - audiences:
+        - two.foo.com
+        issuer: https://two.example.com
+        name: example1
+        remoteJWKS:
+          uri: https://two.example.com/jwt/public-key/jwks.json
+    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
+      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
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          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
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/www_example_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /foo
+        security:
+          authorization:
+            defaultAction: Deny
+            rules:
+            - action: Deny
+              name: allow-jwt-claim
+              principal:
+                jwt:
+                  claims:
+                  - name: sub
+                    values:
+                    - "1234567890"
+                  - name: roles
+                    valueType: StringArray
+                    values:
+                    - admin
+                    - superuser
+                  provider: example1
+                  scopes:
+                  - foo
+                  - bar
+          jwt:
+            providers:
+            - audiences:
+              - two.foo.com
+              issuer: https://two.example.com
+              name: example1
+              remoteJWKS:
+                uri: https://two.example.com/jwt/public-key/jwks.json
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml
index b81eac520..4f9107fb4 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml
@@ -94,7 +94,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -168,7 +170,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml
index dd3d30cb8..a3bad8995 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml
@@ -347,7 +347,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml
index 2756e0ac6..6afaa48eb 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml
@@ -346,7 +346,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml
index cdecbf11a..c55efd9a6 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml
@@ -261,7 +261,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -335,7 +337,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml
index cb14c7d01..0190a0323 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml
@@ -232,7 +232,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -306,7 +308,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml
index 844e8c38b..59abefc9f 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml
@@ -350,7 +350,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml
index 3e2631cf7..acf981d6e 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml
@@ -270,7 +270,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -344,7 +346,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml
index 894bd9597..6eaf679f3 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml
@@ -261,7 +261,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -335,7 +337,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml
index 04230b2f4..b4ff0f41c 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml
@@ -261,7 +261,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -345,6 +347,7 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
           runAsUser: 1234
           seccompProfile:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml
index 74555834e..8707cd128 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml
@@ -350,7 +350,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml
index 708f586ff..128819593 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml
@@ -266,7 +266,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -340,7 +342,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml
index 10911bdec..aceabd683 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml
@@ -94,7 +94,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -168,7 +170,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml
index 558e51410..c4e4ef575 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml
@@ -263,7 +263,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -337,7 +339,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml
index bb6d7bceb..a368ec333 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml
@@ -261,7 +261,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -335,7 +337,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml
index 7df24d4b5..34d8438df 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml
@@ -261,7 +261,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -335,7 +337,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml
index 6fb612dad..aa6aba8b8 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml
@@ -261,7 +261,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -335,7 +337,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml
index cc863ceb2..a69ed0181 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml
@@ -261,7 +261,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -335,7 +337,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml
index 7c9798100..edbcf01d0 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml
@@ -97,7 +97,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -171,7 +173,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml
index b9076bc91..6ac7da412 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml
@@ -98,7 +98,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -172,7 +174,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml
index b770e10c5..7825fe6a5 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml
@@ -352,7 +352,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml
index 3b7f4c131..7c19b8404 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml
@@ -354,7 +354,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml
index 11fc90e2e..608be7b11 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml
@@ -351,7 +351,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml
index a1efff9d4..7ab09dcce 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml
@@ -265,7 +265,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -339,7 +341,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml
index cf2503cc1..8eba24a43 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml
@@ -236,7 +236,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -310,7 +312,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml
index 97f116c06..2cb179ea5 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml
@@ -355,7 +355,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml
index 3f5c31be4..a2ad5c780 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml
@@ -274,7 +274,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -348,7 +350,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml
index 725c4994a..7e742ac47 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml
@@ -265,7 +265,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -339,7 +341,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml
index 4c6726cb5..9abe8a6ca 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml
@@ -265,7 +265,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -349,6 +351,7 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
           runAsUser: 1234
           seccompProfile:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml
index 4c5819a87..b5fca7f2c 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml
@@ -355,7 +355,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml
index f0a9014ec..c96b3d7a3 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml
@@ -270,7 +270,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -344,7 +346,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml
index 2902e865b..3cafba405 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml
@@ -98,7 +98,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -172,7 +174,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml
index cc950e452..001c2c627 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml
@@ -264,7 +264,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -338,7 +340,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml
index d31d189e6..3a44833a2 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml
@@ -267,7 +267,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -341,7 +343,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml
index a60b4d216..ba938efc5 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml
@@ -265,7 +265,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -339,7 +341,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml
index 2dd44b08d..3b4404c54 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml
@@ -265,7 +265,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -339,7 +341,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml
index 235b1e992..f82880666 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml
@@ -265,7 +265,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -339,7 +341,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml
index 60fb354d5..1b3a530ce 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml
@@ -265,7 +265,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
@@ -339,7 +341,9 @@ spec:
             drop:
             - ALL
           privileged: false
+          runAsGroup: 65532
           runAsNonRoot: true
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         startupProbe:
diff --git a/internal/xds/server/runner/runner_test.go b/internal/xds/server/runner/runner_test.go
index 74bf30f2c..823d42686 100644
--- a/internal/xds/server/runner/runner_test.go
+++ b/internal/xds/server/runner/runner_test.go
@@ -10,6 +10,7 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"errors"
+	"io"
 	"net"
 	"os"
 	"path/filepath"
@@ -180,6 +181,10 @@ func peekError(conn net.Conn) error {
 	_ = conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
 	_, err := conn.Read(make([]byte, 1))
 	if err != nil {
+		if errors.Is(err, io.EOF) {
+			return nil
+		}
+
 		var netErr net.Error
 		if !errors.As(netErr, &netErr) || !netErr.Timeout() {
 			return err
diff --git a/internal/xds/translator/testdata/in/xds-ir/authorization.yaml b/internal/xds/translator/testdata/in/xds-ir/authorization-client-cidr.yaml
similarity index 100%
rename from internal/xds/translator/testdata/in/xds-ir/authorization.yaml
rename to internal/xds/translator/testdata/in/xds-ir/authorization-client-cidr.yaml
diff --git a/internal/xds/translator/testdata/in/xds-ir/authorization-jwt-claim.yaml b/internal/xds/translator/testdata/in/xds-ir/authorization-jwt-claim.yaml
new file mode 100644
index 000000000..f952f20cc
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/authorization-jwt-claim.yaml
@@ -0,0 +1,99 @@
+http:
+- address: 0.0.0.0
+  hostnames:
+  - '*'
+  isHTTP2: false
+  metadata:
+    kind: Gateway
+    name: gateway-1
+    namespace: envoy-gateway
+    sectionName: http
+  name: envoy-gateway/gateway-1/http
+  path:
+    escapedSlashesAction: UnescapeAndRedirect
+    mergeSlashes: true
+  port: 10080
+  routes:
+  - destination:
+      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
+    metadata:
+      kind: HTTPRoute
+      name: httproute-1
+      namespace: default
+    name: httproute/default/httproute-1/rule/0/match/0/www_example_com
+    pathMatch:
+      distinct: false
+      name: ""
+      prefix: /foo
+    security:
+      authorization:
+        defaultAction: Deny
+        rules:
+        - action: Deny
+          name: allow-claim-roles
+          principal:
+            jwt:
+              provider: example1
+              claims:
+              - name: user.name
+                values: ["alice", "bob"]
+      jwt:
+        providers:
+        - audiences:
+          - two.foo.com
+          issuer: https://two.example.com
+          name: example1
+          remoteJWKS:
+            uri: https://two.example.com/jwt/public-key/jwks.json
+  - 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
+    metadata:
+      kind: HTTPRoute
+      name: httproute-2
+      namespace: default
+    name: httproute/default/httproute-2/rule/0/match/0/www_example_com
+    pathMatch:
+      distinct: false
+      name: ""
+      prefix: /bar
+    security:
+      authorization:
+        defaultAction: Deny
+        rules:
+        - action: Deny
+          name: allow-claim-roles
+          principal:
+            jwt:
+              provider: example1
+              claims:
+              - name: roles
+                valueType: StringArray
+                values: ["admin", "superuser"]
+              - name: department
+                values: ["engineering"]
+      jwt:
+        providers:
+        - audiences:
+          - one.foo.com
+          issuer: https://one.example.com
+          name: example1
+          remoteJWKS:
+            uri: https://one.example.com/jwt/public-key/jwks.json
diff --git a/internal/xds/translator/testdata/in/xds-ir/authorization-jwt-scope.yaml b/internal/xds/translator/testdata/in/xds-ir/authorization-jwt-scope.yaml
new file mode 100644
index 000000000..332060147
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/authorization-jwt-scope.yaml
@@ -0,0 +1,95 @@
+http:
+- address: 0.0.0.0
+  hostnames:
+  - '*'
+  isHTTP2: false
+  metadata:
+    kind: Gateway
+    name: gateway-1
+    namespace: envoy-gateway
+    sectionName: http
+  name: envoy-gateway/gateway-1/http
+  path:
+    escapedSlashesAction: UnescapeAndRedirect
+    mergeSlashes: true
+  port: 10080
+  routes:
+  - destination:
+      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
+    metadata:
+      kind: HTTPRoute
+      name: httproute-1
+      namespace: default
+    name: httproute/default/httproute-1/rule/0/match/0/www_example_com
+    pathMatch:
+      distinct: false
+      name: ""
+      prefix: /foo
+    security:
+      authorization:
+        defaultAction: Deny
+        rules:
+        - action: Deny
+          name: allow-scope-foo-bar
+          principal:
+            jwt:
+              provider: example1
+              scopes:
+              - foo
+              - bar
+      jwt:
+        providers:
+        - audiences:
+          - two.foo.com
+          issuer: https://two.example.com
+          name: example1
+          remoteJWKS:
+            uri: https://two.example.com/jwt/public-key/jwks.json
+  - 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
+    metadata:
+      kind: HTTPRoute
+      name: httproute-2
+      namespace: default
+    name: httproute/default/httproute-2/rule/0/match/0/www_example_com
+    pathMatch:
+      distinct: false
+      name: ""
+      prefix: /bar
+    security:
+      authorization:
+        defaultAction: Deny
+        rules:
+        - action: Deny
+          name: allow-scope-foo
+          principal:
+            jwt:
+              provider: example1
+              scopes:
+              - foo
+      jwt:
+        providers:
+        - audiences:
+          - one.foo.com
+          issuer: https://one.example.com
+          name: example1
+          remoteJWKS:
+            uri: https://one.example.com/jwt/public-key/jwks.json
diff --git a/internal/xds/translator/testdata/in/xds-ir/authorization-multiple-principals.yaml b/internal/xds/translator/testdata/in/xds-ir/authorization-multiple-principals.yaml
new file mode 100644
index 000000000..c93708b4c
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/authorization-multiple-principals.yaml
@@ -0,0 +1,80 @@
+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-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: Deny
+        rules:
+        - action: Allow
+          name: allow-rule-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
+            jwt:
+              issuer: https://one.example.com
+              scopes:
+              - foo
+              claims:
+              - name: roles
+                valueType: StringArray
+                values: ["admin", "superuser"]
+              - name: department
+                values: ["engineering"]
+        - action: Allow
+          name: allow-rule-2
+          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
+            jwt:
+              issuer: https://two.example.com
+              scopes:
+              - for
+              - bar
+              claims:
+              - name: roles
+                valueType: StringArray
+                values: ["admin", "superuser"]
+              - name: name
+                values: ["alice", "bob"]
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.clusters.yaml
new file mode 100644
index 000000000..b3f75f0e0
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.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-client-cidr.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.endpoints.yaml
new file mode 100644
index 000000000..24596d841
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.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-client-cidr.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.listeners.yaml
new file mode 100644
index 000000000..907d28f78
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.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-10080
+        useRemoteAddress: true
+    name: envoy-gateway/gateway-1/http
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.routes.yaml
new file mode 100644
index 000000000..141d60a15
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-client-cidr.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/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.clusters.yaml
new file mode 100644
index 000000000..660d4f6b2
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.clusters.yaml
@@ -0,0 +1,106 @@
+- 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
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  dnsRefreshRate: 30s
+  lbPolicy: LEAST_REQUEST
+  loadAssignment:
+    clusterName: two_example_com_443
+    endpoints:
+    - lbEndpoints:
+      - endpoint:
+          address:
+            socketAddress:
+              address: two.example.com
+              portValue: 443
+        loadBalancingWeight: 1
+      loadBalancingWeight: 1
+      locality:
+        region: two_example_com_443/backend/0
+  name: two_example_com_443
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  respectDnsTtl: true
+  transportSocket:
+    name: envoy.transport_sockets.tls
+    typedConfig:
+      '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+      commonTlsContext:
+        validationContext:
+          trustedCa:
+            filename: /etc/ssl/certs/ca-certificates.crt
+      sni: two.example.com
+  type: STRICT_DNS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  dnsRefreshRate: 30s
+  lbPolicy: LEAST_REQUEST
+  loadAssignment:
+    clusterName: one_example_com_443
+    endpoints:
+    - lbEndpoints:
+      - endpoint:
+          address:
+            socketAddress:
+              address: one.example.com
+              portValue: 443
+        loadBalancingWeight: 1
+      loadBalancingWeight: 1
+      locality:
+        region: one_example_com_443/backend/0
+  name: one_example_com_443
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  respectDnsTtl: true
+  transportSocket:
+    name: envoy.transport_sockets.tls
+    typedConfig:
+      '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+      commonTlsContext:
+        validationContext:
+          trustedCa:
+            filename: /etc/ssl/certs/ca-certificates.crt
+      sni: one.example.com
+  type: STRICT_DNS
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.endpoints.yaml
new file mode 100644
index 000000000..05442a9a1
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.endpoints.yaml
@@ -0,0 +1,24 @@
+- 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-jwt-claim.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.listeners.yaml
new file mode 100644
index 000000000..f34bcbe36
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.listeners.yaml
@@ -0,0 +1,80 @@
+- 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.jwt_authn
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
+            providers:
+              httproute/default/httproute-1/rule/0/match/0/www_example_com/example1:
+                audiences:
+                - two.foo.com
+                forward: true
+                issuer: https://two.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example1
+                remoteJwks:
+                  asyncFetch: {}
+                  cacheDuration: 300s
+                  httpUri:
+                    cluster: two_example_com_443
+                    timeout: 10s
+                    uri: https://two.example.com/jwt/public-key/jwks.json
+                  retryPolicy: {}
+              httproute/default/httproute-2/rule/0/match/0/www_example_com/example1:
+                audiences:
+                - one.foo.com
+                forward: true
+                issuer: https://one.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example1
+                remoteJwks:
+                  asyncFetch: {}
+                  cacheDuration: 300s
+                  httpUri:
+                    cluster: one_example_com_443
+                    timeout: 10s
+                    uri: https://one.example.com/jwt/public-key/jwks.json
+                  retryPolicy: {}
+            requirementMap:
+              httproute/default/httproute-1/rule/0/match/0/www_example_com:
+                providerName: httproute/default/httproute-1/rule/0/match/0/www_example_com/example1
+              httproute/default/httproute-2/rule/0/match/0/www_example_com:
+                providerName: httproute/default/httproute-2/rule/0/match/0/www_example_com/example1
+        - 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-10080
+        useRemoteAddress: true
+    name: envoy-gateway/gateway-1/http
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.routes.yaml
new file mode 100644
index 000000000..5a465cded
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-claim.routes.yaml
@@ -0,0 +1,185 @@
+- ignorePortInHostMatching: true
+  name: envoy-gateway/gateway-1/http
+  virtualHosts:
+  - domains:
+    - www.example.com
+    metadata:
+      filterMetadata:
+        envoy-gateway:
+          resources:
+          - kind: Gateway
+            name: gateway-1
+            namespace: envoy-gateway
+            sectionName: http
+    name: envoy-gateway/gateway-1/http/www_example_com
+    routes:
+    - match:
+        pathSeparatedPrefix: /foo
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-1
+              namespace: default
+      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.jwt_authn:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
+          requirementName: httproute/default/httproute-1/rule/0/match/0/www_example_com
+        envoy.filters.http.rbac:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
+          rbac:
+            matcher:
+              matcherList:
+                matchers:
+                - onMatch:
+                    action:
+                      name: allow-claim-roles
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                        action: DENY
+                        name: DENY
+                  predicate:
+                    orMatcher:
+                      predicate:
+                      - singlePredicate:
+                          customMatch:
+                            name: claim_matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                              value:
+                                stringMatch:
+                                  exact: alice
+                          input:
+                            name: claim
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                              filter: envoy.filters.http.jwt_authn
+                              path:
+                              - key: example1
+                              - key: user
+                              - key: name
+                      - singlePredicate:
+                          customMatch:
+                            name: claim_matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                              value:
+                                stringMatch:
+                                  exact: bob
+                          input:
+                            name: claim
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                              filter: envoy.filters.http.jwt_authn
+                              path:
+                              - key: example1
+                              - key: user
+                              - key: name
+              onNoMatch:
+                action:
+                  name: default
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    action: DENY
+                    name: DENY
+    - match:
+        pathSeparatedPrefix: /bar
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-2
+              namespace: default
+      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.jwt_authn:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
+          requirementName: httproute/default/httproute-2/rule/0/match/0/www_example_com
+        envoy.filters.http.rbac:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
+          rbac:
+            matcher:
+              matcherList:
+                matchers:
+                - onMatch:
+                    action:
+                      name: allow-claim-roles
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                        action: DENY
+                        name: DENY
+                  predicate:
+                    andMatcher:
+                      predicate:
+                      - orMatcher:
+                          predicate:
+                          - singlePredicate:
+                              customMatch:
+                                name: claim_matcher
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                                  value:
+                                    listMatch:
+                                      oneOf:
+                                        stringMatch:
+                                          exact: admin
+                              input:
+                                name: claim
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                                  filter: envoy.filters.http.jwt_authn
+                                  path:
+                                  - key: example1
+                                  - key: roles
+                          - singlePredicate:
+                              customMatch:
+                                name: claim_matcher
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                                  value:
+                                    listMatch:
+                                      oneOf:
+                                        stringMatch:
+                                          exact: superuser
+                              input:
+                                name: claim
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                                  filter: envoy.filters.http.jwt_authn
+                                  path:
+                                  - key: example1
+                                  - key: roles
+                      - singlePredicate:
+                          customMatch:
+                            name: claim_matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                              value:
+                                stringMatch:
+                                  exact: engineering
+                          input:
+                            name: claim
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                              filter: envoy.filters.http.jwt_authn
+                              path:
+                              - key: example1
+                              - key: department
+              onNoMatch:
+                action:
+                  name: default
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    action: DENY
+                    name: DENY
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.clusters.yaml
new file mode 100644
index 000000000..660d4f6b2
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.clusters.yaml
@@ -0,0 +1,106 @@
+- 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
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  dnsRefreshRate: 30s
+  lbPolicy: LEAST_REQUEST
+  loadAssignment:
+    clusterName: two_example_com_443
+    endpoints:
+    - lbEndpoints:
+      - endpoint:
+          address:
+            socketAddress:
+              address: two.example.com
+              portValue: 443
+        loadBalancingWeight: 1
+      loadBalancingWeight: 1
+      locality:
+        region: two_example_com_443/backend/0
+  name: two_example_com_443
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  respectDnsTtl: true
+  transportSocket:
+    name: envoy.transport_sockets.tls
+    typedConfig:
+      '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+      commonTlsContext:
+        validationContext:
+          trustedCa:
+            filename: /etc/ssl/certs/ca-certificates.crt
+      sni: two.example.com
+  type: STRICT_DNS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  dnsRefreshRate: 30s
+  lbPolicy: LEAST_REQUEST
+  loadAssignment:
+    clusterName: one_example_com_443
+    endpoints:
+    - lbEndpoints:
+      - endpoint:
+          address:
+            socketAddress:
+              address: one.example.com
+              portValue: 443
+        loadBalancingWeight: 1
+      loadBalancingWeight: 1
+      locality:
+        region: one_example_com_443/backend/0
+  name: one_example_com_443
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  respectDnsTtl: true
+  transportSocket:
+    name: envoy.transport_sockets.tls
+    typedConfig:
+      '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+      commonTlsContext:
+        validationContext:
+          trustedCa:
+            filename: /etc/ssl/certs/ca-certificates.crt
+      sni: one.example.com
+  type: STRICT_DNS
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.endpoints.yaml
new file mode 100644
index 000000000..05442a9a1
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.endpoints.yaml
@@ -0,0 +1,24 @@
+- 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-jwt-scope.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.listeners.yaml
new file mode 100644
index 000000000..f34bcbe36
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.listeners.yaml
@@ -0,0 +1,80 @@
+- 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.jwt_authn
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
+            providers:
+              httproute/default/httproute-1/rule/0/match/0/www_example_com/example1:
+                audiences:
+                - two.foo.com
+                forward: true
+                issuer: https://two.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example1
+                remoteJwks:
+                  asyncFetch: {}
+                  cacheDuration: 300s
+                  httpUri:
+                    cluster: two_example_com_443
+                    timeout: 10s
+                    uri: https://two.example.com/jwt/public-key/jwks.json
+                  retryPolicy: {}
+              httproute/default/httproute-2/rule/0/match/0/www_example_com/example1:
+                audiences:
+                - one.foo.com
+                forward: true
+                issuer: https://one.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example1
+                remoteJwks:
+                  asyncFetch: {}
+                  cacheDuration: 300s
+                  httpUri:
+                    cluster: one_example_com_443
+                    timeout: 10s
+                    uri: https://one.example.com/jwt/public-key/jwks.json
+                  retryPolicy: {}
+            requirementMap:
+              httproute/default/httproute-1/rule/0/match/0/www_example_com:
+                providerName: httproute/default/httproute-1/rule/0/match/0/www_example_com/example1
+              httproute/default/httproute-2/rule/0/match/0/www_example_com:
+                providerName: httproute/default/httproute-2/rule/0/match/0/www_example_com/example1
+        - 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-10080
+        useRemoteAddress: true
+    name: envoy-gateway/gateway-1/http
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.routes.yaml
new file mode 100644
index 000000000..cfb4a62ec
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-jwt-scope.routes.yaml
@@ -0,0 +1,149 @@
+- ignorePortInHostMatching: true
+  name: envoy-gateway/gateway-1/http
+  virtualHosts:
+  - domains:
+    - www.example.com
+    metadata:
+      filterMetadata:
+        envoy-gateway:
+          resources:
+          - kind: Gateway
+            name: gateway-1
+            namespace: envoy-gateway
+            sectionName: http
+    name: envoy-gateway/gateway-1/http/www_example_com
+    routes:
+    - match:
+        pathSeparatedPrefix: /foo
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-1
+              namespace: default
+      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.jwt_authn:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
+          requirementName: httproute/default/httproute-1/rule/0/match/0/www_example_com
+        envoy.filters.http.rbac:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
+          rbac:
+            matcher:
+              matcherList:
+                matchers:
+                - onMatch:
+                    action:
+                      name: allow-scope-foo-bar
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                        action: DENY
+                        name: DENY
+                  predicate:
+                    andMatcher:
+                      predicate:
+                      - singlePredicate:
+                          customMatch:
+                            name: scope_matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                              value:
+                                listMatch:
+                                  oneOf:
+                                    stringMatch:
+                                      exact: foo
+                          input:
+                            name: scope
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                              filter: envoy.filters.http.jwt_authn
+                              path:
+                              - key: example1
+                              - key: scope
+                      - singlePredicate:
+                          customMatch:
+                            name: scope_matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                              value:
+                                listMatch:
+                                  oneOf:
+                                    stringMatch:
+                                      exact: bar
+                          input:
+                            name: scope
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                              filter: envoy.filters.http.jwt_authn
+                              path:
+                              - key: example1
+                              - key: scope
+              onNoMatch:
+                action:
+                  name: default
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    action: DENY
+                    name: DENY
+    - match:
+        pathSeparatedPrefix: /bar
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-2
+              namespace: default
+      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.jwt_authn:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
+          requirementName: httproute/default/httproute-2/rule/0/match/0/www_example_com
+        envoy.filters.http.rbac:
+          '@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
+          rbac:
+            matcher:
+              matcherList:
+                matchers:
+                - onMatch:
+                    action:
+                      name: allow-scope-foo
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                        action: DENY
+                        name: DENY
+                  predicate:
+                    singlePredicate:
+                      customMatch:
+                        name: scope_matcher
+                        typedConfig:
+                          '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                          value:
+                            listMatch:
+                              oneOf:
+                                stringMatch:
+                                  exact: foo
+                      input:
+                        name: scope
+                        typedConfig:
+                          '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                          filter: envoy.filters.http.jwt_authn
+                          path:
+                          - key: example1
+                          - key: scope
+              onNoMatch:
+                action:
+                  name: default
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    action: DENY
+                    name: DENY
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.clusters.yaml
new file mode 100644
index 000000000..9714612e3
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.clusters.yaml
@@ -0,0 +1,17 @@
+- 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
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.endpoints.yaml
new file mode 100644
index 000000000..29bb6b4e4
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.endpoints.yaml
@@ -0,0 +1,12 @@
+- 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
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.listeners.yaml
new file mode 100644
index 000000000..907d28f78
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.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-10080
+        useRemoteAddress: true
+    name: envoy-gateway/gateway-1/http
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.routes.yaml
new file mode 100644
index 000000000..c6510f637
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/authorization-multiple-principals.routes.yaml
@@ -0,0 +1,256 @@
+- 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: /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: allow-rule-1
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                        name: ALLOW
+                  predicate:
+                    andMatcher:
+                      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
+                      - singlePredicate:
+                          customMatch:
+                            name: scope_matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                              value:
+                                listMatch:
+                                  oneOf:
+                                    stringMatch:
+                                      exact: foo
+                          input:
+                            name: scope
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                              filter: envoy.filters.http.jwt_authn
+                              path:
+                              - key: ""
+                              - key: scope
+                      - orMatcher:
+                          predicate:
+                          - singlePredicate:
+                              customMatch:
+                                name: claim_matcher
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                                  value:
+                                    listMatch:
+                                      oneOf:
+                                        stringMatch:
+                                          exact: admin
+                              input:
+                                name: claim
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                                  filter: envoy.filters.http.jwt_authn
+                                  path:
+                                  - key: ""
+                                  - key: roles
+                          - singlePredicate:
+                              customMatch:
+                                name: claim_matcher
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                                  value:
+                                    listMatch:
+                                      oneOf:
+                                        stringMatch:
+                                          exact: superuser
+                              input:
+                                name: claim
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                                  filter: envoy.filters.http.jwt_authn
+                                  path:
+                                  - key: ""
+                                  - key: roles
+                      - singlePredicate:
+                          customMatch:
+                            name: claim_matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                              value:
+                                stringMatch:
+                                  exact: engineering
+                          input:
+                            name: claim
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                              filter: envoy.filters.http.jwt_authn
+                              path:
+                              - key: ""
+                              - key: department
+                - onMatch:
+                    action:
+                      name: allow-rule-2
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                        name: ALLOW
+                  predicate:
+                    andMatcher:
+                      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
+                      - singlePredicate:
+                          customMatch:
+                            name: scope_matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                              value:
+                                listMatch:
+                                  oneOf:
+                                    stringMatch:
+                                      exact: for
+                          input:
+                            name: scope
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                              filter: envoy.filters.http.jwt_authn
+                              path:
+                              - key: ""
+                              - key: scope
+                      - singlePredicate:
+                          customMatch:
+                            name: scope_matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                              value:
+                                listMatch:
+                                  oneOf:
+                                    stringMatch:
+                                      exact: bar
+                          input:
+                            name: scope
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                              filter: envoy.filters.http.jwt_authn
+                              path:
+                              - key: ""
+                              - key: scope
+                      - orMatcher:
+                          predicate:
+                          - singlePredicate:
+                              customMatch:
+                                name: claim_matcher
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                                  value:
+                                    listMatch:
+                                      oneOf:
+                                        stringMatch:
+                                          exact: admin
+                              input:
+                                name: claim
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                                  filter: envoy.filters.http.jwt_authn
+                                  path:
+                                  - key: ""
+                                  - key: roles
+                          - singlePredicate:
+                              customMatch:
+                                name: claim_matcher
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                                  value:
+                                    listMatch:
+                                      oneOf:
+                                        stringMatch:
+                                          exact: superuser
+                              input:
+                                name: claim
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                                  filter: envoy.filters.http.jwt_authn
+                                  path:
+                                  - key: ""
+                                  - key: roles
+                      - orMatcher:
+                          predicate:
+                          - singlePredicate:
+                              customMatch:
+                                name: claim_matcher
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                                  value:
+                                    stringMatch:
+                                      exact: alice
+                              input:
+                                name: claim
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                                  filter: envoy.filters.http.jwt_authn
+                                  path:
+                                  - key: ""
+                                  - key: name
+                          - singlePredicate:
+                              customMatch:
+                                name: claim_matcher
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
+                                  value:
+                                    stringMatch:
+                                      exact: bob
+                              input:
+                                name: claim
+                                typedConfig:
+                                  '@type': type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
+                                  filter: envoy.filters.http.jwt_authn
+                                  path:
+                                  - key: ""
+                                  - key: name
+              onNoMatch:
+                action:
+                  name: default
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.config.rbac.v3.Action
+                    action: DENY
+                    name: DENY
diff --git a/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.listeners.yaml
index 46157814e..0a50c1bb2 100644
--- a/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.listeners.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/custom-filter-order.listeners.yaml
@@ -73,7 +73,10 @@
                   headerName: one-route-example-key
                 forward: true
                 issuer: https://one.example.com
-                payloadInMetadata: https://one.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example1
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
@@ -90,7 +93,10 @@
                   headerName: two-route-example-key
                 forward: true
                 issuer: http://two.example.com
-                payloadInMetadata: http://two.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example2
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.listeners.yaml
index 524800c64..9172af851 100644
--- a/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.listeners.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.listeners.yaml
@@ -30,7 +30,10 @@
                 fromParams:
                 - token
                 issuer: https://www.example.com
-                payloadInMetadata: https://www.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-multi-route-multi-provider.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-multi-route-multi-provider.listeners.yaml
index 4c45106ab..bbfa00a53 100644
--- a/internal/xds/translator/testdata/out/xds-ir/jwt-multi-route-multi-provider.listeners.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/jwt-multi-route-multi-provider.listeners.yaml
@@ -26,7 +26,10 @@
                   headerName: one-route-example-key1
                 forward: true
                 issuer: https://www.example.com
-                payloadInMetadata: https://www.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
@@ -47,7 +50,10 @@
                 clearRouteCache: true
                 forward: true
                 issuer: https://www.two.example.com
-                payloadInMetadata: https://www.two.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example2
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
@@ -64,7 +70,10 @@
                   headerName: second-route-example-key1
                 forward: true
                 issuer: https://www.example.com
-                payloadInMetadata: https://www.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
@@ -79,7 +88,10 @@
                 - two.foo.com
                 forward: true
                 issuer: https://www.two.example.com
-                payloadInMetadata: https://www.two.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example2
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-multi-route-single-provider.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-multi-route-single-provider.listeners.yaml
index f247baa83..a48a09b2d 100644
--- a/internal/xds/translator/testdata/out/xds-ir/jwt-multi-route-single-provider.listeners.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/jwt-multi-route-single-provider.listeners.yaml
@@ -48,7 +48,10 @@
                   headerName: first-route-key
                 forward: true
                 issuer: https://www.example.com
-                payloadInMetadata: https://www.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
@@ -62,7 +65,10 @@
                 - foo.com
                 forward: true
                 issuer: https://www.example.com
-                payloadInMetadata: https://www.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-optional.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-optional.listeners.yaml
index 9906e8df6..51d778b9c 100644
--- a/internal/xds/translator/testdata/out/xds-ir/jwt-optional.listeners.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/jwt-optional.listeners.yaml
@@ -30,7 +30,10 @@
                 fromParams:
                 - token
                 issuer: https://www.example.com
-                payloadInMetadata: https://www.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-ratelimit.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-ratelimit.listeners.yaml
index a4520c65b..ead8b20d4 100644
--- a/internal/xds/translator/testdata/out/xds-ir/jwt-ratelimit.listeners.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/jwt-ratelimit.listeners.yaml
@@ -23,7 +23,10 @@
                 - foo.com
                 forward: true
                 issuer: https://www.example.com
-                payloadInMetadata: https://www.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-single-route-single-match.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-single-route-single-match.listeners.yaml
index 91280606b..c839763d9 100644
--- a/internal/xds/translator/testdata/out/xds-ir/jwt-single-route-single-match.listeners.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/jwt-single-route-single-match.listeners.yaml
@@ -23,7 +23,10 @@
                 - foo.com
                 forward: true
                 issuer: https://www.example.com
-                payloadInMetadata: https://www.example.com
+                normalizePayloadInMetadata:
+                  spaceDelimitedClaims:
+                  - scope
+                payloadInMetadata: example
                 remoteJwks:
                   asyncFetch: {}
                   cacheDuration: 300s
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index b716d7ad3..93d845f3d 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -240,7 +240,7 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `name` | _string_ |  false  | Name is a user-friendly name for the rule.<br />If not specified, Envoy Gateway will generate a unique name for the rule.n |
+| `name` | _string_ |  false  | Name is a user-friendly name for the rule.<br />If not specified, Envoy Gateway will generate a unique name for the rule. |
 | `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.<br />If there are multiple principal types, all principals must match for the rule to match.<br />For example, if there are two principals: one for client IP and one for JWT claim,<br />the rule will match only if both the client IP and the JWT claim match. |
 
@@ -852,6 +852,51 @@ _Appears in:_
 | `failClosed` | _boolean_ |  false  | FailClosed is a switch used to control the flow of traffic when client IP detection<br />fails. If set to true, the listener will respond with 403 Forbidden when the client<br />IP address cannot be determined. |
 
 
+#### CustomResponse
+
+
+
+CustomResponse defines the configuration for returning a custom response.
+
+_Appears in:_
+- [ResponseOverride](#responseoverride)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `contentType` | _string_ |  false  | Content Type of the response. This will be set in the Content-Type header. |
+| `body` | _[CustomResponseBody](#customresponsebody)_ |  true  | Body of the Custom Response |
+
+
+#### CustomResponseBody
+
+
+
+CustomResponseBody
+
+_Appears in:_
+- [CustomResponse](#customresponse)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `type` | _[ResponseValueType](#responsevaluetype)_ |  true  | Type is the type of method to use to read the body value. |
+| `inline` | _string_ |  false  | Inline contains the value as an inline string. |
+| `valueRef` | _[LocalObjectReference](#localobjectreference)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported. |
+
+
+#### CustomResponseMatch
+
+
+
+CustomResponseMatch defines the configuration for matching a user response to return a custom one.
+
+_Appears in:_
+- [ResponseOverride](#responseoverride)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `statusCode` | _[StatusCodeMatch](#statuscodematch) array_ |  true  | Status code to match on. The match evaluates to true if any of the matches are successful. |
+
+
 #### CustomTag
 
 
@@ -2295,8 +2340,9 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
+| `provider` | _string_ |  true  | Provider is the name of the JWT provider that used to verify the JWT token.<br />In order to use JWT claims for authorization, you must configure the JWT<br />authentication with the same provider in the same `SecurityPolicy`. |
 | `claims` | _[JWTClaim](#jwtclaim) array_ |  false  | Claims are the claims in a JWT token.<br /><br />If multiple claims are specified, all claims must match for the rule to match.<br />For example, if there are two claims: one for the audience and one for the issuer,<br />the rule will match only if both the audience and the issuer match. |
-| `scopes` | _string array_ |  false  | Scopes are a special type of claim in a JWT token that represents the permissions of the client.<br /><br />The value of the scopes field should be a space delimited string that is expected in the scope parameter,<br />as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749#page-23.<br /><br />If multiple scopes are specified, all scopes must match for the rule to match. |
+| `scopes` | _[JWTScope](#jwtscope) array_ |  false  | Scopes are a special type of claim in a JWT token that represents the permissions of the client.<br /><br />The value of the scopes field should be a space delimited string that is expected in the scope parameter,<br />as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749#page-23.<br /><br />If multiple scopes are specified, all scopes must match for the rule to match. |
 
 
 #### JWTProvider
@@ -2319,6 +2365,17 @@ _Appears in:_
 | `extractFrom` | _[JWTExtractor](#jwtextractor)_ |  false  | ExtractFrom defines different ways to extract the JWT token from HTTP request.<br />If empty, it defaults to extract JWT token from the Authorization HTTP request header using Bearer schema<br />or access_token from query parameters. |
 
 
+#### JWTScope
+
+_Underlying type:_ _string_
+
+
+
+_Appears in:_
+- [JWTPrincipal](#jwtprincipal)
+
+
+
 #### KubernetesContainerSpec
 
 
@@ -2807,10 +2864,7 @@ _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.
+If there are multiple principal types, all principals must match for the rule to match.
 
 _Appears in:_
 - [AuthorizationRule](#authorizationrule)
@@ -2946,6 +3000,21 @@ _Appears in:_
 | `OpenTelemetry` | ProxyAccessLogSinkTypeOpenTelemetry defines the OpenTelemetry accesslog sink.<br />When the provider is Kubernetes, EnvoyGateway always sends `k8s.namespace.name`<br />and `k8s.pod.name` as additional attributes.<br /> | 
 
 
+#### ProxyAccessLogType
+
+_Underlying type:_ _string_
+
+
+
+_Appears in:_
+- [ProxyAccessLogSetting](#proxyaccesslogsetting)
+
+| Value | Description |
+| ----- | ----------- |
+| `Listener` | ProxyAccessLogTypeListener defines the accesslog for Listeners.<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/listener/v3/listener.proto#envoy-v3-api-field-config-listener-v3-listener-access-log<br /> | 
+| `Route` | ProxyAccessLogTypeRoute defines the accesslog for HTTP, GRPC, UDP and TCP Routes.<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto#envoy-v3-api-field-extensions-filters-udp-udp-proxy-v3-udpproxyconfig-access-log<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto#envoy-v3-api-field-extensions-filters-network-tcp-proxy-v3-tcpproxy-access-log<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-access-log<br /> | 
+
+
 #### ProxyBootstrap
 
 
@@ -3426,6 +3495,32 @@ _Appears in:_
 | `File` | ResourceProviderTypeFile defines the "File" provider.<br /> | 
 
 
+#### ResponseOverride
+
+
+
+ResponseOverride defines the configuration to override specific responses with a custom one.
+
+_Appears in:_
+- [BackendTrafficPolicySpec](#backendtrafficpolicyspec)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `match` | _[CustomResponseMatch](#customresponsematch)_ |  true  | Match configuration. |
+| `response` | _[CustomResponse](#customresponse)_ |  true  | Response configuration. |
+
+
+#### ResponseValueType
+
+_Underlying type:_ _string_
+
+ResponseValueType defines the types of values for the response body supported by Envoy Gateway.
+
+_Appears in:_
+- [CustomResponseBody](#customresponsebody)
+
+
+
 
 
 #### RetryOn
@@ -3608,6 +3703,48 @@ _Appears in:_
 | `Distinct` | SourceMatchDistinct Each IP Address within the specified Source IP CIDR is treated as a distinct client selector<br />and uses a separate rate limit bucket/counter.<br />Note: This is only supported for Global Rate Limits.<br /> | 
 
 
+#### StatusCodeMatch
+
+
+
+
+
+_Appears in:_
+- [CustomResponseMatch](#customresponsematch)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `type` | _[StatusCodeValueType](#statuscodevaluetype)_ |  true  | Type is the type of value. |
+| `value` | _string_ |  false  | Value contains the value of the status code. |
+| `range` | _[StatusCodeRange](#statuscoderange)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported. |
+
+
+#### StatusCodeRange
+
+
+
+StatusCodeRange defines the configuration for define a range of status codes.
+
+_Appears in:_
+- [StatusCodeMatch](#statuscodematch)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `start` | _integer_ |  true  | Start of the range, including the start value. |
+| `end` | _integer_ |  true  | End of the range, including the end value. |
+
+
+#### StatusCodeValueType
+
+_Underlying type:_ _string_
+
+StatusCodeValueType defines the types of values for the status code match supported by Envoy Gateway.
+
+_Appears in:_
+- [StatusCodeMatch](#statuscodematch)
+
+
+
 #### StringMatch
 
 
diff --git a/site/content/en/latest/tasks/quickstart.md b/site/content/en/latest/tasks/quickstart.md
index 802b7989a..e1943c21e 100644
--- a/site/content/en/latest/tasks/quickstart.md
+++ b/site/content/en/latest/tasks/quickstart.md
@@ -12,7 +12,7 @@ A Kubernetes cluster.
 
 __Note:__ Refer to the [Compatibility Matrix](/news/releases/matrix) for supported Kubernetes versions.
 
-__Note:__ In case your Kubernetes cluster, does not have a LoadBalancer implementation, we recommend installing one
+__Note:__ In case your Kubernetes cluster does not have a LoadBalancer implementation, we recommend installing one
 so the `Gateway` resource has an Address associated with it. We recommend using [MetalLB](https://metallb.universe.tf/installation/).
 
 __Note:__ For Mac user, you need install and run [Docker Mac Net Connect](https://github.com/chipmk/docker-mac-net-connect) to make the Docker network work.
diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md
index b716d7ad3..93d845f3d 100644
--- a/site/content/zh/latest/api/extension_types.md
+++ b/site/content/zh/latest/api/extension_types.md
@@ -240,7 +240,7 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `name` | _string_ |  false  | Name is a user-friendly name for the rule.<br />If not specified, Envoy Gateway will generate a unique name for the rule.n |
+| `name` | _string_ |  false  | Name is a user-friendly name for the rule.<br />If not specified, Envoy Gateway will generate a unique name for the rule. |
 | `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.<br />If there are multiple principal types, all principals must match for the rule to match.<br />For example, if there are two principals: one for client IP and one for JWT claim,<br />the rule will match only if both the client IP and the JWT claim match. |
 
@@ -852,6 +852,51 @@ _Appears in:_
 | `failClosed` | _boolean_ |  false  | FailClosed is a switch used to control the flow of traffic when client IP detection<br />fails. If set to true, the listener will respond with 403 Forbidden when the client<br />IP address cannot be determined. |
 
 
+#### CustomResponse
+
+
+
+CustomResponse defines the configuration for returning a custom response.
+
+_Appears in:_
+- [ResponseOverride](#responseoverride)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `contentType` | _string_ |  false  | Content Type of the response. This will be set in the Content-Type header. |
+| `body` | _[CustomResponseBody](#customresponsebody)_ |  true  | Body of the Custom Response |
+
+
+#### CustomResponseBody
+
+
+
+CustomResponseBody
+
+_Appears in:_
+- [CustomResponse](#customresponse)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `type` | _[ResponseValueType](#responsevaluetype)_ |  true  | Type is the type of method to use to read the body value. |
+| `inline` | _string_ |  false  | Inline contains the value as an inline string. |
+| `valueRef` | _[LocalObjectReference](#localobjectreference)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported. |
+
+
+#### CustomResponseMatch
+
+
+
+CustomResponseMatch defines the configuration for matching a user response to return a custom one.
+
+_Appears in:_
+- [ResponseOverride](#responseoverride)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `statusCode` | _[StatusCodeMatch](#statuscodematch) array_ |  true  | Status code to match on. The match evaluates to true if any of the matches are successful. |
+
+
 #### CustomTag
 
 
@@ -2295,8 +2340,9 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
+| `provider` | _string_ |  true  | Provider is the name of the JWT provider that used to verify the JWT token.<br />In order to use JWT claims for authorization, you must configure the JWT<br />authentication with the same provider in the same `SecurityPolicy`. |
 | `claims` | _[JWTClaim](#jwtclaim) array_ |  false  | Claims are the claims in a JWT token.<br /><br />If multiple claims are specified, all claims must match for the rule to match.<br />For example, if there are two claims: one for the audience and one for the issuer,<br />the rule will match only if both the audience and the issuer match. |
-| `scopes` | _string array_ |  false  | Scopes are a special type of claim in a JWT token that represents the permissions of the client.<br /><br />The value of the scopes field should be a space delimited string that is expected in the scope parameter,<br />as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749#page-23.<br /><br />If multiple scopes are specified, all scopes must match for the rule to match. |
+| `scopes` | _[JWTScope](#jwtscope) array_ |  false  | Scopes are a special type of claim in a JWT token that represents the permissions of the client.<br /><br />The value of the scopes field should be a space delimited string that is expected in the scope parameter,<br />as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749#page-23.<br /><br />If multiple scopes are specified, all scopes must match for the rule to match. |
 
 
 #### JWTProvider
@@ -2319,6 +2365,17 @@ _Appears in:_
 | `extractFrom` | _[JWTExtractor](#jwtextractor)_ |  false  | ExtractFrom defines different ways to extract the JWT token from HTTP request.<br />If empty, it defaults to extract JWT token from the Authorization HTTP request header using Bearer schema<br />or access_token from query parameters. |
 
 
+#### JWTScope
+
+_Underlying type:_ _string_
+
+
+
+_Appears in:_
+- [JWTPrincipal](#jwtprincipal)
+
+
+
 #### KubernetesContainerSpec
 
 
@@ -2807,10 +2864,7 @@ _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.
+If there are multiple principal types, all principals must match for the rule to match.
 
 _Appears in:_
 - [AuthorizationRule](#authorizationrule)
@@ -2946,6 +3000,21 @@ _Appears in:_
 | `OpenTelemetry` | ProxyAccessLogSinkTypeOpenTelemetry defines the OpenTelemetry accesslog sink.<br />When the provider is Kubernetes, EnvoyGateway always sends `k8s.namespace.name`<br />and `k8s.pod.name` as additional attributes.<br /> | 
 
 
+#### ProxyAccessLogType
+
+_Underlying type:_ _string_
+
+
+
+_Appears in:_
+- [ProxyAccessLogSetting](#proxyaccesslogsetting)
+
+| Value | Description |
+| ----- | ----------- |
+| `Listener` | ProxyAccessLogTypeListener defines the accesslog for Listeners.<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/listener/v3/listener.proto#envoy-v3-api-field-config-listener-v3-listener-access-log<br /> | 
+| `Route` | ProxyAccessLogTypeRoute defines the accesslog for HTTP, GRPC, UDP and TCP Routes.<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto#envoy-v3-api-field-extensions-filters-udp-udp-proxy-v3-udpproxyconfig-access-log<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto#envoy-v3-api-field-extensions-filters-network-tcp-proxy-v3-tcpproxy-access-log<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-access-log<br /> | 
+
+
 #### ProxyBootstrap
 
 
@@ -3426,6 +3495,32 @@ _Appears in:_
 | `File` | ResourceProviderTypeFile defines the "File" provider.<br /> | 
 
 
+#### ResponseOverride
+
+
+
+ResponseOverride defines the configuration to override specific responses with a custom one.
+
+_Appears in:_
+- [BackendTrafficPolicySpec](#backendtrafficpolicyspec)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `match` | _[CustomResponseMatch](#customresponsematch)_ |  true  | Match configuration. |
+| `response` | _[CustomResponse](#customresponse)_ |  true  | Response configuration. |
+
+
+#### ResponseValueType
+
+_Underlying type:_ _string_
+
+ResponseValueType defines the types of values for the response body supported by Envoy Gateway.
+
+_Appears in:_
+- [CustomResponseBody](#customresponsebody)
+
+
+
 
 
 #### RetryOn
@@ -3608,6 +3703,48 @@ _Appears in:_
 | `Distinct` | SourceMatchDistinct Each IP Address within the specified Source IP CIDR is treated as a distinct client selector<br />and uses a separate rate limit bucket/counter.<br />Note: This is only supported for Global Rate Limits.<br /> | 
 
 
+#### StatusCodeMatch
+
+
+
+
+
+_Appears in:_
+- [CustomResponseMatch](#customresponsematch)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `type` | _[StatusCodeValueType](#statuscodevaluetype)_ |  true  | Type is the type of value. |
+| `value` | _string_ |  false  | Value contains the value of the status code. |
+| `range` | _[StatusCodeRange](#statuscoderange)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported. |
+
+
+#### StatusCodeRange
+
+
+
+StatusCodeRange defines the configuration for define a range of status codes.
+
+_Appears in:_
+- [StatusCodeMatch](#statuscodematch)
+
+| Field | Type | Required | Description |
+| ---   | ---  | ---      | ---         |
+| `start` | _integer_ |  true  | Start of the range, including the start value. |
+| `end` | _integer_ |  true  | End of the range, including the end value. |
+
+
+#### StatusCodeValueType
+
+_Underlying type:_ _string_
+
+StatusCodeValueType defines the types of values for the status code match supported by Envoy Gateway.
+
+_Appears in:_
+- [StatusCodeMatch](#statuscodematch)
+
+
+
 #### StringMatch
 
 
diff --git a/test/e2e/testdata/authorization-jwt.yaml b/test/e2e/testdata/authorization-jwt.yaml
new file mode 100644
index 000000000..5d3e31ea1
--- /dev/null
+++ b/test/e2e/testdata/authorization-jwt.yaml
@@ -0,0 +1,143 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-with-authorization-jwt-claim
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - matches:
+    - path:
+        type: Exact
+        value: /claim-test
+    backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-with-authorization-jwt-scope
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - matches:
+    - path:
+        type: Exact
+        value: /scope-test
+    backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-with-authorization-jwt-combined
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - matches:
+    - path:
+        type: Exact
+        value: /combined-test
+    backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: authorization-jwt-claim
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-with-authorization-jwt-claim
+  jwt:
+    providers:
+    - name: example
+      issuer: https://foo.bar.com
+      remoteJWKS:
+        uri: https://raw.githubusercontent.com/envoyproxy/gateway/refs/heads/main/examples/kubernetes/jwt/jwks.json
+  authorization:
+    defaultAction: Deny
+    rules:
+    - name: "allow"
+      action: Allow
+      principal:
+        jwt:
+          provider: example
+          claims:
+          - name: user.name
+            values: ["John Doe"]
+          - name: sub
+            values: ["1234567890"]
+          - name: user.roles
+            valueType: StringArray
+            values: ["admin"]
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: authorization-jwt-scope
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-with-authorization-jwt-scope
+  jwt:
+    providers:
+    - name: example
+      issuer: https://foo.bar.com
+      remoteJWKS:
+        uri: https://raw.githubusercontent.com/envoyproxy/gateway/refs/heads/main/examples/kubernetes/jwt/jwks.json
+  authorization:
+    defaultAction: Deny
+    rules:
+    - name: "allow"
+      action: Allow
+      principal:
+        jwt:
+          provider: example
+          scopes: ["read", "add", "modify"]
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: authorization-jwt-combined
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-with-authorization-jwt-combined
+  jwt:
+    providers:
+    - name: example
+      issuer: https://foo.bar.com
+      remoteJWKS:
+        uri: https://raw.githubusercontent.com/envoyproxy/gateway/refs/heads/main/examples/kubernetes/jwt/jwks.json
+  authorization:
+    defaultAction: Deny
+    rules:
+    - name: "allow"
+      action: Allow
+      principal:
+        jwt:
+          provider: example
+          scopes: ["read", "add", "modify"]
+          claims:
+          - name: user.name
+            values: ["John Doe"]
+          - name: sub
+            values: ["1234567890"]
+          - name: user.roles
+            valueType: StringArray
+            values: ["admin"]
diff --git a/test/e2e/tests/authorization_jwt.go b/test/e2e/tests/authorization_jwt.go
new file mode 100644
index 000000000..ff6872bc8
--- /dev/null
+++ b/test/e2e/tests/authorization_jwt.go
@@ -0,0 +1,186 @@
+// 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"
+	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+	gwapiv1a2 "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"
+	"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
+)
+
+//			{
+//		     "iss": "https://foo.bar.com",
+//			 "sub": "1234567890",
+//	      "user": {
+//	         "name": "John Doe",
+//	         "email": "john.doe@example.com",
+//	         "roles": ["admin", "editor"]
+//	      },
+//	      "premium_user": true,
+//			 "iat": 1516239022,
+//			 "scope": "read add delete modify"
+//			}
+//
+// nolint: gosec
+const (
+	jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImI1MjBiM2MyYzRiZDc1YTEwZTljZWJjOTU3NjkzM2RjIn0.eyJpc3MiOiJodHRwczovL2Zvby5iYXIuY29tIiwic3ViIjoiMTIzNDU2Nzg5MCIsInVzZXIiOnsibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huLmRvZUBleGFtcGxlLmNvbSIsInJvbGVzIjpbImFkbWluIiwiZWRpdG9yIl19LCJwcmVtaXVtX3VzZXIiOnRydWUsImlhdCI6MTUxNjIzOTAyMiwic2NvcGUiOiJyZWFkIGFkZCBkZWxldGUgbW9kaWZ5In0.P36iAlmiRCC79OiB3vstF5Q_9OqUYAMGF3a3H492GlojbV6DcuOz8YIEYGsRSWc-BNJaBKlyvUKsKsGVPtYbbF8ajwZTs64wyO-zhd2R8riPkg_HsW7iwGswV12f5iVRpfQ4AG2owmdOToIaoch0aym89He1ZzEjcShr9olgqlAbbmhnk-namd1rP-xpzPnWhhIVI3mCz5hYYgDTMcM7qbokM5FzFttTRXAn5_Luor23U1062Ct_K53QArwxBvwJ-QYiqcBycHf-hh6sMx_941cUswrZucCpa-EwA3piATf9PKAyeeWHfHV9X-y8ipGOFg3mYMMVBuUZ1lBkJCik9f9kboRY6QzpOISARQj9PKMXfxZdIPNuGmA7msSNAXQgqkvbx04jMwb9U7eCEdGZztH4C8LhlRjgj0ZdD7eNbRjeH2F6zrWyMUpGWaWyq6rMuP98W2DWM5ZflK6qvT1c7FuFsWPvWLkgxQwTWQKrHdKwdbsu32Sj8VtUBJ0-ddEb"
+	//		{
+	//	     "iss": "https://foo.bar.com",
+	//		 "sub": "1234567890",
+	//       "user": {
+	//          "name": "Alice Smith",
+	//          "email": "alice.smith@example.com",
+	//          "roles": ["developer"]
+	//       },
+	//       "premium_user": false,
+	//		 "iat": 1516239022,
+	//		 "scope": "read add delete"
+	//		}
+	//
+	// nolint: gosec
+	jwtTokenWithoutRequiredValues = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImI1MjBiM2MyYzRiZDc1YTEwZTljZWJjOTU3NjkzM2RjIn0.eyJpc3MiOiJodHRwczovL2Zvby5iYXIuY29tIiwic3ViIjoiMTIzNDU2Nzg5MCIsInVzZXIiOnsibmFtZSI6IkFsaWNlIFNtaXRoIiwiZW1haWwiOiJhbGljZS5zbWl0aEBleGFtcGxlLmNvbSIsInJvbGVzIjpbImRldmVsb3BlciJdfSwicHJlbWl1bV91c2VyIjpmYWxzZSwiaWF0IjoxNTE2MjM5MDIyLCJzY29wZSI6InJlYWQgYWRkIGRlbGV0ZSJ9.Da547nNXzuQXm5E7LuLAiyFswXsW4RDhuitD_rpadtR7PTwzzOsJoqrVWJ_u1jJDaOTWIpLF4gwxDoY-Aoz_couzXzlAbECLs45ZFoc_UdffpfIbGKqTZx8VtwKuDLFsAeDDDqqx1flxFhvXHftJJdZYr1FgFz9u-absMmRU90DLmEZX3Hnyc8k8eBgeiu6vsWUD0-aNy8cWkFRbwRggkGmucFyUTG8Z1MY3iyH5E66W-ISoX8G9bzE9PTxVAAPDTvefD5iLJPSDJ8qV69OuMCJ8Dczq0L9Dd_w0sF-D1s9MTvexmGg4zBWluJ3r-pU9NHEdhqBypehp_yH8xF5Rt9AE7stZ4oPFZNyfrtkE-4IOnSEkMmzcC65g_rscn0ycerv4N5ZNpkr0x2IYYM4iGuo-ULv5Htnli3rffST45kx1XA8cdsrT1D0K3aPxdIxDIk8sTJf5-WVqRyo-bwxXXltwQLB9jCM_7QbTWQBYAJwUpi-0RW4jCl44-42gZnXf"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, AuthorizationJWTTest)
+}
+
+var AuthorizationJWTTest = suite.ConformanceTest{
+	ShortName:   "Authorization with jwt claims and scopes",
+	Description: "Authorization with jwt claims and scopes",
+	Manifests:   []string{"testdata/authorization-jwt.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-infra"
+		route1NN := types.NamespacedName{Name: "http-with-authorization-jwt-claim", Namespace: ns}
+		route2NN := types.NamespacedName{Name: "http-with-authorization-jwt-scope", Namespace: ns}
+		route3NN := types.NamespacedName{Name: "http-with-authorization-jwt-combined", 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, route3NN)
+
+		ancestorRef := gwapiv1a2.ParentReference{
+			Group:     gatewayapi.GroupPtr(gwapiv1.GroupName),
+			Kind:      gatewayapi.KindPtr(resource.KindGateway),
+			Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+			Name:      gwapiv1.ObjectName(gwNN.Name),
+		}
+		SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-jwt-claim", Namespace: ns}, suite.ControllerName, ancestorRef)
+		SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-jwt-scope", Namespace: ns}, suite.ControllerName, ancestorRef)
+		SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "authorization-jwt-combined", Namespace: ns}, suite.ControllerName, ancestorRef)
+
+		t.Run("allow requests with jwt claims", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/claim-test",
+					Headers: map[string]string{
+						"Authorization": "Bearer " + jwtToken,
+					},
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+		})
+
+		t.Run("deny requests with jwt claims that do not match the required claim value", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/claim-test",
+					Headers: map[string]string{
+						"Authorization": "Bearer " + jwtTokenWithoutRequiredValues,
+					},
+				},
+				Response: http.Response{
+					StatusCode: 403,
+				},
+				Namespace: ns,
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+		})
+
+		t.Run("allow requests with jwt scopes", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/scope-test",
+					Headers: map[string]string{
+						"Authorization": "Bearer " + jwtToken,
+					},
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+		})
+
+		t.Run("deny requests with jwt scopes that do not match the required scope value", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/scope-test",
+					Headers: map[string]string{
+						"Authorization": "Bearer " + jwtTokenWithoutRequiredValues,
+					},
+				},
+				Response: http.Response{
+					StatusCode: 403,
+				},
+				Namespace: ns,
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+		})
+
+		t.Run("allow requests with jwt claims and scopes", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/combined-test",
+					Headers: map[string]string{
+						"Authorization": "Bearer " + jwtToken,
+					},
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+		})
+
+		t.Run("deny requests with jwt scopes and claims that do not match the required scope and claim values", func(t *testing.T) {
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/combined-test",
+					Headers: map[string]string{
+						"Authorization": "Bearer " + jwtTokenWithoutRequiredValues,
+					},
+				},
+				Response: http.Response{
+					StatusCode: 403,
+				},
+				Namespace: ns,
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+		})
+	},
+}
diff --git a/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml b/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml
index c280e54ca..096e1eb55 100644
--- a/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml
+++ b/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml
@@ -429,6 +429,8 @@ spec:
             - ALL
           privileged: false
           runAsNonRoot: true
+          runAsGroup: 65532
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         volumeMounts:
diff --git a/test/helm/gateway-helm/control-plane-with-pdb.out.yaml b/test/helm/gateway-helm/control-plane-with-pdb.out.yaml
index 3db52f2bc..4c9a3d6cf 100644
--- a/test/helm/gateway-helm/control-plane-with-pdb.out.yaml
+++ b/test/helm/gateway-helm/control-plane-with-pdb.out.yaml
@@ -444,6 +444,8 @@ spec:
             - ALL
           privileged: false
           runAsNonRoot: true
+          runAsGroup: 65532
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         volumeMounts:
diff --git a/test/helm/gateway-helm/default-config.out.yaml b/test/helm/gateway-helm/default-config.out.yaml
index 0dd66b5c2..c830348f0 100644
--- a/test/helm/gateway-helm/default-config.out.yaml
+++ b/test/helm/gateway-helm/default-config.out.yaml
@@ -429,6 +429,8 @@ spec:
             - ALL
           privileged: false
           runAsNonRoot: true
+          runAsGroup: 65532
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         volumeMounts:
diff --git a/test/helm/gateway-helm/deployment-custom-topology.out.yaml b/test/helm/gateway-helm/deployment-custom-topology.out.yaml
index b9fc66251..fd468b505 100644
--- a/test/helm/gateway-helm/deployment-custom-topology.out.yaml
+++ b/test/helm/gateway-helm/deployment-custom-topology.out.yaml
@@ -457,6 +457,8 @@ spec:
             - ALL
           privileged: false
           runAsNonRoot: true
+          runAsGroup: 65532
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         volumeMounts:
diff --git a/test/helm/gateway-helm/deployment-images-config.out.yaml b/test/helm/gateway-helm/deployment-images-config.out.yaml
index 4da6c9a57..aa5a36ff2 100644
--- a/test/helm/gateway-helm/deployment-images-config.out.yaml
+++ b/test/helm/gateway-helm/deployment-images-config.out.yaml
@@ -429,6 +429,8 @@ spec:
             - ALL
           privileged: false
           runAsNonRoot: true
+          runAsGroup: 65532
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         volumeMounts:
diff --git a/test/helm/gateway-helm/envoy-gateway-config.out.yaml b/test/helm/gateway-helm/envoy-gateway-config.out.yaml
index 056a0bde7..aa91dacec 100644
--- a/test/helm/gateway-helm/envoy-gateway-config.out.yaml
+++ b/test/helm/gateway-helm/envoy-gateway-config.out.yaml
@@ -431,6 +431,8 @@ spec:
             - ALL
           privileged: false
           runAsNonRoot: true
+          runAsGroup: 65532
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         volumeMounts:
diff --git a/test/helm/gateway-helm/global-images-config.out.yaml b/test/helm/gateway-helm/global-images-config.out.yaml
index 5eea20550..e18eecd7b 100644
--- a/test/helm/gateway-helm/global-images-config.out.yaml
+++ b/test/helm/gateway-helm/global-images-config.out.yaml
@@ -433,6 +433,8 @@ spec:
             - ALL
           privileged: false
           runAsNonRoot: true
+          runAsGroup: 65532
+          runAsUser: 65532
           seccompProfile:
             type: RuntimeDefault
         volumeMounts: