Repository: envoyproxy/gateway
Pull request #5171 (2025-01-28 18:54:52)

feat: implement Lua EnvoyExtensionPolicy
**What type of PR is this?**
<!--
Your PR title should be descriptive, and generally start with type that contains a subsystem name with `()` if necessary
and summary followed by a colon. format `chore/docs/api/feat/fix/refactor/style/test: summary`.
Examples:
* "docs: fix grammar error"
* "feat(translator): add new feature"
* "fix: fix xx bug"
* "chore: change ci & build tools etc"
* "api: add xxx fields in ClientTrafficPolicy"
-->
 feat: implement Lua feature in EnvoyExtensionPolicy

<!--
NOTE: If your PR contains any API changes (changes under `/api`), we recommend you to separate these API changes into
a new PR, and we will review the API part first. It will save you lots of implementation time if the API get accepted.
-->

**What this PR does / why we need it**:
Implement API introduced in #4932

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Related #4627 

<!--
For any non-trivial changes, you need to provide a brief description of the changes in the release notes.
Please add the description to the release-notes/current.yaml file and include this file in the PR.
-->
Release Notes: No


rudrakhp: @arkodg @zhaohuabing Wanted to get an initial buy in regarding the approach while I add more unit tests and E2E, please do review. Thanks!
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/5171?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
Attention: Patch coverage is `28.00000%` with `108 lines` in your changes missing coverage. Please review.
> Project coverage is 66.69%. Comparing base [(`f7a10eb`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/f7a10eb1177b8d9e7f424f0eb2a563ed5ba45d6a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`93c9c44`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/93c9c449043bbb4617781fb33252ac9555e7005b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).
> Report is 2 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/envoyproxy/gateway/pull/5171?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) | Patch % | Lines |
|---|---|---|
| [internal/gatewayapi/envoyextensionpolicy.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5171?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Fenvoyextensionpolicy.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9lbnZveWV4dGVuc2lvbnBvbGljeS5nbw==) | 10.00% | [51 Missing and 3 partials :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5171?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/xds/translator/lua.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5171?src=pr&el=tree&filepath=internal%2Fxds%2Ftranslator%2Flua.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwveGRzL3RyYW5zbGF0b3IvbHVhLmdv) | 22.72% | [48 Missing and 3 partials :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5171?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/gatewayapi/luavalidator/lua\_validator.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5171?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Fluavalidator%2Flua_validator.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9sdWF2YWxpZGF0b3IvbHVhX3ZhbGlkYXRvci5nbw==) | 87.50% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5171?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |

:x: Your patch check has failed because the patch coverage (28.00%) is below the target coverage (60.00%). You can increase the patch coverage or adjust the [target](https://docs.codecov.com/docs/commit-status#target) coverage.

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


```diff
@@            Coverage Diff             @@
##             main    #5171      +/-   ##
==========================================
- Coverage   66.85%   66.69%   -0.17%     
==========================================
  Files         210      212       +2     
  Lines       32979    33129     +150     
==========================================
+ Hits        22047    22094      +47     
- Misses       9592     9689      +97     
- Partials     1340     1346       +6     
```

</details>

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

arkodg: hey @rudrakhp the code looks great ! 

please feel free to continue with unit tests and e2e
rudrakhp: > gateway/internal/xds/translator/httpfilters.go

Thanks for the comments @zhaohuabing, wanted to understand the rationale behind the ordering of the filters. Although the comments explained the high level thought process, didn't get why there is a huge gap of order between ExtProc and WASM, considering these two (along with Lua) are similar filters. IMO it might make sense to execute Lua first as they are light weight compared to ExtProc or WASM, WDYT?
rudrakhp: >Lua filter has built-in perRoute support, so we won't need to create a seperate lua filter for each route.

Yes I started with LuaPerRoute filter but finally followed the generic framework we have for WASM and ExtProc, adding all Lua filters and enabling them as required per route.
zhaohuabing: > IMO it makes sense to execute Lua first as they are light weight compared to ExtProc or WASM, WDYT?

Agree. This order is adjustable if users want to change the default order in the filter chain.

Patch:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index d26e933b0..c08a6eb0b 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -36,14 +36,14 @@ jobs:
     - uses: ./tools/github-actions/setup-deps
 
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a  # v3.28.8
+      uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0  # v3.28.9
       with:
         languages: ${{ matrix.language }}
 
     - name: Autobuild
-      uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a  # v3.28.8
+      uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0  # v3.28.9
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a  # v3.28.8
+      uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0  # v3.28.9
       with:
         category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 10fbaa3b4..c098731e9 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -40,6 +40,6 @@ jobs:
         retention-days: 5
 
     - name: "Upload to code-scanning"
-      uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a  # v3.28.8
+      uses: github/codeql-action/upload-sarif@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0  # v3.28.9
       with:
         sarif_file: results.sarif
diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go
index 8b9c83e17..a423d3fb2 100644
--- a/api/v1alpha1/envoyproxy_types.go
+++ b/api/v1alpha1/envoyproxy_types.go
@@ -114,6 +114,8 @@ type EnvoyProxySpec struct {
 	//
 	// - envoy.filters.http.stateful_session
 	//
+	// - envoy.filters.http.lua
+	//
 	// - envoy.filters.http.ext_proc
 	//
 	// - envoy.filters.http.wasm
@@ -195,7 +197,7 @@ type FilterPosition struct {
 }
 
 // EnvoyFilter defines the type of Envoy HTTP filter.
-// +kubebuilder:validation:Enum=envoy.filters.http.health_check;envoy.filters.http.fault;envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.api_key_auth;envoy.filters.http.basic_auth;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.stateful_session;envoy.filters.http.ext_proc;envoy.filters.http.wasm;envoy.filters.http.rbac;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit;envoy.filters.http.custom_response;envoy.filters.http.compressor
+// +kubebuilder:validation:Enum=envoy.filters.http.health_check;envoy.filters.http.fault;envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.api_key_auth;envoy.filters.http.basic_auth;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.stateful_session;envoy.filters.http.lua;envoy.filters.http.ext_proc;envoy.filters.http.wasm;envoy.filters.http.rbac;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit;envoy.filters.http.custom_response;envoy.filters.http.compressor
 type EnvoyFilter string
 
 const (
@@ -233,6 +235,9 @@ const (
 	// EnvoyFilterWasm defines the Envoy HTTP WebAssembly filter.
 	EnvoyFilterWasm EnvoyFilter = "envoy.filters.http.wasm"
 
+	// EnvoyFilterLua defines the Envoy HTTP Lua filter.
+	EnvoyFilterLua EnvoyFilter = "envoy.filters.http.lua"
+
 	// EnvoyFilterRBAC defines the Envoy RBAC filter.
 	EnvoyFilterRBAC EnvoyFilter = "envoy.filters.http.rbac"
 
diff --git a/api/v1alpha1/lua_types.go b/api/v1alpha1/lua_types.go
index 40e1bd966..9c9388006 100644
--- a/api/v1alpha1/lua_types.go
+++ b/api/v1alpha1/lua_types.go
@@ -41,7 +41,7 @@ type Lua struct {
 	// The value of key `lua` in the ConfigMap will be used.
 	// If the key is not found, the first value in the ConfigMap will be used.
 	//
-	// +kubebuilder:validation:XValidation:rule="self.kind == 'ConfigMap' && (!has(self.group) || self.group == '')",message="Only a reference to an object of kind ConfigMap belonging to default core API group is supported."
+	// +kubebuilder:validation:XValidation:rule="self.kind == 'ConfigMap' && (self.group == 'v1' || self.group == '')",message="Only a reference to an object of kind ConfigMap belonging to default v1 API group is supported."
 	// +optional
 	// +unionMember
 	ValueRef *gwapiv1.LocalObjectReference `json:"valueRef,omitempty"`
diff --git a/api/v1alpha1/policy_helpers.go b/api/v1alpha1/policy_helpers.go
index 054ca72f7..8c4c9762a 100644
--- a/api/v1alpha1/policy_helpers.go
+++ b/api/v1alpha1/policy_helpers.go
@@ -6,6 +6,7 @@
 package v1alpha1
 
 import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 )
@@ -37,7 +38,14 @@ type TargetSelector struct {
 	Kind gwapiv1.Kind `json:"kind"`
 
 	// MatchLabels are the set of label selectors for identifying the targeted resource
-	MatchLabels map[string]string `json:"matchLabels"`
+	// +optional
+	MatchLabels map[string]string `json:"matchLabels,omitempty"`
+
+	// MatchExpressions is a list of label selector requirements. The requirements are ANDed.
+	//
+	// +optional
+	// +listType=atomic
+	MatchExpressions []metav1.LabelSelectorRequirement `json:"matchExpressions,omitempty"`
 }
 
 func (p PolicyTargetReferences) GetTargetRefs() []gwapiv1a2.LocalPolicyTargetReferenceWithSectionName {
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index f26bbf457..1090e34f9 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -5673,6 +5673,13 @@ func (in *TargetSelector) DeepCopyInto(out *TargetSelector) {
 			(*out)[key] = val
 		}
 	}
+	if in.MatchExpressions != nil {
+		in, out := &in.MatchExpressions, &out.MatchExpressions
+		*out = make([]metav1.LabelSelectorRequirement, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetSelector.
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 6808e7992..187df4482 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -1508,6 +1508,39 @@ spec:
                       minLength: 1
                       pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
                       type: string
+                    matchExpressions:
+                      description: MatchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: |-
+                          A label selector requirement is a selector that contains values, a key, and an operator that
+                          relates the key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: |-
+                              operator represents a key's relationship to a set of values.
+                              Valid operators are In, NotIn, Exists and DoesNotExist.
+                            type: string
+                          values:
+                            description: |-
+                              values is an array of string values. If the operator is In or NotIn,
+                              the values array must be non-empty. If the operator is Exists or DoesNotExist,
+                              the values array must be empty. This array is replaced during a strategic
+                              merge patch.
+                            items:
+                              type: string
+                            type: array
+                            x-kubernetes-list-type: atomic
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                      x-kubernetes-list-type: atomic
                     matchLabels:
                       additionalProperties:
                         type: string
@@ -1516,7 +1549,6 @@ spec:
                       type: object
                   required:
                   - kind
-                  - matchLabels
                   type: object
                   x-kubernetes-validations:
                   - message: group must be gateway.networking.k8s.io
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
index ebeba3ae1..8d19d3c01 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
@@ -608,6 +608,39 @@ spec:
                       minLength: 1
                       pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
                       type: string
+                    matchExpressions:
+                      description: MatchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: |-
+                          A label selector requirement is a selector that contains values, a key, and an operator that
+                          relates the key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: |-
+                              operator represents a key's relationship to a set of values.
+                              Valid operators are In, NotIn, Exists and DoesNotExist.
+                            type: string
+                          values:
+                            description: |-
+                              values is an array of string values. If the operator is In or NotIn,
+                              the values array must be non-empty. If the operator is Exists or DoesNotExist,
+                              the values array must be empty. This array is replaced during a strategic
+                              merge patch.
+                            items:
+                              type: string
+                            type: array
+                            x-kubernetes-list-type: atomic
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                      x-kubernetes-list-type: atomic
                     matchLabels:
                       additionalProperties:
                         type: string
@@ -616,7 +649,6 @@ spec:
                       type: object
                   required:
                   - kind
-                  - matchLabels
                   type: object
                   x-kubernetes-validations:
                   - message: group must be gateway.networking.k8s.io
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
index 6036db826..a213b2904 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
@@ -1069,8 +1069,8 @@ spec:
                       type: object
                       x-kubernetes-validations:
                       - message: Only a reference to an object of kind ConfigMap belonging
-                          to default core API group is supported.
-                        rule: self.kind == 'ConfigMap' && (!has(self.group) || self.group
+                          to default v1 API group is supported.
+                        rule: self.kind == 'ConfigMap' && (self.group == 'v1' || self.group
                           == '')
                   required:
                   - type
@@ -1201,6 +1201,39 @@ spec:
                       minLength: 1
                       pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
                       type: string
+                    matchExpressions:
+                      description: MatchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: |-
+                          A label selector requirement is a selector that contains values, a key, and an operator that
+                          relates the key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: |-
+                              operator represents a key's relationship to a set of values.
+                              Valid operators are In, NotIn, Exists and DoesNotExist.
+                            type: string
+                          values:
+                            description: |-
+                              values is an array of string values. If the operator is In or NotIn,
+                              the values array must be non-empty. If the operator is Exists or DoesNotExist,
+                              the values array must be empty. This array is replaced during a strategic
+                              merge patch.
+                            items:
+                              type: string
+                            type: array
+                            x-kubernetes-list-type: atomic
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                      x-kubernetes-list-type: atomic
                     matchLabels:
                       additionalProperties:
                         type: string
@@ -1209,7 +1242,6 @@ spec:
                       type: object
                   required:
                   - kind
-                  - matchLabels
                   type: object
                   x-kubernetes-validations:
                   - message: group must be gateway.networking.k8s.io
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 391fb3147..5e2bfab7d 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
@@ -300,6 +300,8 @@ spec:
 
                   - envoy.filters.http.stateful_session
 
+                  - envoy.filters.http.lua
+
                   - envoy.filters.http.ext_proc
 
                   - envoy.filters.http.wasm
@@ -333,6 +335,7 @@ spec:
                       - envoy.filters.http.oauth2
                       - envoy.filters.http.jwt_authn
                       - envoy.filters.http.stateful_session
+                      - envoy.filters.http.lua
                       - envoy.filters.http.ext_proc
                       - envoy.filters.http.wasm
                       - envoy.filters.http.rbac
@@ -355,6 +358,7 @@ spec:
                       - envoy.filters.http.oauth2
                       - envoy.filters.http.jwt_authn
                       - envoy.filters.http.stateful_session
+                      - envoy.filters.http.lua
                       - envoy.filters.http.ext_proc
                       - envoy.filters.http.wasm
                       - envoy.filters.http.rbac
@@ -375,6 +379,7 @@ spec:
                       - envoy.filters.http.oauth2
                       - envoy.filters.http.jwt_authn
                       - envoy.filters.http.stateful_session
+                      - envoy.filters.http.lua
                       - envoy.filters.http.ext_proc
                       - envoy.filters.http.wasm
                       - envoy.filters.http.rbac
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 bc228f30f..56e1583ba 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
@@ -4447,6 +4447,39 @@ spec:
                       minLength: 1
                       pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
                       type: string
+                    matchExpressions:
+                      description: MatchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: |-
+                          A label selector requirement is a selector that contains values, a key, and an operator that
+                          relates the key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: |-
+                              operator represents a key's relationship to a set of values.
+                              Valid operators are In, NotIn, Exists and DoesNotExist.
+                            type: string
+                          values:
+                            description: |-
+                              values is an array of string values. If the operator is In or NotIn,
+                              the values array must be non-empty. If the operator is Exists or DoesNotExist,
+                              the values array must be empty. This array is replaced during a strategic
+                              merge patch.
+                            items:
+                              type: string
+                            type: array
+                            x-kubernetes-list-type: atomic
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                      x-kubernetes-list-type: atomic
                     matchLabels:
                       additionalProperties:
                         type: string
@@ -4455,7 +4488,6 @@ spec:
                       type: object
                   required:
                   - kind
-                  - matchLabels
                   type: object
                   x-kubernetes-validations:
                   - message: group must be gateway.networking.k8s.io
diff --git a/examples/extension-server/go.mod b/examples/extension-server/go.mod
index 156f1a9e1..f6e45acdb 100644
--- a/examples/extension-server/go.mod
+++ b/examples/extension-server/go.mod
@@ -5,7 +5,7 @@ go 1.23.6
 require (
 	github.com/envoyproxy/gateway v1.0.2
 	github.com/envoyproxy/go-control-plane v0.13.4
-	github.com/envoyproxy/go-control-plane/envoy v1.32.3
+	github.com/envoyproxy/go-control-plane/envoy v1.32.4
 	github.com/urfave/cli/v2 v2.27.5
 	google.golang.org/grpc v1.70.0
 	google.golang.org/protobuf v1.36.5
@@ -18,7 +18,7 @@ require (
 	cel.dev/expr v0.19.0 // indirect
 	github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
-	github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
+	github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
diff --git a/examples/extension-server/go.sum b/examples/extension-server/go.sum
index a931cc492..f49101043 100644
--- a/examples/extension-server/go.sum
+++ b/examples/extension-server/go.sum
@@ -11,10 +11,10 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
 github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
-github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ2iYBtGxP6luyLrx6uOk=
-github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE=
-github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
-github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
+github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
+github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
 github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
 github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
 github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
@@ -63,8 +63,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
diff --git a/go.mod b/go.mod
index 1e7bccefe..7a53d2371 100644
--- a/go.mod
+++ b/go.mod
@@ -13,11 +13,11 @@ require (
 	github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
 	github.com/docker/cli v27.5.1+incompatible
-	github.com/docker/docker v27.5.0+incompatible
+	github.com/docker/docker v27.5.1+incompatible
 	github.com/dominikbraun/graph v0.23.0
 	github.com/envoyproxy/go-control-plane v0.13.4
-	github.com/envoyproxy/go-control-plane/contrib v1.32.3
-	github.com/envoyproxy/go-control-plane/envoy v1.32.3
+	github.com/envoyproxy/go-control-plane/contrib v1.32.4
+	github.com/envoyproxy/go-control-plane/envoy v1.32.4
 	github.com/envoyproxy/go-control-plane/ratelimit v0.1.0
 	github.com/envoyproxy/ratelimit v1.4.1-0.20230427142404-e2a87f41d3a7
 	github.com/evanphx/json-patch v5.9.0+incompatible
@@ -27,6 +27,9 @@ require (
 	github.com/go-logfmt/logfmt v0.6.0
 	github.com/go-logr/logr v1.4.2
 	github.com/go-logr/zapr v1.3.0
+	github.com/go-openapi/spec v0.21.0
+	github.com/go-openapi/strfmt v0.23.0
+	github.com/go-openapi/validate v0.24.0
 	github.com/golang/protobuf v1.5.4
 	github.com/google/cel-go v0.22.1
 	github.com/google/go-cmp v0.6.0
@@ -40,11 +43,12 @@ require (
 	github.com/prometheus/common v0.62.0
 	github.com/replicatedhq/troubleshoot v0.107.5
 	github.com/spf13/cobra v1.8.1
-	github.com/spf13/pflag v1.0.5
+	github.com/spf13/pflag v1.0.6
 	github.com/stretchr/testify v1.10.0
 	github.com/telepresenceio/watchable v0.0.0-20220726211108-9bb86f92afa7
 	github.com/tetratelabs/func-e v1.1.5-0.20240822223546-c85a098d5bf0
 	github.com/tsaarni/certyaml v0.10.0
+	github.com/yuin/gopher-lua v1.1.1
 	go.opentelemetry.io/otel v1.34.0
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0
@@ -69,6 +73,7 @@ require (
 	k8s.io/cli-runtime v0.32.1
 	k8s.io/client-go v0.32.1
 	k8s.io/klog/v2 v2.130.1
+	k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7
 	k8s.io/kubectl v0.32.1
 	k8s.io/utils v0.0.0-20241210054802-24370beab758
 	sigs.k8s.io/controller-runtime v0.20.1
@@ -130,7 +135,7 @@ require (
 	github.com/docker/go-metrics v0.0.1 // indirect
 	github.com/docker/go-units v0.5.0 // indirect
 	github.com/emicklei/go-restful/v3 v3.12.0 // indirect
-	github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
+	github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
 	github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
@@ -139,8 +144,11 @@ require (
 	github.com/go-gorp/gorp/v3 v3.1.0 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-ole/go-ole v1.3.0 // indirect
+	github.com/go-openapi/analysis v0.23.0 // indirect
+	github.com/go-openapi/errors v0.22.0 // indirect
 	github.com/go-openapi/jsonpointer v0.21.0 // indirect
 	github.com/go-openapi/jsonreference v0.21.0 // indirect
+	github.com/go-openapi/loads v0.22.0 // indirect
 	github.com/go-openapi/swag v0.23.0 // indirect
 	github.com/go-redis/redis/v7 v7.4.1 // indirect
 	github.com/go-sql-driver/mysql v1.8.1 // indirect
@@ -211,6 +219,7 @@ require (
 	github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
+	github.com/oklog/ulid v1.3.1 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.1.0 // indirect
 	github.com/opencontainers/runtime-spec v1.2.0 // indirect
@@ -257,6 +266,7 @@ require (
 	go.etcd.io/etcd/api/v3 v3.5.16 // indirect
 	go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect
 	go.etcd.io/etcd/client/v3 v3.5.16 // indirect
+	go.mongodb.org/mongo-driver v1.14.0 // indirect
 	go.opencensus.io v0.24.0 // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
@@ -282,7 +292,6 @@ require (
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	k8s.io/apiserver v0.32.1 // indirect
 	k8s.io/component-base v0.32.1 // indirect
-	k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
 	k8s.io/metrics v0.32.1 // indirect
 	oras.land/oras-go v1.2.6 // indirect
 	periph.io/x/host/v3 v3.8.2 // indirect
diff --git a/go.sum b/go.sum
index 14b901efb..de41d7a71 100644
--- a/go.sum
+++ b/go.sum
@@ -192,8 +192,8 @@ github.com/docker/cli v27.5.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.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U=
-github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8=
+github.com/docker/docker v27.5.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=
@@ -226,15 +226,15 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
 github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
-github.com/envoyproxy/go-control-plane/contrib v1.32.3 h1:tDoJiLSnxeSIwd5Plpd/kJa7V9VP1Al+avyzjOGo97s=
-github.com/envoyproxy/go-control-plane/contrib v1.32.3/go.mod h1:HMK/OApOHoma+0RnR4oxmK4PeiUpwdu2r6qVSfwsLHg=
-github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ2iYBtGxP6luyLrx6uOk=
-github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE=
+github.com/envoyproxy/go-control-plane/contrib v1.32.4 h1:/udV6s9xkDGe13WfrT2MHAxXTNDMBYBPxI1GkleCrmM=
+github.com/envoyproxy/go-control-plane/contrib v1.32.4/go.mod h1:gkGYoY7plfQg7FPBDhyKtP1cDA9frFR/3YsCx8taRvI=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
 github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
 github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
-github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
+github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
+github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
 github.com/envoyproxy/ratelimit v1.4.1-0.20230427142404-e2a87f41d3a7 h1:yz9/p/8QVPuEjPqRfZDXJmRaURKpKkxCZXUhl22i+cU=
 github.com/envoyproxy/ratelimit v1.4.1-0.20230427142404-e2a87f41d3a7/go.mod h1:NmJBO+gDMvSQWvcSWq8wmlgkDmHHAkx1SCxEGva5hKU=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -293,9 +293,13 @@ github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpR
 github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
 github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
+github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
+github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
 github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
+github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
+github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
 github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
 github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
@@ -315,6 +319,8 @@ github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf
 github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
 github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
+github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
+github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
 github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
 github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
 github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
@@ -323,10 +329,14 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd
 github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
 github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
+github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
 github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
 github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
+github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
+github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
 github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
 github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
@@ -337,6 +347,8 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ
 github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
 github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
 github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
+github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
+github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
 github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
 github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
 github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
@@ -634,6 +646,7 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/ohler55/ojg v1.26.1 h1:J5TaLmVEuvnpVH7JMdT1QdbpJU545Yp6cKiCO4aQILc=
 github.com/ohler55/ojg v1.26.1/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -780,8 +793,9 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
@@ -865,6 +879,8 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
 github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
+github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
 github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
 github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -890,6 +906,8 @@ go.etcd.io/etcd/server/v3 v3.5.16/go.mod h1:ynhyZZpdDp1Gq49jkUg5mfkDWZwXnn3eIqCq
 go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
+go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go
index 145662e54..320d54120 100644
--- a/internal/gatewayapi/envoyextensionpolicy.go
+++ b/internal/gatewayapi/envoyextensionpolicy.go
@@ -20,9 +20,11 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
 	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/gatewayapi/luavalidator"
 	"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
 	"github.com/envoyproxy/gateway/internal/gatewayapi/status"
 	"github.com/envoyproxy/gateway/internal/ir"
@@ -293,6 +295,7 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute(
 ) error {
 	var (
 		wasms     []ir.Wasm
+		luas      []ir.Lua
 		err, errs error
 	)
 
@@ -301,6 +304,11 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute(
 		errs = errors.Join(errs, err)
 	}
 
+	if luas, err = t.buildLuas(policy, resources); err != nil {
+		err = perr.WithMessage(err, "Lua")
+		errs = errors.Join(errs, err)
+	}
+
 	// Apply IR to all relevant routes
 	prefix := irRoutePrefix(route)
 	parentRefs := GetParentReferences(route)
@@ -332,6 +340,7 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute(
 						r.EnvoyExtensions = &ir.EnvoyExtensionFeatures{
 							ExtProcs: extProcs,
 							Wasms:    wasms,
+							Luas:     luas,
 						}
 					}
 				}
@@ -352,6 +361,7 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(
 	var (
 		extProcs  []ir.ExtProc
 		wasms     []ir.Wasm
+		luas      []ir.Lua
 		err, errs error
 	)
 
@@ -363,6 +373,10 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(
 		err = perr.WithMessage(err, "Wasm")
 		errs = errors.Join(errs, err)
 	}
+	if luas, err = t.buildLuas(policy, resources); err != nil {
+		err = perr.WithMessage(err, "Lua")
+		errs = errors.Join(errs, err)
+	}
 
 	irKey := t.getIRKey(gateway.Gateway)
 	// Should exist since we've validated this
@@ -395,6 +409,7 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(
 			r.EnvoyExtensions = &ir.EnvoyExtensionFeatures{
 				ExtProcs: extProcs,
 				Wasms:    wasms,
+				Luas:     luas,
 			}
 		}
 	}
@@ -402,6 +417,72 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(
 	return errs
 }
 
+func (t *Translator) buildLuas(policy *egv1a1.EnvoyExtensionPolicy, resources *resource.Resources) ([]ir.Lua, error) {
+	var luaIRList []ir.Lua
+
+	if policy == nil {
+		return nil, nil
+	}
+
+	for idx, ep := range policy.Spec.Lua {
+		name := irConfigNameForLua(policy, idx)
+		luaIR, err := t.buildLua(name, policy, ep, resources)
+		if err != nil {
+			return nil, err
+		}
+		luaIRList = append(luaIRList, *luaIR)
+	}
+	return luaIRList, nil
+}
+
+func (t *Translator) buildLua(
+	name string,
+	policy *egv1a1.EnvoyExtensionPolicy,
+	lua egv1a1.Lua,
+	resources *resource.Resources,
+) (*ir.Lua, error) {
+	var luaCode *string
+	var err error
+	if lua.Type == egv1a1.LuaValueTypeValueRef {
+		luaCode, err = getLuaBodyFromLocalObjectReference(lua.ValueRef, resources, policy.Namespace)
+	} else {
+		luaCode = lua.Inline
+	}
+	if err != nil {
+		return nil, err
+	}
+	if err = luavalidator.NewLuaValidator(*luaCode).Validate(); err != nil {
+		return nil, fmt.Errorf("validation failed for lua body in policy with name %v: %w", name, err)
+	}
+	return &ir.Lua{
+		Name: name,
+		Code: luaCode,
+	}, nil
+}
+
+// getLuaBodyFromLocalObjectReference assumes the local object reference points to a Kubernetes ConfigMap
+func getLuaBodyFromLocalObjectReference(valueRef *gwapiv1.LocalObjectReference, resources *resource.Resources, policyNs string) (*string, error) {
+	cm := resources.GetConfigMap(policyNs, string(valueRef.Name))
+	if cm != nil {
+		b, dataOk := cm.Data["lua"]
+		switch {
+		case dataOk:
+			return &b, nil
+		case len(cm.Data) > 0: // Fallback to the first key if lua is not found
+			for _, value := range cm.Data {
+				b = value
+				break
+			}
+			return &b, nil
+		default:
+			return nil, fmt.Errorf("can't find the key lua in the referenced configmap %s", valueRef.Name)
+		}
+
+	} else {
+		return nil, fmt.Errorf("can't find the referenced configmap %s in namespace %s", valueRef.Name, policyNs)
+	}
+}
+
 func (t *Translator) buildExtProcs(policy *egv1a1.EnvoyExtensionPolicy, resources *resource.Resources, envoyProxy *egv1a1.EnvoyProxy) ([]ir.ExtProc, error) {
 	var extProcIRList []ir.ExtProc
 
@@ -522,6 +603,13 @@ func irConfigNameForExtProc(policy *egv1a1.EnvoyExtensionPolicy, index int) stri
 		strconv.Itoa(index))
 }
 
+func irConfigNameForLua(policy *egv1a1.EnvoyExtensionPolicy, index int) string {
+	return fmt.Sprintf(
+		"%s/lua/%s",
+		irConfigName(policy),
+		strconv.Itoa(index))
+}
+
 func (t *Translator) buildWasms(
 	policy *egv1a1.EnvoyExtensionPolicy,
 	resources *resource.Resources,
diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go
index 416ce9808..0ef3dd4d8 100644
--- a/internal/gatewayapi/helpers.go
+++ b/internal/gatewayapi/helpers.go
@@ -533,10 +533,22 @@ type targetRefWithTimestamp struct {
 	CreationTimestamp metav1.Time
 }
 
+func selectorFromTargetSelector(selector egv1a1.TargetSelector) labels.Selector {
+	l, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
+		MatchLabels:      selector.MatchLabels,
+		MatchExpressions: selector.MatchExpressions,
+	})
+	if err != nil {
+		// TODO - how do we we bubble this up
+		return labels.Nothing()
+	}
+	return l
+}
+
 func getPolicyTargetRefs[T client.Object](policy egv1a1.PolicyTargetReferences, potentialTargets []T) []gwapiv1a2.LocalPolicyTargetReferenceWithSectionName {
 	dedup := sets.New[targetRefWithTimestamp]()
 	for _, currSelector := range policy.TargetSelectors {
-		labelSelector := labels.SelectorFromSet(currSelector.MatchLabels)
+		labelSelector := selectorFromTargetSelector(currSelector)
 		for _, obj := range potentialTargets {
 			gvk := obj.GetObjectKind().GroupVersionKind()
 			if gvk.Kind != string(currSelector.Kind) ||
diff --git a/internal/gatewayapi/luavalidator/lua_validator.go b/internal/gatewayapi/luavalidator/lua_validator.go
new file mode 100644
index 000000000..11694fbbe
--- /dev/null
+++ b/internal/gatewayapi/luavalidator/lua_validator.go
@@ -0,0 +1,60 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package luavalidator
+
+import (
+	_ "embed"
+	"fmt"
+	"strings"
+
+	lua "github.com/yuin/gopher-lua"
+)
+
+// mockData contains mocks of Envoy supported APIs for Lua filters.
+// Refer: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter#stream-handle-api
+//
+//go:embed mocks.lua
+var mockData []byte
+
+// LuaValidator validates user provided Lua for compatibility with Envoy supported Lua HTTP filter
+type LuaValidator struct {
+	code string
+}
+
+// NewLuaValidator returns a LuaValidator for user provided Lua code
+func NewLuaValidator(code string) *LuaValidator {
+	return &LuaValidator{
+		code: code,
+	}
+}
+
+// Validate runs all validations for the LuaValidator
+func (l *LuaValidator) Validate() error {
+	if !strings.Contains(l.code, "envoy_on_request") && !strings.Contains(l.code, "envoy_on_response") {
+		return fmt.Errorf("expected one of envoy_on_request() or envoy_on_response() to be defined")
+	}
+	if strings.Contains(l.code, "envoy_on_request") {
+		if err := l.runLua(string(mockData) + "\n" + l.code + "\nenvoy_on_request(StreamHandle)"); err != nil {
+			return fmt.Errorf("failed to mock run envoy_on_request: %w", err)
+		}
+	}
+	if strings.Contains(l.code, "envoy_on_response") {
+		if err := l.runLua(string(mockData) + "\n" + l.code + "\nenvoy_on_response(StreamHandle)"); err != nil {
+			return fmt.Errorf("failed to mock run envoy_on_response: %w", err)
+		}
+	}
+	return nil
+}
+
+// runLua interprets and runs the provided Lua code in runtime
+func (l *LuaValidator) runLua(code string) error {
+	L := lua.NewState()
+	defer L.Close()
+	if err := L.DoString(code); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/internal/gatewayapi/luavalidator/mocks.lua b/internal/gatewayapi/luavalidator/mocks.lua
new file mode 100644
index 000000000..4cb136322
--- /dev/null
+++ b/internal/gatewayapi/luavalidator/mocks.lua
@@ -0,0 +1,613 @@
+-- ParsedName object.
+local ParsedName = {}
+
+-- Logging methods.
+function ParsedName:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function ParsedName:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function ParsedName:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function ParsedName:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function ParsedName:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function ParsedName:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+-- Method to return the common name.
+function ParsedName:commonName()
+  return "common_name"
+end
+
+-- Method to return the organization name.
+function ParsedName:organizationName()
+  return "organization_name"
+end
+
+local SSLConnectionObject = {}
+
+-- Logging methods
+function SSLConnectionObject:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function SSLConnectionObject:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function SSLConnectionObject:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function SSLConnectionObject:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function SSLConnectionObject:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function SSLConnectionObject:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+-- SSL-related methods
+function SSLConnectionObject:peerCertificatePresented()
+  return true -- Simulate that the peer certificate is always presented
+end
+
+function SSLConnectionObject:peerCertificateValidated()
+  return false -- Simulate no validation for TLS session resumption
+end
+
+function SSLConnectionObject:uriSanLocalCertificate()
+  local t = {}
+  for i = 1, 1000 do
+    t[i] = "san"
+  end
+  return t
+end
+
+function SSLConnectionObject:sha256PeerCertificateDigest()
+  return "abcdef1234567890" -- Simulated SHA256 digest
+end
+
+function SSLConnectionObject:serialNumberPeerCertificate()
+  return "123456789" -- Simulated serial number
+end
+
+function SSLConnectionObject:issuerPeerCertificate()
+  return "CN=Mock Issuer, O=Example Org" -- Simulated issuer
+end
+
+function SSLConnectionObject:subjectPeerCertificate()
+  return "CN=Mock Subject, O=Example Org" -- Simulated subject
+end
+
+function SSLConnectionObject:parsedSubjectPeerCertificate()
+  return {
+    commonName = function() return "Mock Subject" end,
+    organizationName = function() return "Example Org" end,
+  }
+end
+
+function SSLConnectionObject:uriSanPeerCertificate()
+  return {"uri1", "uri2"} -- Example SAN URIs for peer certificate
+end
+
+function SSLConnectionObject:subjectLocalCertificate()
+  return "CN=Local Cert, O=Example Org" -- Simulated local certificate subject
+end
+
+function SSLConnectionObject:urlEncodedPemEncodedPeerCertificate()
+  return "PEM_ENCODED_PEER_CERT" -- Simulated PEM encoding
+end
+
+function SSLConnectionObject:urlEncodedPemEncodedPeerCertificateChain()
+  return "PEM_ENCODED_CERT_CHAIN" -- Simulated PEM chain
+end
+
+function SSLConnectionObject:dnsSansPeerCertificate()
+  local t = {}
+    for i = 1, 1000 do
+      t[i] = "example.com"
+    end
+  return t
+end
+
+function SSLConnectionObject:dnsSansLocalCertificate()
+  local t = {}
+    for i = 1, 1000 do
+      t[i] = "local.com"
+    end
+  return t
+end
+
+function SSLConnectionObject:oidsPeerCertificate()
+  local t = {}
+    for i = 1, 1000 do
+      t[i] = "1.2.840.113549.1.1.1"
+    end
+  return t
+end
+
+function SSLConnectionObject:oidsLocalCertificate()
+  local t = {}
+    for i = 1, 1000 do
+      t[i] = "1.2.840.113549.1.1.1"
+    end
+  return t
+end
+
+function SSLConnectionObject:validFromPeerCertificate()
+  return 1672531200
+end
+
+function SSLConnectionObject:expirationPeerCertificate()
+  return 2724608000
+end
+
+function SSLConnectionObject:sessionId()
+  return "abcdef1234567890abcdef1234567890"
+end
+
+function SSLConnectionObject:ciphersuiteId()
+  return "0x1301"
+end
+
+function SSLConnectionObject:ciphersuiteString()
+  return "TLS_AES_128_GCM_SHA256"
+end
+
+function SSLConnectionObject:tlsVersion()
+  return "TLSv1.3"
+end
+
+-- Connection object
+local Connection = {}
+
+-- Logging methods for Connection
+function Connection:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function Connection:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function Connection:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function Connection:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function Connection:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function Connection:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+function Connection:ssl()
+  return SSLConnectionObject
+end
+
+local DynamicMetadata = {
+  data = {
+    key1 = "value1",
+    key2 = "value2"
+  }
+}
+
+setmetatable(DynamicMetadata, {
+  __pairs = function(self)
+    return pairs(self.data)
+  end,
+  __index = function(self, key)
+    return self.data[key]
+  end
+})
+
+-- Logging methods
+function DynamicMetadata:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function DynamicMetadata:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function DynamicMetadata:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function DynamicMetadata:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function DynamicMetadata:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function DynamicMetadata:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+function DynamicMetadata:get(filterName)
+  return "get_result"
+end
+
+function DynamicMetadata:set(filterName, key, value)
+end
+
+-- ConnectionStreamInfo Object
+local ConnectionStreamInfo = {}
+
+-- Logging methods for ConnectionStreamInfo
+function ConnectionStreamInfo:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function ConnectionStreamInfo:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function ConnectionStreamInfo:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function ConnectionStreamInfo:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function ConnectionStreamInfo:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function ConnectionStreamInfo:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+function ConnectionStreamInfo:dynamicMetadata()
+  return DynamicMetadata
+end
+
+-- StreamInfo Object
+local StreamInfo = {}
+
+function StreamInfo:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function StreamInfo:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function StreamInfo:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function StreamInfo:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function StreamInfo:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function StreamInfo:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+function StreamInfo:protocol()
+  return "HTTP/2"
+end
+
+function StreamInfo:routeName()
+  return "example-route"
+end
+
+function StreamInfo:virtualClusterName()
+  return "example-virtual-cluster"
+end
+
+function StreamInfo:downstreamDirectLocalAddress()
+  return "127.0.0.1:8080"
+end
+
+function StreamInfo:downstreamLocalAddress()
+  return "192.168.1.100:8080"
+end
+
+function StreamInfo:downstreamDirectRemoteAddress()
+  return "192.168.1.50:5050"
+end
+
+function StreamInfo:downstreamRemoteAddress()
+  return "10.0.0.1:5050"
+end
+
+function StreamInfo:dynamicMetadata()
+  return DynamicMetadata
+end
+
+function StreamInfo:downstreamSslConnection()
+  return SSLConnectionObject
+end
+
+function StreamInfo:requestedServerName()
+  return "example-server"
+end
+
+-- Metadata Object
+local Metadata = {
+  data = {
+    key1 = "value1",
+    key2 = "value2"
+  }
+}
+
+setmetatable(Metadata, {
+  __pairs = function(self)
+    return pairs(self.data)
+  end,
+  __index = function(self, key)
+    return self.data[key]
+  end
+})
+
+-- Logging Methods
+function Metadata:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function Metadata:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function Metadata:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function Metadata:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function Metadata:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function Metadata:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+function Metadata:get(key)
+  return "get_example"
+end
+
+-- Buffer Object
+local Buffer = {}
+
+-- Logging Methods
+function Buffer:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function Buffer:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function Buffer:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function Buffer:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function Buffer:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function Buffer:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+function Buffer:length()
+  return 10
+end
+
+function Buffer:getBytes(index, length)
+  return "example_bytes"
+end
+
+function Buffer:setBytes(setTo)
+end
+
+-- Headers Object
+local Headers = {
+  data = {
+    key1 = "value1",
+    key2 = "value2"
+  }
+}
+
+setmetatable(Headers, {
+  __pairs = function(self)
+    return pairs(self.data)
+  end,
+  __index = function(self, key)
+    return self.data[key]
+  end
+})
+
+-- Logging Methods
+function Headers:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function Headers:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function Headers:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function Headers:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function Headers:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function Headers:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+function Headers:add(key, value)
+end
+
+function Headers:get(key)
+  return "example_value"
+end
+
+function Headers:getAtIndex(key, index)
+  return "example_value"
+end
+
+function Headers:getNumValues(key)
+  return 1
+end
+
+function Headers:remove(key)
+end
+
+function Headers:replace(key, value)
+end
+
+function Headers:setHttp1ReasonPhrase(reasonPhrase)
+end
+
+function Headers:getHttp1ReasonPhrase()
+  return "reason"
+end
+
+-- StreamHandle Object
+local StreamHandle = {
+data = {Buffer}
+}
+
+-- Logging Methods (with internal logging mechanism)
+function StreamHandle:logTrace(message)
+  print("[TRACE]: " .. message)
+end
+
+function StreamHandle:logDebug(message)
+  print("[DEBUG]: " .. message)
+end
+
+function StreamHandle:logInfo(message)
+  print("[INFO]: " .. message)
+end
+
+function StreamHandle:logWarn(message)
+  print("[WARN]: " .. message)
+end
+
+function StreamHandle:logErr(message)
+  print("[ERROR]: " .. message)
+end
+
+function StreamHandle:logCritical(message)
+  print("[CRITICAL]: " .. message)
+end
+
+function StreamHandle:headers()
+  return Headers
+end
+
+function StreamHandle:body(always_wrap_body)
+  return "body"
+end
+
+function StreamHandle:bodyChunks()
+  local index = 0
+  local chunks = self.data or {}
+
+  return function()
+    index = index + 1
+    local chunk = chunks[index]
+    if chunk then
+      return chunk
+    end
+  end
+end
+
+function StreamHandle:trailers()
+  return Headers
+end
+
+function StreamHandle:httpCall(cluster, headers, body, timeout_ms, asynchronous)
+  self:logInfo("Making HTTP call to cluster: " .. cluster)
+  return headers, body
+end
+
+function StreamHandle:respond(headers, body)
+  self:logInfo("Responding with status: " .. headers[":status"])
+end
+
+function StreamHandle:metadata()
+  return Metadata
+end
+
+function StreamHandle:streamInfo()
+  return StreamInfo
+end
+
+function StreamHandle:connection()
+  return Connection
+end
+
+function StreamHandle:connectionStreamInfo()
+  return ConnectionStreamInfo
+end
+
+function StreamHandle:setUpstreamOverrideHost(host, strict)
+end
+
+function StreamHandle:importPublicKey(keyder, keyderLength)
+  return "mocked_public_key"
+end
+
+function StreamHandle:verifySignature(hashFunction, pubkey, signature, signatureLength, data, dataLength)
+  return true, ""
+end
+
+function StreamHandle:base64Escape(inputString)
+  return inputString
+end
+
+function StreamHandle:timestamp(format)
+  return os.time() * 1000
+end
+
+function StreamHandle:timestampString(resolution)
+  return os.date("!%Y-%m-%dT%H:%M:%SZ", os.time())
+end
\ No newline at end of file
diff --git a/internal/gatewayapi/resource/defaulter.go b/internal/gatewayapi/resource/defaulter.go
new file mode 100644
index 000000000..9a380c48e
--- /dev/null
+++ b/internal/gatewayapi/resource/defaulter.go
@@ -0,0 +1,270 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package resource
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"path"
+	"sort"
+	"strings"
+
+	gospec "github.com/go-openapi/spec"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/validate"
+	"github.com/go-openapi/validate/post"
+	"golang.org/x/exp/maps"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/client-go/openapi"
+	"k8s.io/kube-openapi/pkg/spec3"
+	kubespec "k8s.io/kube-openapi/pkg/validation/spec"
+	"sigs.k8s.io/kubectl-validate/pkg/openapiclient"
+	"sigs.k8s.io/kubectl-validate/pkg/utils"
+	"sigs.k8s.io/kubectl-validate/pkg/validator"
+)
+
+// This file contains code derived from kubectl-validate,
+// https://github.com/kubernetes-sigs/kubectl-validate
+// from the source file
+// https://github.com/kubernetes-sigs/kubectl-validate/blob/main/pkg/validator/validator.go
+// and is provided here subject to the following:
+// Copyright Project kubectl-validate Authors
+// SPDX-License-Identifier: Apache-2.0
+//
+// The Defaulter in this file is derived from Validator in kubectl-validate,
+// since the Validator field `validatorCache` is not exposed and we would like
+// to use the parsed schema for our CRD from it, we build this Defaulter that
+// meets our needs.
+// TODO: remove this file once can directly get schema from the Validator in kubectl-validate.
+
+var gatewaySchemaDefaulter, _ = newDefaulter(openapiclient.NewLocalCRDFiles(gatewayCRDsFS))
+
+// Defaulter can set default values for crd object according to their schema.
+type Defaulter struct {
+	gvs         map[string]openapi.GroupVersion
+	schemaCache map[schema.GroupVersionKind]*kubespec.Schema
+}
+
+func newDefaulter(client openapi.Client) (*Defaulter, error) {
+	gvs, err := client.Paths()
+	if err != nil {
+		return nil, err
+	}
+
+	return &Defaulter{
+		gvs:         gvs,
+		schemaCache: map[schema.GroupVersionKind]*kubespec.Schema{},
+	}, nil
+}
+
+// ApplyDefault applies default values for input object, and return the object with default values.
+func (d *Defaulter) ApplyDefault(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
+	if obj == nil || obj.Object == nil {
+		return nil, fmt.Errorf("passed object cannot be nil")
+	}
+
+	// shallow copy input object, this method can modify apiVersion, kind, or metadata.
+	obj = &unstructured.Unstructured{Object: maps.Clone(obj.UnstructuredContent())}
+	// deep copy metadata object.
+	obj.Object["metadata"] = runtime.DeepCopyJSONValue(obj.Object["metadata"])
+	gvk := obj.GroupVersionKind()
+	schema, err := d.parseSchema(gvk)
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve validator: %w", err)
+	}
+
+	// convert kube-openapi spec to go-openapi spec via JSON format.
+	schemaBytes, err := schema.MarshalJSON()
+	if err != nil {
+		return nil, fmt.Errorf("failed to marshal schema: %w", err)
+	}
+	var goSchema gospec.Schema
+	err = goSchema.UnmarshalJSON(schemaBytes)
+	if err != nil {
+		return nil, fmt.Errorf("failed to unmarshal schema: %w", err)
+	}
+
+	v := validate.NewSchemaValidator(&goSchema, nil, "", strfmt.Default)
+	rs := v.Validate(obj.Object)
+	post.ApplyDefaults(rs)
+	// convert output object into unstructured one.
+	output, ok := rs.Data().(map[string]interface{})
+	if !ok {
+		return nil, fmt.Errorf("failed to convert output object")
+	}
+
+	return &unstructured.Unstructured{Object: output}, nil
+}
+
+func (d *Defaulter) parseSchema(gvk schema.GroupVersionKind) (*kubespec.Schema, error) {
+	if existing, ok := d.schemaCache[gvk]; ok {
+		return existing, nil
+	}
+
+	// Otherwise, fetch the open API schema for this GV and do the above
+	// Lookup gvk in client
+	// Guess the rest mapping since we don't have a rest mapper for the target
+	// cluster
+	gvPath := "apis/" + gvk.Group + "/" + gvk.Version
+	if len(gvk.Group) == 0 {
+		gvPath = "api/" + gvk.Version
+	}
+	gvFetcher, exists := d.gvs[gvPath]
+	if !exists {
+		return nil, fmt.Errorf("failed to locate OpenAPI spec for GV: %v", gvk.GroupVersion())
+	}
+
+	documentBytes, err := gvFetcher.Schema("application/json")
+	if err != nil {
+		return nil, fmt.Errorf("error fetching openapi at path %s: %w", gvPath, err)
+	}
+
+	openapiSpec := spec3.OpenAPI{}
+	if err := json.Unmarshal(documentBytes, &openapiSpec); err != nil {
+		return nil, fmt.Errorf("error parsing openapi spec: %w", err)
+	}
+
+	// Apply our transformations to workaround known k8s schema deficiencies
+	for name, def := range openapiSpec.Components.Schemas {
+		//!TODO: would be useful to know which version of k8s each schema is believed
+		// to come from.
+		openapiSpec.Components.Schemas[name] = validator.ApplySchemaPatches(0, gvk.GroupVersion(), name, def)
+	}
+
+	// Remove all references/indirection.
+	// This is kinda hacky because we still do allow recursive schemas via
+	// pointer trickery.
+	// No need for stack/queue approach since we mutate same dictionary/slice instances
+	// destructively.
+	// Replaces subschemas that contain refs with copy of the thing they refer to
+	// !TODO validate that no cyces are created by this process. If so, do not
+	// allow structural schema creation via JSON
+	// !TODO: track unresolved references?
+	// !TODO: Once Declarative Validation for native types lands we will be
+	//	able to validate against the spec.Schema directly rather than
+	//	StructuralSchema, so this will be able to be removed
+	var referenceErrors []error
+	for name, def := range openapiSpec.Components.Schemas {
+		// This hack only works because top level schemas never have references
+		// so we can reliably copy them knowing they won't change and pointer-share
+		// their subfields. The only schemas being modified here should be sub-fields.
+		openapiSpec.Components.Schemas[name] = utils.VisitSchema(name, def, utils.PreorderVisitor(func(ctx utils.VisitingContext, sch *kubespec.Schema) (*kubespec.Schema, bool) {
+			defName := sch.Ref.String()
+
+			if len(sch.AllOf) == 1 && len(sch.AllOf[0].Ref.String()) > 0 {
+				// SPECIAL CASE
+				// OpenAPIV3 does not support having Refs in schemas with fields like
+				// Description, Default filled in. So k8s stuffs the Ref into a standalone
+				// AllOf in these cases.
+				// But structural schema doesn't like schemas that specify fields inside AllOf
+				// SO in the case of
+				// Properties
+				//	-> AllOf
+				//		-> Ref
+				defName = sch.AllOf[0].Ref.String()
+			}
+
+			if len(defName) == 0 {
+				// Nothing to do for no references
+				return sch, true
+			}
+
+			defName = path.Base(defName)
+			resolved, ok := openapiSpec.Components.Schemas[defName]
+			if !ok {
+				// Can't resolve schema. This is an error.
+				var path []string
+				for cursor := &ctx; cursor != nil; cursor = cursor.Parent {
+					if len(cursor.Key) == 0 {
+						path = append(path, fmt.Sprint(cursor.Index))
+					} else {
+						path = append(path, cursor.Key)
+					}
+				}
+				sort.Stable(sort.Reverse(sort.StringSlice(path)))
+				referenceErrors = append(referenceErrors, fmt.Errorf("cannot resolve reference %v in %v.%v", defName, name, strings.Join(path, ".")))
+				return sch, true
+			}
+
+			resolvedCopy := *resolved
+
+			if sch.Default != nil {
+				resolvedCopy.Default = sch.Default
+			}
+
+			// NOTE: No way to tell if field overrides nullable
+			// or if it is unset. Right now if the referred schema is
+			// nullable we will resolve to a nullable schema.
+			// There are no upstream schemas where nullable is used as a field
+			// level override, so we will assume `false` means `unset`.
+			// But this should be fixed in kube-openapi.
+			resolvedCopy.Nullable = resolvedCopy.Nullable || sch.Nullable
+
+			if len(sch.Type) > 0 {
+				resolvedCopy.Type = sch.Type
+			}
+
+			if len(sch.Description) > 0 {
+				resolvedCopy.Description = sch.Description
+			}
+
+			newExtensions := kubespec.Extensions{}
+			for k, v := range resolvedCopy.Extensions {
+				newExtensions.Add(k, v)
+			}
+			for k, v := range sch.Extensions {
+				newExtensions.Add(k, v)
+			}
+			if len(newExtensions) > 0 {
+				resolvedCopy.Extensions = newExtensions
+			}
+
+			// Don't explore children. This was a reference node and shares
+			// pointers with its schema which will be traversed in this loop.
+			return &resolvedCopy, false
+		}))
+	}
+
+	if len(referenceErrors) > 0 {
+		return nil, errors.Join(referenceErrors...)
+	}
+
+	namespaced := sets.New[schema.GroupVersionKind]()
+	if openapiSpec.Paths != nil {
+		for path, pathInfo := range openapiSpec.Paths.Paths {
+			for _, gvk := range utils.ExtractPathGVKs(pathInfo) {
+				if !namespaced.Has(gvk) {
+					if strings.Contains(path, "namespaces/{namespace}") {
+						namespaced.Insert(gvk)
+					}
+				}
+			}
+		}
+	}
+
+	for _, def := range openapiSpec.Components.Schemas {
+		gvks := utils.ExtractExtensionGVKs(def.Extensions)
+		if len(gvks) == 0 {
+			continue
+		}
+
+		for _, specGVK := range gvks {
+			d.schemaCache[specGVK] = def
+		}
+	}
+
+	// Check again to see if the desired GVK was added to the spec cache.
+	// If so, create validator for it
+	if existing, ok := d.schemaCache[gvk]; ok {
+		return existing, nil
+	}
+
+	return nil, fmt.Errorf("kind %v not found in %v groupversion", gvk.Kind, gvk.GroupVersion())
+}
diff --git a/internal/gatewayapi/resource/load.go b/internal/gatewayapi/resource/load.go
index 7c87ffb79..3229aabe4 100644
--- a/internal/gatewayapi/resource/load.go
+++ b/internal/gatewayapi/resource/load.go
@@ -45,11 +45,9 @@ func LoadResourcesFromYAMLBytes(yamlBytes []byte, addMissingResources bool) (*Re
 // loadKubernetesYAMLToResources converts a Kubernetes YAML string into GatewayAPI Resources.
 // TODO: add support for kind:
 //   - EnvoyExtensionPolicy (gateway.envoyproxy.io/v1alpha1)
-//   - HTTPRouteFilter (gateway.envoyproxy.io/v1alpha1)
 //   - BackendLPPolicy (gateway.networking.k8s.io/v1alpha2)
 //   - BackendTLSPolicy (gateway.networking.k8s.io/v1alpha3)
 //   - ReferenceGrant (gateway.networking.k8s.io/v1alpha2)
-//   - TLSRoute (gateway.networking.k8s.io/v1alpha2)
 func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Resources, error) {
 	resources := NewResources()
 	var useDefaultNamespace bool
@@ -64,7 +62,7 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			return err
 		}
 
-		un := unstructured.Unstructured{Object: obj}
+		un := &unstructured.Unstructured{Object: obj}
 		gvk := un.GroupVersionKind()
 		name, namespace := un.GetName(), un.GetNamespace()
 		if len(namespace) == 0 {
@@ -72,11 +70,16 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 			namespace = config.DefaultNamespace
 		}
 
-		// Perform local validation for gateway-api related resources only.
+		// Perform local validation and apply default values for gateway-api related resources only.
 		if gvk.Group == egv1a1.GroupName || gvk.Group == gwapiv1.GroupName {
 			if err = defaultValidator.Validate(yamlByte); err != nil {
 				return fmt.Errorf("local validation error: %w", err)
 			}
+
+			un, err = gatewaySchemaDefaulter.ApplyDefault(un)
+			if err != nil {
+				return fmt.Errorf("failed to apply default values for %s/%s: %w", un.GetKind(), un.GetName(), err)
+			}
 		}
 
 		requiredNamespaceMap.Insert(namespace)
@@ -84,7 +87,7 @@ func loadKubernetesYAMLToResources(input []byte, addMissingResources bool) (*Res
 		if err != nil {
 			return err
 		}
-		err = combinedScheme.Convert(&un, kobj, nil)
+		err = combinedScheme.Convert(un, kobj, nil)
 		if err != nil {
 			return err
 		}
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index f653fbf32..9f20aed76 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -862,6 +862,8 @@ type EnvoyExtensionFeatures struct {
 	ExtProcs []ExtProc `json:"extProcs,omitempty" yaml:"extProcs,omitempty"`
 	// Wasm extensions
 	Wasms []Wasm `json:"wasms,omitempty" yaml:"wasms,omitempty"`
+	// Lua extensions
+	Luas []Lua `json:"luas,omitempty" yaml:"luas,omitempty"`
 }
 
 // UnstructuredRef holds unstructured data for an arbitrary k8s resource introduced by an extension
@@ -2822,6 +2824,16 @@ type ExtProc struct {
 	AllowModeOverride bool `json:"allowModeOverride,omitempty" yaml:"allowModeOverride,omitempty"`
 }
 
+// Lua holds the information associated with Lua extensions
+// +k8s:deepcopy-gen=true
+type Lua struct {
+	// Name is a unique name for the LUa configuration.
+	// The xds translator only generates one Lua filter for each unique name
+	Name string
+	// Code is the Lua source code
+	Code *string
+}
+
 // Wasm holds the information associated with the Wasm extensions.
 // +k8s:deepcopy-gen=true
 type Wasm struct {
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index a5be0b4b1..75b371f5f 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -888,6 +888,13 @@ func (in *EnvoyExtensionFeatures) DeepCopyInto(out *EnvoyExtensionFeatures) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.Luas != nil {
+		in, out := &in.Luas, &out.Luas
+		*out = make([]Lua, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyExtensionFeatures.
@@ -2137,6 +2144,26 @@ func (in *LocalRateLimit) DeepCopy() *LocalRateLimit {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Lua) DeepCopyInto(out *Lua) {
+	*out = *in
+	if in.Code != nil {
+		in, out := &in.Code, &out.Code
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Lua.
+func (in *Lua) DeepCopy() *Lua {
+	if in == nil {
+		return nil
+	}
+	out := new(Lua)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Metrics) DeepCopyInto(out *Metrics) {
 	*out = *in
diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go
index bc7b5ba8c..9ed0445fe 100644
--- a/internal/provider/kubernetes/controller.go
+++ b/internal/provider/kubernetes/controller.go
@@ -2102,6 +2102,7 @@ func (r *gatewayAPIReconciler) processExtensionServerPolicies(
 // to the resourceTree
 // - BackendRefs for ExtProcs
 // - SecretRefs for Wasms
+// - ValueRefs for Luas
 func (r *gatewayAPIReconciler) processEnvoyExtensionPolicyObjectRefs(
 	ctx context.Context, resourceTree *resource.Resources, resourceMap *resourceMappings,
 ) {
@@ -2173,5 +2174,30 @@ func (r *gatewayAPIReconciler) processEnvoyExtensionPolicyObjectRefs(
 				}
 			}
 		}
+
+		// Add referenced ConfigMaps in Lua EnvoyExtensionPolicies to the resource tree
+		for _, lua := range policy.Spec.Lua {
+			if lua.Type == egv1a1.LuaValueTypeValueRef {
+				if lua.ValueRef != nil && string(lua.ValueRef.Kind) == resource.KindConfigMap {
+					configMap := new(corev1.ConfigMap)
+					err := r.client.Get(ctx,
+						types.NamespacedName{Namespace: policy.Namespace, Name: string(lua.ValueRef.Name)},
+						configMap,
+					)
+					if err != nil {
+						r.log.Error(err,
+							"failed to process Lua ValueRef for EnvoyExtensionPolicy",
+							"policy", policy, "ValueRef", lua.ValueRef.Name)
+					}
+
+					resourceMap.allAssociatedNamespaces.Insert(policy.Namespace)
+					if !resourceMap.allAssociatedConfigMaps.Has(utils.NamespacedName(configMap).String()) {
+						resourceMap.allAssociatedConfigMaps.Insert(utils.NamespacedName(configMap).String())
+						resourceTree.ConfigMaps = append(resourceTree.ConfigMaps, configMap)
+						r.log.Info("processing ConfigMap", "namespace", policy.Namespace, "name", string(lua.ValueRef.Name))
+					}
+				}
+			}
+		}
 	}
 }
diff --git a/internal/xds/extensions/extensions.gen.go b/internal/xds/extensions/extensions.gen.go
index 95946e438..052eb2216 100644
--- a/internal/xds/extensions/extensions.gen.go
+++ b/internal/xds/extensions/extensions.gen.go
@@ -34,6 +34,8 @@ import (
 	_ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/private_key_providers/qat/v3alpha"
 	_ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/regex_engines/hyperscan/v3alpha"
 	_ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha"
+	_ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/tap_sinks/udp_sink/v3alpha"
+	_ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/upstreams/http/tcp/golang/v3alpha"
 	_ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/vcl/v3alpha"
 	_ "github.com/envoyproxy/go-control-plane/envoy/admin/v3"
 	_ "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
diff --git a/internal/xds/translator/httpfilters.go b/internal/xds/translator/httpfilters.go
index 7fcb1e0b7..e291a3b46 100644
--- a/internal/xds/translator/httpfilters.go
+++ b/internal/xds/translator/httpfilters.go
@@ -116,22 +116,24 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter {
 		order = 7
 	case isFilterType(filter, egv1a1.EnvoyFilterSessionPersistence):
 		order = 8
-	case isFilterType(filter, egv1a1.EnvoyFilterExtProc):
+	case isFilterType(filter, egv1a1.EnvoyFilterLua):
 		order = 9 + mustGetFilterIndex(filter.Name)
-	case isFilterType(filter, egv1a1.EnvoyFilterWasm):
+	case isFilterType(filter, egv1a1.EnvoyFilterExtProc):
 		order = 100 + mustGetFilterIndex(filter.Name)
+	case isFilterType(filter, egv1a1.EnvoyFilterWasm):
+		order = 200 + mustGetFilterIndex(filter.Name)
 	case isFilterType(filter, egv1a1.EnvoyFilterRBAC):
-		order = 201
+		order = 301
 	case isFilterType(filter, egv1a1.EnvoyFilterLocalRateLimit):
-		order = 202
+		order = 302
 	case isFilterType(filter, egv1a1.EnvoyFilterRateLimit):
-		order = 203
+		order = 303
 	case isFilterType(filter, egv1a1.EnvoyFilterCustomResponse):
-		order = 204
+		order = 304
 	case isFilterType(filter, egv1a1.EnvoyFilterCompressor):
-		order = 205
+		order = 305
 	case isFilterType(filter, egv1a1.EnvoyFilterRouter):
-		order = 206
+		order = 306
 	}
 
 	return &OrderedHTTPFilter{
diff --git a/internal/xds/translator/lua.go b/internal/xds/translator/lua.go
new file mode 100644
index 000000000..f4444a97d
--- /dev/null
+++ b/internal/xds/translator/lua.go
@@ -0,0 +1,128 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package translator
+
+import (
+	"errors"
+
+	corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+	routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
+	luafilterv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/lua/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+	"google.golang.org/protobuf/types/known/anypb"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/ir"
+	"github.com/envoyproxy/gateway/internal/xds/types"
+)
+
+func init() {
+	registerHTTPFilter(&lua{})
+}
+
+type lua struct{}
+
+var _ httpFilter = &lua{}
+
+// patchHCM builds and appends the lua Filters to the HTTP Connection Manager
+// Lua filters are created in disabled mode.
+func (*lua) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error {
+	if mgr == nil {
+		return errors.New("hcm is nil")
+	}
+	if irListener == nil {
+		return errors.New("ir listener is nil")
+	}
+
+	var errs error
+	for _, route := range irListener.Routes {
+		if !routeContainsLua(route) {
+			continue
+		}
+		for _, ep := range route.EnvoyExtensions.Luas {
+			if hcmContainsFilter(mgr, luaFilterName(ep)) {
+				continue
+			}
+			filter, err := buildHCMLuaFilter(ep)
+			if err != nil {
+				errs = errors.Join(errs, err)
+				continue
+			}
+			mgr.HttpFilters = append(mgr.HttpFilters, filter)
+		}
+	}
+
+	return errs
+}
+
+// buildHCMLuaFilter returns a Lua filter for HCM.
+func buildHCMLuaFilter(lua ir.Lua) (*hcmv3.HttpFilter, error) {
+	var (
+		luaProto *luafilterv3.Lua
+		luaAny   *anypb.Any
+		err      error
+	)
+	luaProto = &luafilterv3.Lua{
+		DefaultSourceCode: &corev3.DataSource{
+			Specifier: &corev3.DataSource_InlineString{
+				InlineString: *lua.Code,
+			},
+		},
+	}
+	if err = luaProto.ValidateAll(); err != nil {
+		return nil, err
+	}
+	if luaAny, err = anypb.New(luaProto); err != nil {
+		return nil, err
+	}
+
+	return &hcmv3.HttpFilter{
+		Name:     luaFilterName(lua),
+		Disabled: true,
+		ConfigType: &hcmv3.HttpFilter_TypedConfig{
+			TypedConfig: luaAny,
+		},
+	}, nil
+}
+
+func luaFilterName(lua ir.Lua) string {
+	return perRouteFilterName(egv1a1.EnvoyFilterLua, lua.Name)
+}
+
+// routeContainsLua returns true if Luas exists for the provided route.
+func routeContainsLua(irRoute *ir.HTTPRoute) bool {
+	if irRoute == nil {
+		return false
+	}
+
+	return irRoute.EnvoyExtensions != nil && len(irRoute.EnvoyExtensions.Luas) > 0
+}
+
+// patchResources patches the cluster resources for the http lua code source.
+func (*lua) patchResources(_ *types.ResourceVersionTable, _ []*ir.HTTPRoute) error {
+	return nil
+}
+
+// patchRoute patches the provided route so Lua filters are enabled if applicable.
+func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
+	if route == nil {
+		return errors.New("xds route is nil")
+	}
+	if irRoute == nil {
+		return errors.New("ir route is nil")
+	}
+	if irRoute.EnvoyExtensions == nil {
+		return nil
+	}
+
+	for _, ep := range irRoute.EnvoyExtensions.Luas {
+		filterName := luaFilterName(ep)
+		if err := enableFilterOnRoute(route, filterName); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index f036c6a87..8d17ced8e 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -11,6 +11,8 @@ security updates: |
 new features: |
   Added support for configuring maxUnavailable in KubernetesPodDisruptionBudgetSpec
   Added support for percentage-based request mirroring
+  Allow matchExpressions in TargetSelector
+  Add defaulter for gateway-api resources loading from file to be able to set default values.
 
 bug fixes: |
 
diff --git a/tools/github-actions/setup-deps/action.yaml b/tools/github-actions/setup-deps/action.yaml
index 3171c901e..7b93b1a1f 100644
--- a/tools/github-actions/setup-deps/action.yaml
+++ b/tools/github-actions/setup-deps/action.yaml
@@ -8,5 +8,5 @@ runs:
       run: sudo apt-get install libbtrfs-dev -y
     - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34  # v5.0.1
       with:
-        go-version: 1.23.x
+        go-version-file: go.mod
         cache: true
diff --git a/tools/make/golang.mk b/tools/make/golang.mk
index 4f4dce00f..36531fed6 100644
--- a/tools/make/golang.mk
+++ b/tools/make/golang.mk
@@ -53,6 +53,7 @@ go.testdata.complete: ## Override test ouputdata
 	go test -timeout 30s github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/proxy --override-testdata=true
 	go test -timeout 30s github.com/envoyproxy/gateway/internal/xds/bootstrap --override-testdata=true
 	go test -timeout 60s github.com/envoyproxy/gateway/internal/gatewayapi --override-testdata=true
+	go test -timeout 60s github.com/envoyproxy/gateway/internal/gatewayapi/resource --override-testdata=true
 
 .PHONY: go.test.coverage
 go.test.coverage: go.test.cel ## Run go unit and integration tests in GitHub Actions


Test patch:
diff --git a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml
index d3f3ed2c7..0841f8487 100644
--- a/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/backend-endpoint.all.yaml
@@ -40,7 +40,10 @@ gateways:
   spec:
     gatewayClassName: eg
     listeners:
-    - name: http
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: http
       port: 80
       protocol: HTTP
   status:
@@ -78,12 +81,15 @@ httpRoutes:
     hostnames:
     - www.example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
     rules:
     - backendRefs:
       - group: gateway.envoyproxy.io
         kind: Backend
         name: backend
+        weight: 1
       matches:
       - path:
           type: PathPrefix
@@ -103,4 +109,6 @@ httpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
diff --git a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml
index 0963e89f9..d05242b70 100644
--- a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml
@@ -202,13 +202,22 @@ gateways:
   spec:
     gatewayClassName: eg
     listeners:
-    - name: tcp
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: tcp
       port: 1234
       protocol: TCP
-    - name: udp
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: udp
       port: 1234
       protocol: UDP
-    - hostname: foo.com
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      hostname: foo.com
       name: tls-passthrough
       port: 8443
       protocol: TLS
@@ -218,6 +227,8 @@ gateways:
         kinds:
         - group: gateway.networking.k8s.io
           kind: HTTPRoute
+        namespaces:
+          from: Same
       name: http
       port: 80
       protocol: HTTP
@@ -225,6 +236,8 @@ gateways:
         kinds:
         - group: gateway.networking.k8s.io
           kind: GRPCRoute
+        namespaces:
+          from: Same
       name: grpc
       port: 8080
       protocol: HTTP
@@ -345,7 +358,9 @@ grpcRoutes:
     hostnames:
     - www.grpc-example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: grpc
     rules:
     - backendRefs:
@@ -358,6 +373,7 @@ grpcRoutes:
       - method:
           method: DoThing
           service: com.example.Things
+          type: Exact
   status:
     parents:
     - conditions:
@@ -373,6 +389,8 @@ grpcRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: grpc
 httpRoutes:
@@ -385,7 +403,9 @@ httpRoutes:
     hostnames:
     - www.example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
     rules:
     - backendRefs:
       - group: ""
@@ -412,6 +432,8 @@ httpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
 tcpRoutes:
 - kind: TCPRoute
@@ -421,7 +443,9 @@ tcpRoutes:
     namespace: default
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: tcp
     rules:
     - backendRefs:
@@ -445,6 +469,8 @@ tcpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: tcp
 tlsRoutes:
@@ -455,7 +481,9 @@ tlsRoutes:
     namespace: default
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: tls-passthrough
     rules:
     - backendRefs:
@@ -479,6 +507,8 @@ tlsRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: tls-passthrough
 udpRoutes:
@@ -489,7 +519,9 @@ udpRoutes:
     namespace: default
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: udp
     rules:
     - backendRefs:
@@ -513,6 +545,8 @@ udpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: udp
 xds:
diff --git a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml
index de5d16949..8d819d612 100644
--- a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml
@@ -22,7 +22,10 @@ gateways:
   spec:
     gatewayClassName: eg
     listeners:
-    - name: http
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: http
       port: 80
       protocol: HTTP
   status:
@@ -60,7 +63,9 @@ httpRoutes:
     hostnames:
     - www.example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
     rules:
     - backendRefs:
       - group: ""
@@ -87,6 +92,8 @@ httpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
 xds:
   envoy-gateway-system/eg:
diff --git a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json
index f069c670a..cb22c731e 100644
--- a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json
+++ b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json
@@ -35,7 +35,12 @@
           {
             "name": "http",
             "port": 80,
-            "protocol": "HTTP"
+            "protocol": "HTTP",
+            "allowedRoutes": {
+              "namespaces": {
+                "from": "Same"
+              }
+            }
           }
         ]
       },
@@ -93,6 +98,8 @@
       "spec": {
         "parentRefs": [
           {
+            "group": "gateway.networking.k8s.io",
+            "kind": "Gateway",
             "name": "eg"
           }
         ],
@@ -125,6 +132,8 @@
         "parents": [
           {
             "parentRef": {
+              "group": "gateway.networking.k8s.io",
+              "kind": "Gateway",
               "name": "eg"
             },
             "controllerName": "gateway.envoyproxy.io/gatewayclass-controller",
diff --git a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml
index bd4ac1d19..b51220df3 100644
--- a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml
@@ -18,7 +18,9 @@ envoyProxyForGatewayClass:
             socket_address:
               address: 127.0.0.1
               port_value: 19000
-    logging: {}
+    logging:
+      level:
+        default: warn
   status: {}
 gatewayClass:
   kind: GatewayClass
@@ -49,13 +51,22 @@ gateways:
   spec:
     gatewayClassName: eg
     listeners:
-    - name: tcp
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: tcp
       port: 1234
       protocol: TCP
-    - name: udp
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: udp
       port: 1234
       protocol: UDP
-    - hostname: foo.com
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      hostname: foo.com
       name: tls-passthrough
       port: 8443
       protocol: TLS
@@ -65,6 +76,8 @@ gateways:
         kinds:
         - group: gateway.networking.k8s.io
           kind: HTTPRoute
+        namespaces:
+          from: Same
       name: http
       port: 80
       protocol: HTTP
@@ -72,6 +85,8 @@ gateways:
         kinds:
         - group: gateway.networking.k8s.io
           kind: GRPCRoute
+        namespaces:
+          from: Same
       name: grpc
       port: 8080
       protocol: HTTP
@@ -192,7 +207,9 @@ grpcRoutes:
     hostnames:
     - www.grpc-example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: grpc
     rules:
     - backendRefs:
@@ -205,6 +222,7 @@ grpcRoutes:
       - method:
           method: DoThing
           service: com.example.Things
+          type: Exact
   status:
     parents:
     - conditions:
@@ -220,6 +238,8 @@ grpcRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: grpc
 httpRoutes:
@@ -232,7 +252,9 @@ httpRoutes:
     hostnames:
     - www.example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
     rules:
     - backendRefs:
       - group: ""
@@ -259,6 +281,8 @@ httpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
 tcpRoutes:
 - kind: TCPRoute
@@ -268,7 +292,9 @@ tcpRoutes:
     namespace: default
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: tcp
     rules:
     - backendRefs:
@@ -292,6 +318,8 @@ tcpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: tcp
 tlsRoutes:
@@ -302,7 +330,9 @@ tlsRoutes:
     namespace: default
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: tls-passthrough
     rules:
     - backendRefs:
@@ -326,6 +356,8 @@ tlsRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: tls-passthrough
 udpRoutes:
@@ -336,7 +368,9 @@ udpRoutes:
     namespace: default
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: udp
     rules:
     - backendRefs:
@@ -360,5 +394,7 @@ udpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: udp
diff --git a/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml b/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml
index 84a0ebc0f..696d00a25 100644
--- a/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml
@@ -7,7 +7,10 @@ gateways:
   spec:
     gatewayClassName: eg
     listeners:
-    - name: http
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: http
       port: 80
       protocol: HTTP
   status:
@@ -45,7 +48,9 @@ httpRoutes:
     hostnames:
     - www.example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
     rules:
     - backendRefs:
       - group: ""
@@ -72,6 +77,8 @@ httpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
 infraIR:
   envoy-gateway-system/eg:
diff --git a/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml b/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml
index 18e5910ac..cff3a5f33 100644
--- a/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml
@@ -22,7 +22,10 @@ gateways:
   spec:
     gatewayClassName: eg
     listeners:
-    - name: tls
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: tls
       port: 8443
       protocol: TLS
   status:
@@ -53,7 +56,9 @@ httpRoutes:
     namespace: envoy-gateway-system
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
     rules:
     - backendRefs:
       - group: ""
@@ -80,4 +85,6 @@ httpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
diff --git a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml
index fe1b452f2..7ba42aa75 100644
--- a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml
@@ -5,12 +5,16 @@ envoyProxyForGatewayClass:
     name: example
     namespace: default
   spec:
-    logging: {}
+    logging:
+      level:
+        default: warn
     provider:
       kubernetes:
         envoyService:
           annotations:
             custom1: svc-annotation1
+          externalTrafficPolicy: Local
+          type: LoadBalancer
       type: Kubernetes
   status: {}
 gatewayClass:
@@ -42,13 +46,22 @@ gateways:
   spec:
     gatewayClassName: eg
     listeners:
-    - name: tcp
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: tcp
       port: 1234
       protocol: TCP
-    - name: udp
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: udp
       port: 1234
       protocol: UDP
-    - hostname: foo.com
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      hostname: foo.com
       name: tls-passthrough
       port: 8443
       protocol: TLS
@@ -58,6 +71,8 @@ gateways:
         kinds:
         - group: gateway.networking.k8s.io
           kind: HTTPRoute
+        namespaces:
+          from: Same
       name: http
       port: 80
       protocol: HTTP
@@ -65,6 +80,8 @@ gateways:
         kinds:
         - group: gateway.networking.k8s.io
           kind: GRPCRoute
+        namespaces:
+          from: Same
       name: grpc
       port: 8080
       protocol: HTTP
@@ -185,7 +202,9 @@ grpcRoutes:
     hostnames:
     - www.grpc-example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: grpc
     rules:
     - backendRefs:
@@ -198,6 +217,7 @@ grpcRoutes:
       - method:
           method: DoThing
           service: com.example.Things
+          type: Exact
   status:
     parents:
     - conditions:
@@ -213,6 +233,8 @@ grpcRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: grpc
 httpRoutes:
@@ -225,7 +247,9 @@ httpRoutes:
     hostnames:
     - www.example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
     rules:
     - backendRefs:
       - group: ""
@@ -252,6 +276,8 @@ httpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
 tcpRoutes:
 - kind: TCPRoute
@@ -261,7 +287,9 @@ tcpRoutes:
     namespace: default
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: tcp
     rules:
     - backendRefs:
@@ -285,6 +313,8 @@ tcpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: tcp
 tlsRoutes:
@@ -295,7 +325,9 @@ tlsRoutes:
     namespace: default
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: tls-passthrough
     rules:
     - backendRefs:
@@ -319,6 +351,8 @@ tlsRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: tls-passthrough
 udpRoutes:
@@ -329,7 +363,9 @@ udpRoutes:
     namespace: default
   spec:
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
       sectionName: udp
     rules:
     - backendRefs:
@@ -353,5 +389,7 @@ udpRoutes:
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
         name: eg
         sectionName: udp
diff --git a/internal/gatewayapi/helpers_test.go b/internal/gatewayapi/helpers_test.go
index 6403279a5..22bcafd2c 100644
--- a/internal/gatewayapi/helpers_test.go
+++ b/internal/gatewayapi/helpers_test.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/stretchr/testify/require"
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
@@ -475,6 +476,106 @@ func TestGetPolicyTargetRefs(t *testing.T) {
 			},
 			results: []gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{},
 		},
+		{
+			name: "match expression",
+			policy: egv1a1.PolicyTargetReferences{
+				TargetSelectors: []egv1a1.TargetSelector{
+					{
+						Kind: "Gateway",
+						MatchExpressions: []metav1.LabelSelectorRequirement{
+							{
+								Key:      "environment",
+								Operator: "In",
+								Values:   []string{"prod", "staging"},
+							},
+						},
+					},
+				},
+			},
+			targets: []*unstructured.Unstructured{
+				{
+					Object: map[string]any{
+						"apiVersion": "gateway.networking.k8s.io/v1",
+						"kind":       "Gateway",
+						"metadata": map[string]any{
+							"name":      "first",
+							"namespace": "default",
+							"labels": map[string]any{
+								"environment": "prod",
+							},
+						},
+					},
+				},
+				{
+					Object: map[string]any{
+						"apiVersion": "gateway.networking.k8s.io/v1",
+						"kind":       "Gateway",
+						"metadata": map[string]any{
+							"name":      "second",
+							"namespace": "default",
+							"labels": map[string]any{
+								"environment": "dev",
+							},
+						},
+					},
+				},
+			},
+			results: []gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+				{
+					LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+						Group: "gateway.networking.k8s.io",
+						Kind:  "Gateway",
+						Name:  "first",
+					},
+				},
+			},
+		},
+		{
+			name: "match expression - bad expression matches nothing",
+			policy: egv1a1.PolicyTargetReferences{
+				TargetSelectors: []egv1a1.TargetSelector{
+					{
+						Kind: "Gateway",
+						MatchExpressions: []metav1.LabelSelectorRequirement{
+							{
+								Key:      "environment",
+								Operator: "Foo",
+								Values:   []string{"prod", "staging"},
+							},
+						},
+					},
+				},
+			},
+			targets: []*unstructured.Unstructured{
+				{
+					Object: map[string]any{
+						"apiVersion": "gateway.networking.k8s.io/v1",
+						"kind":       "Gateway",
+						"metadata": map[string]any{
+							"name":      "first",
+							"namespace": "default",
+							"labels": map[string]any{
+								"environment": "prod",
+							},
+						},
+					},
+				},
+				{
+					Object: map[string]any{
+						"apiVersion": "gateway.networking.k8s.io/v1",
+						"kind":       "Gateway",
+						"metadata": map[string]any{
+							"name":      "second",
+							"namespace": "default",
+							"labels": map[string]any{
+								"environment": "dev",
+							},
+						},
+					},
+				},
+			},
+			results: []gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{},
+		},
 	}
 
 	for _, tc := range testCases {
diff --git a/internal/gatewayapi/luavalidator/lua_validator_test.go b/internal/gatewayapi/luavalidator/lua_validator_test.go
new file mode 100644
index 000000000..cb3ba7548
--- /dev/null
+++ b/internal/gatewayapi/luavalidator/lua_validator_test.go
@@ -0,0 +1,146 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package luavalidator
+
+import (
+	"strings"
+	"testing"
+)
+
+func Test_Validate(t *testing.T) {
+	type args struct {
+		name                 string
+		code                 string
+		expectedErrSubstring string
+	}
+	tests := []args{
+		{
+			name:                 "empty body",
+			code:                 "",
+			expectedErrSubstring: "expected one of envoy_on_request() or envoy_on_response() to be defined",
+		},
+		{
+			name: "logInfo: envoy_on_response",
+			code: `function envoy_on_response(response_handle)
+                     response_handle:logInfo("Goodbye.")
+                   end`,
+			expectedErrSubstring: "",
+		},
+		{
+			name: "logInfo: envoy_on_request",
+			code: `function envoy_on_request(request_handle)
+                     request_handle:logInfo("Goodbye.")
+                   end`,
+			expectedErrSubstring: "",
+		},
+		{
+			name: "stream:headers:Get",
+			code: `function envoy_on_request(request_handle)
+                     request_handle:headers():get("foo")
+                   end`,
+			expectedErrSubstring: "",
+		},
+		{
+			name: "stream:connection:ssl:expirationPeerCertificate",
+			code: `function envoy_on_request(request_handle)
+                     request_handle:connection():ssl():expirationPeerCertificate()
+                   end`,
+			expectedErrSubstring: "",
+		},
+		{
+			name: "stream:metadata:pairs",
+			code: `function envoy_on_request(request_handle)
+                     for key, value in pairs(request_handle:metadata()) do
+                       print(key, value)
+					 end
+                   end`,
+			expectedErrSubstring: "",
+		},
+		{
+			name: "stream:httpCall",
+			code: `function envoy_on_request(request_handle)
+			  -- Make an HTTP call.
+			  local headers, body = request_handle:httpCall(
+			  "lua_cluster",
+			  {
+				[":method"] = "POST",
+				[":path"] = "/",
+				[":authority"] = "lua_cluster",
+				["set-cookie"] = { "lang=lua; Path=/", "type=binding; Path=/" }
+			  },
+			  "hello world",
+			  5000)
+			
+			  -- Response directly and set a header from the HTTP call. No further filter iteration
+			  -- occurs.
+			  request_handle:respond(
+				{[":status"] = "403",
+				 ["upstream_foo"] = headers["foo"]},
+				"nope")
+			end`,
+			expectedErrSubstring: "",
+		},
+		{
+			name: "stream:httpPostCall unsupported api",
+			code: `function envoy_on_request(request_handle)
+			  -- Make an HTTP call.
+			  local headers, body = request_handle:httpPostCall(
+			  "lua_cluster",
+			  {
+				[":method"] = "POST",
+				[":path"] = "/",
+				[":authority"] = "lua_cluster",
+				["set-cookie"] = { "lang=lua; Path=/", "type=binding; Path=/" }
+			  },
+			  "hello world",
+			  5000)
+			
+			  -- Response directly and set a header from the HTTP call. No further filter iteration
+			  -- occurs.
+			  request_handle:respond(
+				{[":status"] = "403",
+				 ["upstream_foo"] = headers["foo"]},
+				"nope")
+			end`,
+			expectedErrSubstring: "attempt to call a non-function object",
+		},
+		{
+			name: "stream:bodyChunks",
+			code: `function envoy_on_response(response_handle)
+			  -- Sets the content-type.
+			  response_handle:headers():replace("content-type", "text/html")
+			  local last
+			  for chunk in response_handle:bodyChunks() do
+				-- Clears each received chunk.
+				chunk:setBytes("")
+				last = chunk
+			  end
+			
+			  last:setBytes("<html><b>Not Found<b></html>")
+            end`,
+			expectedErrSubstring: "",
+		},
+		{
+			name: "unsupported api",
+			code: `function envoy_on_request(request_handle)
+                     request_handle:unknownApi()
+                   end`,
+			expectedErrSubstring: "attempt to call a non-function object",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			l := NewLuaValidator(tt.code)
+			if err := l.Validate(); err != nil && tt.expectedErrSubstring == "" {
+				t.Errorf("Unexpected error: %v", err)
+			} else if err != nil && !strings.Contains(err.Error(), tt.expectedErrSubstring) {
+				t.Errorf("Expected substring in error: %v, got error: %v", tt.expectedErrSubstring, err)
+			} else if err == nil && tt.expectedErrSubstring != "" {
+				t.Errorf("Expected error with substring: %v", tt.expectedErrSubstring)
+			}
+		})
+	}
+}
diff --git a/internal/gatewayapi/resource/defaulter_test.go b/internal/gatewayapi/resource/defaulter_test.go
new file mode 100644
index 000000000..79014e952
--- /dev/null
+++ b/internal/gatewayapi/resource/defaulter_test.go
@@ -0,0 +1,157 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package resource
+
+import (
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"sigs.k8s.io/kubectl-validate/pkg/openapiclient"
+)
+
+func TestApplyDefault(t *testing.T) {
+	defaulter, err := newDefaulter(openapiclient.NewLocalCRDFiles(os.DirFS("testdata/schema")))
+	require.NoError(t, err)
+
+	testCases := []struct {
+		name   string
+		error  bool
+		input  map[string]interface{}
+		expect map[string]interface{}
+	}{
+		{
+			name: "empty object with nested field",
+			input: map[string]interface{}{
+				"apiVersion": "example.com/v1",
+				"kind":       "TestCR",
+				"metadata": map[string]interface{}{
+					"name":      "test-cr",
+					"namespace": "default",
+				},
+				"spec": map[string]interface{}{
+					"objectField": map[string]interface{}{},
+				},
+			},
+			expect: map[string]interface{}{
+				"apiVersion": "example.com/v1",
+				"kind":       "TestCR",
+				"metadata": map[string]interface{}{
+					"name":      "test-cr",
+					"namespace": "default",
+				},
+				"spec": map[string]interface{}{
+					"stringField":  "defaultString",
+					"integerField": 42.,
+					"floatField":   3.14,
+					"booleanField": true,
+					"enumField":    "option1",
+					"objectField": map[string]interface{}{
+						"nestedString":  "nestedDefault",
+						"nestedInteger": 10.,
+					},
+					"mapField": map[string]interface{}{
+						"key1": "value1",
+						"key2": "value2",
+					},
+				},
+			},
+			error: false,
+		},
+		{
+			name: "empty object without nested field",
+			input: map[string]interface{}{
+				"apiVersion": "example.com/v1",
+				"kind":       "TestCR",
+				"metadata": map[string]interface{}{
+					"name":      "test-cr",
+					"namespace": "default",
+				},
+				"spec": map[string]interface{}{},
+			},
+			expect: map[string]interface{}{
+				"apiVersion": "example.com/v1",
+				"kind":       "TestCR",
+				"metadata": map[string]interface{}{
+					"name":      "test-cr",
+					"namespace": "default",
+				},
+				"spec": map[string]interface{}{
+					"stringField":  "defaultString",
+					"integerField": 42.,
+					"floatField":   3.14,
+					"booleanField": true,
+					"enumField":    "option1",
+					"mapField": map[string]interface{}{
+						"key1": "value1",
+						"key2": "value2",
+					},
+				},
+			},
+			error: false,
+		},
+		{
+			name: "object with few field unset",
+			input: map[string]interface{}{
+				"apiVersion": "example.com/v1",
+				"kind":       "TestCR",
+				"metadata": map[string]interface{}{
+					"name":      "test-cr",
+					"namespace": "default",
+				},
+				"spec": map[string]interface{}{
+					"stringField":  "exampleString",
+					"booleanField": false,
+					"objectField": map[string]interface{}{
+						"nestedString": "nestedExample",
+					},
+				},
+			},
+			expect: map[string]interface{}{
+				"apiVersion": "example.com/v1",
+				"kind":       "TestCR",
+				"metadata": map[string]interface{}{
+					"name":      "test-cr",
+					"namespace": "default",
+				},
+				"spec": map[string]interface{}{
+					"stringField":  "exampleString",
+					"integerField": 42.,
+					"floatField":   3.14,
+					"booleanField": false,
+					"enumField":    "option1",
+					"objectField": map[string]interface{}{
+						"nestedString":  "nestedExample",
+						"nestedInteger": 10.,
+					},
+					"mapField": map[string]interface{}{
+						"key1": "value1",
+						"key2": "value2",
+					},
+				},
+			},
+			error: false,
+		},
+		{
+			name:  "nil input",
+			input: nil,
+			error: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got, err := defaulter.ApplyDefault(&unstructured.Unstructured{Object: tc.input})
+			if tc.error {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+				require.Equal(t, tc.expect, got.Object)
+			}
+		})
+	}
+}
diff --git a/internal/gatewayapi/resource/load_test.go b/internal/gatewayapi/resource/load_test.go
index df3629251..534730608 100644
--- a/internal/gatewayapi/resource/load_test.go
+++ b/internal/gatewayapi/resource/load_test.go
@@ -6,12 +6,22 @@
 package resource
 
 import (
+	"flag"
+	"fmt"
+	"os"
+	"path/filepath"
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/stretchr/testify/require"
 	"sigs.k8s.io/yaml"
+
+	"github.com/envoyproxy/gateway/internal/utils/file"
 )
 
+var overrideTestData = flag.Bool("override-testdata", true, "if override the test output data.")
+
 func TestIterYAMLBytes(t *testing.T) {
 	inputs := `test: foo1
 ---
@@ -37,3 +47,36 @@ test: foo3
 	require.NoError(t, err)
 	require.ElementsMatch(t, names, []string{"foo1", "foo2", "foo3"})
 }
+
+func TestLoadAllSupportedResourcesFromYAMLBytes(t *testing.T) {
+	inFile := requireTestDataFile(t, "all-resources", "in")
+	got, err := loadKubernetesYAMLToResources(inFile, true)
+	require.NoError(t, err)
+
+	if *overrideTestData {
+		out, err := yaml.Marshal(got)
+		require.NoError(t, err)
+		require.NoError(t, file.Write(string(out), filepath.Join("testdata", "all-resources.out.yaml")))
+	}
+
+	want := &Resources{}
+	outFile := requireTestDataFile(t, "all-resources", "out")
+	mustUnmarshal(t, outFile, want)
+
+	opts := []cmp.Option{
+		cmpopts.IgnoreFields(Resources{}, "serviceMap"),
+		cmpopts.EquateEmpty(),
+	}
+	require.Empty(t, cmp.Diff(want, got, opts...))
+}
+
+func requireTestDataFile(t *testing.T, name, ioType string) []byte {
+	t.Helper()
+	content, err := os.ReadFile(filepath.Join("testdata", fmt.Sprintf("%s.%s.yaml", name, ioType)))
+	require.NoError(t, err)
+	return content
+}
+
+func mustUnmarshal(t *testing.T, val []byte, out interface{}) {
+	require.NoError(t, yaml.UnmarshalStrict(val, out, yaml.DisallowUnknownFields))
+}
diff --git a/internal/gatewayapi/resource/testdata/all-resources.in.yaml b/internal/gatewayapi/resource/testdata/all-resources.in.yaml
new file mode 100644
index 000000000..733660178
--- /dev/null
+++ b/internal/gatewayapi/resource/testdata/all-resources.in.yaml
@@ -0,0 +1,231 @@
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyProxy
+metadata:
+  name: example
+  namespace: default
+spec:
+  provider:
+    type: Kubernetes
+    kubernetes:
+      envoyService:
+        annotations:
+          custom1: svc-annotation1
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+  name: eg
+spec:
+  controllerName: gateway.envoyproxy.io/gatewayclass-controller
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: eg
+spec:
+  gatewayClassName: eg
+  listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+---
+apiVersion: gateway.networking.k8s.io/v1alpha2
+kind: TCPRoute
+metadata:
+  name: backend
+  namespace: default
+spec:
+  parentRefs:
+    - name: eg
+      sectionName: tcp
+  rules:
+    - backendRefs:
+        - name: backend
+          port: 3000
+---
+apiVersion: gateway.networking.k8s.io/v1alpha2
+kind: UDPRoute
+metadata:
+  name: backend
+  namespace: default
+spec:
+  parentRefs:
+    - name: eg
+      sectionName: udp
+  rules:
+    - backendRefs:
+        - name: backend
+          port: 3000
+---
+apiVersion: gateway.networking.k8s.io/v1alpha2
+kind: TLSRoute
+metadata:
+  name: backend
+  namespace: default
+spec:
+  parentRefs:
+    - name: eg
+      sectionName: tls-passthrough
+  rules:
+    - backendRefs:
+        - name: backend
+          port: 3000
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: backend
+  namespace: default
+spec:
+  parentRefs:
+    - name: eg
+  hostnames:
+    - "www.example.com"
+  rules:
+    - backendRefs:
+        - name: providedBackend
+          port: 8000
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: GRPCRoute
+metadata:
+  name: backend
+  namespace: default
+spec:
+  parentRefs:
+    - name: eg
+      sectionName: grpc
+  hostnames:
+    - "www.grpc-example.com"
+  rules:
+    - matches:
+        - method:
+            service: com.example.Things
+            method: DoThing
+          headers:
+            - name: com.example.Header
+              value: foobar
+      backendRefs:
+        - name: providedBackend
+          port: 9000
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyPatchPolicy
+metadata:
+  name: ratelimit-patch-policy
+  namespace: default
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: Gateway
+    name: eg
+  type: JSONPatch
+  jsonPatches:
+    - type: "type.googleapis.com/envoy.config.listener.v3.Listener"
+      # The listener name is of the form <GatewayNamespace>/<GatewayName>/<GatewayListenerName>
+      name: default/eg/http
+      operation:
+        op: add
+        path: "/default_filter_chain/filters/0/typed_config/http_filters/0"
+        value:
+          name: "envoy.filters.http.ratelimit"
+          typed_config:
+            "@type": "type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit"
+            domain: "eag-ratelimit"
+            failure_mode_deny: true
+            timeout: 1s
+            rate_limit_service:
+              grpc_service:
+                envoy_grpc:
+                  cluster_name: rate-limit-cluster
+              transport_api_version: V3
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: SecurityPolicy
+metadata:
+  name: jwt-example
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: backend
+  apiKeyAuth:
+    credentialRefs:
+      - name: foobar
+    extractFrom:
+      - headers:
+          - foobar
+  jwt:
+    providers:
+      - name: example
+        remoteJWKS:
+          uri: https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/jwt/jwks.json
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: cookie-lb-policy
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: cookie-lb-route
+  loadBalancer:
+    type: ConsistentHash
+    consistentHash:
+      type: Cookie
+      cookie:
+        name: "Lb-Test-Cookie"
+        ttl: 60s
+        attributes:
+          SameSite: Strict
+  retry:
+    retryOn:
+      httpStatusCodes:
+        - 200
+        - 404
+  healthCheck:
+    active:
+      type: HTTP
+      http:
+        path: "/"
+        method: GET
+  circuitBreaker:
+    maxRequestsPerConnection: 123
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: ClientTrafficPolicy
+metadata:
+  name: client-timeout
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: same-namespace
+  timeout:
+    http:
+      requestReceivedTimeout: 50ms
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: HTTPRouteFilter
+metadata:
+  name: direct-response-inline
+  namespace: default
+spec:
+  directResponse:
+    contentType: text/plain
+    body:
+      type: Inline
+      inline: "OK"
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: Backend
+metadata:
+  name: backend
+spec:
+  endpoints:
+    - ip:
+        address: 0.0.0.0
+        port: 4321
diff --git a/internal/gatewayapi/resource/testdata/all-resources.out.yaml b/internal/gatewayapi/resource/testdata/all-resources.out.yaml
new file mode 100644
index 000000000..162b782ab
--- /dev/null
+++ b/internal/gatewayapi/resource/testdata/all-resources.out.yaml
@@ -0,0 +1,360 @@
+backendTrafficPolicies:
+- kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: cookie-lb-policy
+    namespace: gateway-conformance-infra
+  spec:
+    circuitBreaker:
+      maxConnections: 1024
+      maxParallelRequests: 1024
+      maxParallelRetries: 1024
+      maxPendingRequests: 1024
+      maxRequestsPerConnection: 123
+    healthCheck:
+      active:
+        healthyThreshold: 1
+        http:
+          method: GET
+          path: /
+        interval: 3s
+        timeout: 1s
+        type: HTTP
+        unhealthyThreshold: 3
+    loadBalancer:
+      consistentHash:
+        cookie:
+          attributes:
+            SameSite: Strict
+          name: Lb-Test-Cookie
+          ttl: 1m0s
+        tableSize: 65537
+        type: Cookie
+      type: ConsistentHash
+    retry:
+      numRetries: 2
+      retryOn:
+        httpStatusCodes:
+        - 200
+        - 404
+    targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: cookie-lb-route
+  status:
+    ancestors: null
+backends:
+- kind: Backend
+  metadata:
+    creationTimestamp: null
+    name: backend
+    namespace: envoy-gateway-system
+  spec:
+    endpoints:
+    - ip:
+        address: 0.0.0.0
+        port: 4321
+  status: {}
+clientTrafficPolicies:
+- kind: ClientTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: client-timeout
+    namespace: gateway-conformance-infra
+  spec:
+    targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: same-namespace
+    timeout:
+      http:
+        requestReceivedTimeout: 50ms
+  status:
+    ancestors: null
+envoyPatchPolicies:
+- kind: EnvoyPatchPolicy
+  metadata:
+    creationTimestamp: null
+    name: ratelimit-patch-policy
+    namespace: default
+  spec:
+    jsonPatches:
+    - name: default/eg/http
+      operation:
+        op: add
+        path: /default_filter_chain/filters/0/typed_config/http_filters/0
+        value:
+          name: envoy.filters.http.ratelimit
+          typed_config:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
+            domain: eag-ratelimit
+            failure_mode_deny: true
+            rate_limit_service:
+              grpc_service:
+                envoy_grpc:
+                  cluster_name: rate-limit-cluster
+              transport_api_version: V3
+            timeout: 1s
+      type: type.googleapis.com/envoy.config.listener.v3.Listener
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
+    type: JSONPatch
+  status:
+    ancestors: null
+envoyProxyForGatewayClass:
+  kind: EnvoyProxy
+  metadata:
+    creationTimestamp: null
+    name: example
+    namespace: default
+  spec:
+    logging:
+      level:
+        default: warn
+    provider:
+      kubernetes:
+        envoyService:
+          annotations:
+            custom1: svc-annotation1
+          externalTrafficPolicy: Local
+          type: LoadBalancer
+      type: Kubernetes
+  status: {}
+gatewayClass:
+  kind: GatewayClass
+  metadata:
+    creationTimestamp: null
+    name: eg
+    namespace: envoy-gateway-system
+  spec:
+    controllerName: gateway.envoyproxy.io/gatewayclass-controller
+  status: {}
+gateways:
+- kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: eg
+    namespace: envoy-gateway-system
+  spec:
+    gatewayClassName: eg
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: http
+      port: 80
+      protocol: HTTP
+  status: {}
+grpcRoutes:
+- kind: GRPCRoute
+  metadata:
+    creationTimestamp: null
+    name: backend
+    namespace: default
+  spec:
+    hostnames:
+    - www.grpc-example.com
+    parentRefs:
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
+      sectionName: grpc
+    rules:
+    - backendRefs:
+      - group: ""
+        kind: Service
+        name: providedBackend
+        port: 9000
+        weight: 1
+      matches:
+      - headers:
+        - name: com.example.Header
+          type: Exact
+          value: foobar
+        method:
+          method: DoThing
+          service: com.example.Things
+          type: Exact
+  status:
+    parents: null
+httpFilters:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: HTTPRouteFilter
+  metadata:
+    creationTimestamp: null
+    name: direct-response-inline
+    namespace: default
+  spec:
+    directResponse:
+      body:
+        inline: OK
+        type: Inline
+      contentType: text/plain
+httpRoutes:
+- kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: backend
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
+    rules:
+    - backendRefs:
+      - group: ""
+        kind: Service
+        name: providedBackend
+        port: 8000
+        weight: 1
+      matches:
+      - path:
+          type: PathPrefix
+          value: /
+  status:
+    parents: null
+namespaces:
+- metadata:
+    creationTimestamp: null
+    name: envoy-gateway-system
+  spec: {}
+  status: {}
+- metadata:
+    creationTimestamp: null
+    name: default
+  spec: {}
+  status: {}
+- metadata:
+    creationTimestamp: null
+    name: gateway-conformance-infra
+  spec: {}
+  status: {}
+securityPolicies:
+- kind: SecurityPolicy
+  metadata:
+    creationTimestamp: null
+    name: jwt-example
+    namespace: envoy-gateway-system
+  spec:
+    apiKeyAuth:
+      credentialRefs:
+      - group: ""
+        kind: Secret
+        name: foobar
+      extractFrom:
+      - headers:
+        - foobar
+    jwt:
+      providers:
+      - name: example
+        remoteJWKS:
+          uri: https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/jwt/jwks.json
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: backend
+  status:
+    ancestors: null
+services:
+- metadata:
+    creationTimestamp: null
+    name: backend
+    namespace: default
+  spec:
+    clusterIP: 1.2.3.4
+    ports:
+    - name: TCP-3000
+      port: 3000
+      protocol: TCP
+      targetPort: 0
+    - name: UDP-3000
+      port: 3000
+      protocol: UDP
+      targetPort: 0
+  status:
+    loadBalancer: {}
+- metadata:
+    creationTimestamp: null
+    name: providedBackend
+    namespace: default
+  spec:
+    clusterIP: 1.2.3.4
+    ports:
+    - name: TCP-8000
+      port: 8000
+      protocol: TCP
+      targetPort: 0
+    - name: TCP-9000
+      port: 9000
+      protocol: TCP
+      targetPort: 0
+  status:
+    loadBalancer: {}
+tcpRoutes:
+- kind: TCPRoute
+  metadata:
+    creationTimestamp: null
+    name: backend
+    namespace: default
+  spec:
+    parentRefs:
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
+      sectionName: tcp
+    rules:
+    - backendRefs:
+      - group: ""
+        kind: Service
+        name: backend
+        port: 3000
+        weight: 1
+  status:
+    parents: null
+tlsRoutes:
+- kind: TLSRoute
+  metadata:
+    creationTimestamp: null
+    name: backend
+    namespace: default
+  spec:
+    parentRefs:
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
+      sectionName: tls-passthrough
+    rules:
+    - backendRefs:
+      - group: ""
+        kind: Service
+        name: backend
+        port: 3000
+        weight: 1
+  status:
+    parents: null
+udpRoutes:
+- kind: UDPRoute
+  metadata:
+    creationTimestamp: null
+    name: backend
+    namespace: default
+  spec:
+    parentRefs:
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
+      sectionName: udp
+    rules:
+    - backendRefs:
+      - group: ""
+        kind: Service
+        name: backend
+        port: 3000
+        weight: 1
+  status:
+    parents: null
diff --git a/internal/gatewayapi/resource/testdata/schema/crd.yaml b/internal/gatewayapi/resource/testdata/schema/crd.yaml
new file mode 100644
index 000000000..69c42f323
--- /dev/null
+++ b/internal/gatewayapi/resource/testdata/schema/crd.yaml
@@ -0,0 +1,79 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: testcrs.example.com
+spec:
+  group: example.com
+  versions:
+    - name: v1
+      served: true
+      storage: true
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                stringField:
+                  type: string
+                  default: "defaultString"
+                  minLength: 3
+                  maxLength: 10
+                integerField:
+                  type: integer
+                  default: 42
+                  minimum: 0
+                  maximum: 100
+                floatField:
+                  type: number
+                  default: 3.14
+                  minimum: 0.0
+                  maximum: 10.0
+                booleanField:
+                  type: boolean
+                  default: true
+                enumField:
+                  type: string
+                  enum:
+                    - option1
+                    - option2
+                    - option3
+                  default: "option1"
+                arrayField:
+                  type: array
+                  items:
+                    type: string
+                  minItems: 1
+                  maxItems: 5
+                objectField:
+                  type: object
+                  properties:
+                    nestedString:
+                      type: string
+                      default: "nestedDefault"
+                    nestedInteger:
+                      type: integer
+                      default: 10
+                      minimum: 1
+                      maximum: 20
+                  required:
+                    - nestedString
+                mapField:
+                  type: object
+                  additionalProperties:
+                    type: string
+                  default:
+                    key1: "value1"
+                    key2: "value2"
+              required:
+                - stringField
+                - integerField
+                - booleanField
+  scope: Namespaced
+  names:
+    plural: testcrs
+    singular: testcr
+    kind: TestCR
+    shortNames:
+      - tc
diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.in.yaml
new file mode 100644
index 000000000..e47c317a3
--- /dev/null
+++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.in.yaml
@@ -0,0 +1,85 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    namespace: envoy-gateway
+    name: gateway-1
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      allowedRoutes:
+        namespaces:
+          from: All
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-1
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/foo"
+      backendRefs:
+      - name: service-1
+        port: 8080
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-2
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/bar"
+      backendRefs:
+      - name: service-1
+        port: 8080
+envoyextensionpolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  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
+    lua:
+    - type: Inline
+      inline: "function envoy_on_request(request_handle)
+      request_handle:logInfo('Goodbye.')
+      end"
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    namespace: default
+    name: policy-for-http-route   # Invalid Lua
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+    lua:
+    - type: Inline
+      inline: "function envoy_on_response(response_handle)
+    response_handle:UnknownApi()
+    end"
diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml
new file mode 100644
index 000000000..cfad596e7
--- /dev/null
+++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml
@@ -0,0 +1,281 @@
+envoyExtensionPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-http-route
+    namespace: default
+  spec:
+    lua:
+    - inline: function envoy_on_response(response_handle) response_handle:UnknownApi()
+        end
+      type: Inline
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: "Lua: validation failed for lua body in policy with name envoyextensionpolicy/default/policy-for-http-route/lua/0:
+          failed to mock run envoy_on_response: <string>:614: attempt to call a non-function
+          object\nstack traceback:\n\t<string>:614: in function 'envoy_on_response'\n\t<string>:615:
+          in main chunk\n\t[G]: ?."
+        reason: Invalid
+        status: "False"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-gateway
+    namespace: envoy-gateway
+  spec:
+    lua:
+    - inline: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.')
+        end
+      type: Inline
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: gateway-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: 'This policy is being overridden by other envoyExtensionPolicies
+          for these routes: [default/httproute-1]'
+        reason: Overridden
+        status: "True"
+        type: Overridden
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+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: 2
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /foo
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-2
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /bar
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+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
+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
+        directResponse:
+          statusCode: 500
+        envoyExtensions:
+          luas:
+          - Code: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.')
+              end
+            Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0
+        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
+      - destination:
+          name: httproute/default/httproute-2/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        envoyExtensions:
+          luas:
+          - Code: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.')
+              end
+            Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0
+        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
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.in.yaml
new file mode 100644
index 000000000..c2825cd11
--- /dev/null
+++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.in.yaml
@@ -0,0 +1,229 @@
+configmaps:
+- apiVersion: v1
+  kind: ConfigMap
+  metadata:
+    name: cm-policy-for-gateway
+    namespace: envoy-gateway
+  data:
+    lua: |
+      function envoy_on_request(request_handle)
+      request_handle:logInfo('Goodbye.')
+      end
+- apiVersion: v1
+  kind: ConfigMap
+  metadata:
+    name: cm-policy-for-http-route
+    namespace: default
+  data:
+    lua: |
+      function envoy_on_response(response_handle)
+      response_handle:logWarn('Goodbye.')
+      end
+- apiVersion: v1
+  kind: ConfigMap
+  metadata:
+    name: cm-policy-for-http-route-no-key
+    namespace: default
+  data:
+    key: |
+      function envoy_on_response(response_handle)
+      response_handle:logWarn('Goodbye.')
+      end
+- apiVersion: v1
+  kind: ConfigMap
+  metadata:
+    name: cm-policy-for-http-route-empty
+    namespace: default
+  data: {}
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    namespace: envoy-gateway
+    name: gateway-1
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      allowedRoutes:
+        namespaces:
+          from: All
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-1
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/foo"
+      backendRefs:
+      - name: service-1
+        port: 8080
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-2
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/bar"
+      backendRefs:
+      - name: service-1
+        port: 8080
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-3
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/bar3"
+      backendRefs:
+      - name: service-1
+        port: 8080
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-4
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/bar4"
+      backendRefs:
+      - name: service-1
+        port: 8080
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-5
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/bar5"
+      backendRefs:
+      - name: service-1
+        port: 8080
+envoyextensionpolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  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
+    lua:
+    - type: ValueRef
+      valueRef:
+        name: cm-policy-for-gateway
+        kind: ConfigMap
+        group: v1
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    namespace: default
+    name: policy-for-http-route   # This policy should attach httproute-1
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+    lua:
+    - type: ValueRef
+      valueRef:
+        name: cm-policy-for-http-route
+        kind: ConfigMap
+        group: v1
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    namespace: default
+    name: policy-for-http-route-4
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-3
+    lua:
+    - type: ValueRef
+      valueRef:
+        name: cm-policy-for-http-route-no-exist
+        kind: ConfigMap
+        group: v1
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    namespace: default
+    name: policy-for-http-route-4
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-4
+    lua:
+    - type: ValueRef
+      valueRef:
+        name: cm-policy-for-http-route-no-key
+        kind: ConfigMap
+        group: v1
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    namespace: default
+    name: policy-for-http-route-5
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-5
+    lua:
+    - type: ValueRef
+      valueRef:
+        name: cm-policy-for-http-route-empty
+        kind: ConfigMap
+        group: v1
diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.out.yaml
new file mode 100644
index 000000000..6ef4cb93a
--- /dev/null
+++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.out.yaml
@@ -0,0 +1,551 @@
+envoyExtensionPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-http-route
+    namespace: default
+  spec:
+    lua:
+    - type: ValueRef
+      valueRef:
+        group: v1
+        kind: ConfigMap
+        name: cm-policy-for-http-route
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-http-route-4
+    namespace: default
+  spec:
+    lua:
+    - type: ValueRef
+      valueRef:
+        group: v1
+        kind: ConfigMap
+        name: cm-policy-for-http-route-no-exist
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-3
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: 'Lua: can''t find the referenced configmap cm-policy-for-http-route-no-exist
+          in namespace default.'
+        reason: Invalid
+        status: "False"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-http-route-5
+    namespace: default
+  spec:
+    lua:
+    - type: ValueRef
+      valueRef:
+        group: v1
+        kind: ConfigMap
+        name: cm-policy-for-http-route-empty
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-5
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: 'Lua: can''t find the key lua in the referenced configmap cm-policy-for-http-route-empty.'
+        reason: Invalid
+        status: "False"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-gateway
+    namespace: envoy-gateway
+  spec:
+    lua:
+    - type: ValueRef
+      valueRef:
+        group: v1
+        kind: ConfigMap
+        name: cm-policy-for-gateway
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: gateway-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: 'This policy is being overridden by other envoyExtensionPolicies
+          for these routes: [default/httproute-1 default/httproute-3 default/httproute-4
+          default/httproute-5]'
+        reason: Overridden
+        status: "True"
+        type: Overridden
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+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: 5
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /foo
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-2
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /bar
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-3
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /bar3
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-4
+    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: /bar4
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-5
+    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: /bar5
+  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
+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-3/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        directResponse:
+          statusCode: 500
+        envoyExtensions:
+          luas:
+          - Code: |
+              function envoy_on_request(request_handle)
+              request_handle:logInfo('Goodbye.')
+              end
+            Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0
+        hostname: www.example.com
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-3
+          namespace: default
+        name: httproute/default/httproute-3/rule/0/match/0/www_example_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /bar3
+      - destination:
+          name: httproute/default/httproute-4/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        directResponse:
+          statusCode: 500
+        envoyExtensions:
+          luas:
+          - Code: |
+              function envoy_on_request(request_handle)
+              request_handle:logInfo('Goodbye.')
+              end
+            Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0
+        hostname: www.example.com
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-4
+          namespace: default
+        name: httproute/default/httproute-4/rule/0/match/0/www_example_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /bar4
+      - destination:
+          name: httproute/default/httproute-5/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        directResponse:
+          statusCode: 500
+        envoyExtensions:
+          luas:
+          - Code: |
+              function envoy_on_request(request_handle)
+              request_handle:logInfo('Goodbye.')
+              end
+            Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0
+        hostname: www.example.com
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-5
+          namespace: default
+        name: httproute/default/httproute-5/rule/0/match/0/www_example_com
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /bar5
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        envoyExtensions:
+          luas:
+          - Code: |
+              function envoy_on_response(response_handle)
+              response_handle:logWarn('Goodbye.')
+              end
+            Name: envoyextensionpolicy/default/policy-for-http-route/lua/0
+        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
+      - destination:
+          name: httproute/default/httproute-2/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        envoyExtensions:
+          luas:
+          - Code: |
+              function envoy_on_request(request_handle)
+              request_handle:logInfo('Goodbye.')
+              end
+            Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0
+        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
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.in.yaml
new file mode 100644
index 000000000..65dd36d9f
--- /dev/null
+++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.in.yaml
@@ -0,0 +1,85 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    namespace: envoy-gateway
+    name: gateway-1
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      allowedRoutes:
+        namespaces:
+          from: All
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-1
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/foo"
+      backendRefs:
+      - name: service-1
+        port: 8080
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-2
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/bar"
+      backendRefs:
+      - name: service-1
+        port: 8080
+envoyextensionpolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  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
+    lua:
+    - type: Inline
+      inline: "function envoy_on_request(request_handle)
+      request_handle:logInfo('Goodbye.')
+      end"
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    namespace: default
+    name: policy-for-http-route   # This policy should attach httproute-1
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+    lua:
+    - type: Inline
+      inline: "function envoy_on_response(response_handle)
+    response_handle:logWarn('Goodbye.')
+    end"
diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml
new file mode 100644
index 000000000..d4b53944d
--- /dev/null
+++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml
@@ -0,0 +1,276 @@
+envoyExtensionPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-http-route
+    namespace: default
+  spec:
+    lua:
+    - inline: function envoy_on_response(response_handle) response_handle:logWarn('Goodbye.')
+        end
+      type: Inline
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: EnvoyExtensionPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-gateway
+    namespace: envoy-gateway
+  spec:
+    lua:
+    - inline: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.')
+        end
+      type: Inline
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: gateway-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: 'This policy is being overridden by other envoyExtensionPolicies
+          for these routes: [default/httproute-1]'
+        reason: Overridden
+        status: "True"
+        type: Overridden
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+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: 2
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /foo
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-2
+    namespace: default
+  spec:
+    hostnames:
+    - www.example.com
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /bar
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+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
+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
+        envoyExtensions:
+          luas:
+          - Code: function envoy_on_response(response_handle) response_handle:logWarn('Goodbye.')
+              end
+            Name: envoyextensionpolicy/default/policy-for-http-route/lua/0
+        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
+      - destination:
+          name: httproute/default/httproute-2/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        envoyExtensions:
+          luas:
+          - Code: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.')
+              end
+            Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0
+        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
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/provider/file/testdata/resources.all.yaml b/internal/provider/file/testdata/resources.all.yaml
index 079647dc6..19d93d0c2 100644
--- a/internal/provider/file/testdata/resources.all.yaml
+++ b/internal/provider/file/testdata/resources.all.yaml
@@ -28,7 +28,10 @@ gateways:
   spec:
     gatewayClassName: eg
     listeners:
-    - name: http
+    - allowedRoutes:
+        namespaces:
+          from: Same
+      name: http
       port: 8888
       protocol: HTTP
   status: {}
@@ -42,12 +45,15 @@ httpRoutes:
     hostnames:
     - www.example.com
     parentRefs:
-    - name: eg
+    - group: gateway.networking.k8s.io
+      kind: Gateway
+      name: eg
     rules:
     - backendRefs:
       - group: gateway.envoyproxy.io
         kind: Backend
         name: backend
+        weight: 1
       matches:
       - path:
           type: PathPrefix
diff --git a/internal/xds/translator/testdata/in/xds-ir/lua.yaml b/internal/xds/translator/testdata/in/xds-ir/lua.yaml
new file mode 100644
index 000000000..e72cb05ec
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/lua.yaml
@@ -0,0 +1,59 @@
+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
+    envoyExtensions:
+      luas:
+      - code: function envoy_on_request(request_handle)
+          request_handle:logInfo('Goodbye.')
+          end
+        name: envoyextensionpolicy/default/policy-for-http-route/lua/0
+  - destination:
+      name: httproute/default/httproute-2/rule/0
+      settings:
+      - addressType: IP
+        endpoints:
+        - host: 7.7.7.7
+          port: 8080
+        protocol: HTTP
+        weight: 1
+    hostname: www.example.com
+    isHTTP2: false
+    name: httproute/default/httproute-2/rule/0/match/0/www_example_com
+    pathMatch:
+      distinct: false
+      name: ""
+      prefix: /bar
+    envoyExtensions:
+      luas:
+      - code: function envoy_on_response(response_handle)
+          response_handle:logWarn('Goodbye.')
+          end
+        name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0
+      - code: function envoy_on_response(response_handle)
+          response_handle:logError('Hello.')
+          end
+        name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/1
diff --git a/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml
new file mode 100644
index 000000000..ba27dfd9d
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml
@@ -0,0 +1,34 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_PREFERRED
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-1/rule/0
+  ignoreHealthOnHostRemoval: true
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-1/rule/0
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_PREFERRED
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-2/rule/0
+  ignoreHealthOnHostRemoval: true
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-2/rule/0
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml
new file mode 100644
index 000000000..05442a9a1
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/lua.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/lua.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml
new file mode 100644
index 000000000..d28bf2fe2
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml
@@ -0,0 +1,55 @@
+- 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:
+        - disabled: true
+          name: envoy.filters.http.lua/envoyextensionpolicy/default/policy-for-http-route/lua/0
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
+            defaultSourceCode:
+              inlineString: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.')
+                end
+        - disabled: true
+          name: envoy.filters.http.lua/envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
+            defaultSourceCode:
+              inlineString: function envoy_on_response(response_handle) response_handle:logWarn('Goodbye.')
+                end
+        - disabled: true
+          name: envoy.filters.http.lua/envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/1
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
+            defaultSourceCode:
+              inlineString: function envoy_on_response(response_handle) response_handle:logError('Hello.')
+                end
+        - 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/lua.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml
new file mode 100644
index 000000000..307616b16
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml
@@ -0,0 +1,32 @@
+- 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.lua/envoyextensionpolicy/default/policy-for-http-route/lua/0:
+          '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig
+          config: {}
+    - 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.lua/envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0:
+          '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig
+          config: {}
+        envoy.filters.http.lua/envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/1:
+          '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig
+          config: {}
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index 49a929a4f..363d7bdd3 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -1043,6 +1043,7 @@ _Appears in:_
 | `envoy.filters.http.stateful_session` | EnvoyFilterSessionPersistence defines the Envoy HTTP session persistence filter.<br /> | 
 | `envoy.filters.http.ext_proc` | EnvoyFilterExtProc defines the Envoy HTTP external process filter.<br /> | 
 | `envoy.filters.http.wasm` | EnvoyFilterWasm defines the Envoy HTTP WebAssembly filter.<br /> | 
+| `envoy.filters.http.lua` | EnvoyFilterLua defines the Envoy HTTP Lua filter.<br /> | 
 | `envoy.filters.http.rbac` | EnvoyFilterRBAC defines the Envoy RBAC filter.<br /> | 
 | `envoy.filters.http.local_ratelimit` | EnvoyFilterLocalRateLimit defines the Envoy HTTP local rate limit filter.<br /> | 
 | `envoy.filters.http.ratelimit` | EnvoyFilterRateLimit defines the Envoy HTTP rate limit filter.<br /> | 
@@ -1483,7 +1484,7 @@ _Appears in:_
 | `extraArgs` | _string array_ |  false  |  | ExtraArgs defines additional command line options that are provided to Envoy.<br />More info: https://www.envoyproxy.io/docs/envoy/latest/operations/cli#command-line-options<br />Note: some command line options are used internally(e.g. --log-level) so they cannot be provided here. |
 | `mergeGateways` | _boolean_ |  false  |  | MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.<br />Setting this field to true would merge all Gateway Listeners under the parent Gateway Class.<br />This means that the port, protocol and hostname tuple must be unique for every listener.<br />If a duplicate listener is detected, the newer listener (based on timestamp) will be rejected and its status will be updated with a "Accepted=False" condition. |
 | `shutdown` | _[ShutdownConfig](#shutdownconfig)_ |  false  |  | Shutdown defines configuration for graceful envoy shutdown process. |
-| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  |  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.health_check<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_auth<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.stateful_session<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.rbac<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.custom_response<br /><br />- envoy.filters.http.router<br /><br />Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. |
+| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  |  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.health_check<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_auth<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.stateful_session<br /><br />- envoy.filters.http.lua<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.rbac<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.custom_response<br /><br />- envoy.filters.http.router<br /><br />Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. |
 | `backendTLS` | _[BackendTLSConfig](#backendtlsconfig)_ |  false  |  | BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends.<br />These settings are applied on backends for which TLS policies are specified. |
 | `ipFamily` | _[IPFamily](#ipfamily)_ |  false  |  | IPFamily specifies the IP family for the EnvoyProxy fleet.<br />This setting only affects the Gateway listener port and does not impact<br />other aspects of the Envoy proxy configuration.<br />If not specified, the system will operate as follows:<br />- It defaults to IPv4 only.<br />- IPv6 and dual-stack environments are not supported in this default configuration.<br />Note: To enable IPv6 or dual-stack functionality, explicit configuration is required. |
 | `preserveRouteOrder` | _boolean_ |  false  |  | PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API<br />specification (https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule)<br />or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list.<br />Default: False |
@@ -4297,7 +4298,8 @@ _Appears in:_
 | ---   | ---  | ---      | ---     | ---         |
 | `group` | _[Group](#group)_ |  true  | gateway.networking.k8s.io | Group is the group that this selector targets. Defaults to gateway.networking.k8s.io |
 | `kind` | _[Kind](#kind)_ |  true  |  | Kind is the resource kind that this selector targets. |
-| `matchLabels` | _object (keys:string, values:string)_ |  true  |  | MatchLabels are the set of label selectors for identifying the targeted resource |
+| `matchLabels` | _object (keys:string, values:string)_ |  false  |  | MatchLabels are the set of label selectors for identifying the targeted resource |
+| `matchExpressions` | _[LabelSelectorRequirement](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#labelselectorrequirement-v1-meta) array_ |  false  |  | MatchExpressions is a list of label selector requirements. The requirements are ANDed. |
 
 
 #### Timeout
diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md
index 49a929a4f..363d7bdd3 100644
--- a/site/content/zh/latest/api/extension_types.md
+++ b/site/content/zh/latest/api/extension_types.md
@@ -1043,6 +1043,7 @@ _Appears in:_
 | `envoy.filters.http.stateful_session` | EnvoyFilterSessionPersistence defines the Envoy HTTP session persistence filter.<br /> | 
 | `envoy.filters.http.ext_proc` | EnvoyFilterExtProc defines the Envoy HTTP external process filter.<br /> | 
 | `envoy.filters.http.wasm` | EnvoyFilterWasm defines the Envoy HTTP WebAssembly filter.<br /> | 
+| `envoy.filters.http.lua` | EnvoyFilterLua defines the Envoy HTTP Lua filter.<br /> | 
 | `envoy.filters.http.rbac` | EnvoyFilterRBAC defines the Envoy RBAC filter.<br /> | 
 | `envoy.filters.http.local_ratelimit` | EnvoyFilterLocalRateLimit defines the Envoy HTTP local rate limit filter.<br /> | 
 | `envoy.filters.http.ratelimit` | EnvoyFilterRateLimit defines the Envoy HTTP rate limit filter.<br /> | 
@@ -1483,7 +1484,7 @@ _Appears in:_
 | `extraArgs` | _string array_ |  false  |  | ExtraArgs defines additional command line options that are provided to Envoy.<br />More info: https://www.envoyproxy.io/docs/envoy/latest/operations/cli#command-line-options<br />Note: some command line options are used internally(e.g. --log-level) so they cannot be provided here. |
 | `mergeGateways` | _boolean_ |  false  |  | MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.<br />Setting this field to true would merge all Gateway Listeners under the parent Gateway Class.<br />This means that the port, protocol and hostname tuple must be unique for every listener.<br />If a duplicate listener is detected, the newer listener (based on timestamp) will be rejected and its status will be updated with a "Accepted=False" condition. |
 | `shutdown` | _[ShutdownConfig](#shutdownconfig)_ |  false  |  | Shutdown defines configuration for graceful envoy shutdown process. |
-| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  |  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.health_check<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_auth<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.stateful_session<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.rbac<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.custom_response<br /><br />- envoy.filters.http.router<br /><br />Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. |
+| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  |  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.health_check<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_auth<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.stateful_session<br /><br />- envoy.filters.http.lua<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.rbac<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.custom_response<br /><br />- envoy.filters.http.router<br /><br />Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. |
 | `backendTLS` | _[BackendTLSConfig](#backendtlsconfig)_ |  false  |  | BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends.<br />These settings are applied on backends for which TLS policies are specified. |
 | `ipFamily` | _[IPFamily](#ipfamily)_ |  false  |  | IPFamily specifies the IP family for the EnvoyProxy fleet.<br />This setting only affects the Gateway listener port and does not impact<br />other aspects of the Envoy proxy configuration.<br />If not specified, the system will operate as follows:<br />- It defaults to IPv4 only.<br />- IPv6 and dual-stack environments are not supported in this default configuration.<br />Note: To enable IPv6 or dual-stack functionality, explicit configuration is required. |
 | `preserveRouteOrder` | _boolean_ |  false  |  | PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API<br />specification (https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule)<br />or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list.<br />Default: False |
@@ -4297,7 +4298,8 @@ _Appears in:_
 | ---   | ---  | ---      | ---     | ---         |
 | `group` | _[Group](#group)_ |  true  | gateway.networking.k8s.io | Group is the group that this selector targets. Defaults to gateway.networking.k8s.io |
 | `kind` | _[Kind](#kind)_ |  true  |  | Kind is the resource kind that this selector targets. |
-| `matchLabels` | _object (keys:string, values:string)_ |  true  |  | MatchLabels are the set of label selectors for identifying the targeted resource |
+| `matchLabels` | _object (keys:string, values:string)_ |  false  |  | MatchLabels are the set of label selectors for identifying the targeted resource |
+| `matchExpressions` | _[LabelSelectorRequirement](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#labelselectorrequirement-v1-meta) array_ |  false  |  | MatchExpressions is a list of label selector requirements. The requirements are ANDed. |
 
 
 #### Timeout
diff --git a/test/cel-validation/envoyextensionpolicy_test.go b/test/cel-validation/envoyextensionpolicy_test.go
index ec401f2c1..c8f3fe776 100644
--- a/test/cel-validation/envoyextensionpolicy_test.go
+++ b/test/cel-validation/envoyextensionpolicy_test.go
@@ -445,8 +445,9 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) {
 						{
 							Type: egv1a1.LuaValueTypeValueRef,
 							ValueRef: &gwapiv1a2.LocalObjectReference{
-								Kind: gwapiv1a2.Kind("ConfigMap"),
-								Name: gwapiv1a2.ObjectName("eg"),
+								Kind:  gwapiv1a2.Kind("ConfigMap"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+								Group: gwapiv1a2.Group("v1"),
 							},
 						},
 					},
@@ -471,8 +472,9 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) {
 						{
 							Type: egv1a1.LuaValueTypeInline,
 							ValueRef: &gwapiv1a2.LocalObjectReference{
-								Kind: gwapiv1a2.Kind("ConfigMap"),
-								Name: gwapiv1a2.ObjectName("eg"),
+								Kind:  gwapiv1a2.Kind("ConfigMap"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+								Group: gwapiv1a2.Group("v1"),
 							},
 						},
 					},
@@ -524,8 +526,9 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) {
 						{
 							Type: egv1a1.LuaValueTypeValueRef,
 							ValueRef: &gwapiv1a2.LocalObjectReference{
-								Kind: gwapiv1a2.Kind("NotConfigMap"),
-								Name: gwapiv1a2.ObjectName("eg"),
+								Kind:  gwapiv1a2.Kind("NotConfigMap"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+								Group: gwapiv1a2.Group("v1"),
 							},
 						},
 					},
@@ -541,7 +544,36 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) {
 				}
 			},
 			wantErrors: []string{
-				"spec.lua[0].valueRef: Invalid value: \"object\": Only a reference to an object of kind ConfigMap belonging to default core API group is supported.",
+				"spec.lua[0].valueRef: Invalid value: \"object\": Only a reference to an object of kind ConfigMap belonging to default v1 API group is supported.",
+			},
+		},
+		{
+			desc: "Invalid Lua filter (source object group not default)",
+			mutate: func(sp *egv1a1.EnvoyExtensionPolicy) {
+				sp.Spec = egv1a1.EnvoyExtensionPolicySpec{
+					Lua: []egv1a1.Lua{
+						{
+							Type: egv1a1.LuaValueTypeValueRef,
+							ValueRef: &gwapiv1a2.LocalObjectReference{
+								Kind:  gwapiv1a2.Kind("ConfigMap"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+								Group: gwapiv1a2.Group(gwapiv1a2.GroupName),
+							},
+						},
+					},
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: "gateway.networking.k8s.io",
+								Kind:  "Gateway",
+								Name:  "eg",
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.lua[0].valueRef: Invalid value: \"object\": Only a reference to an object of kind ConfigMap belonging to default v1 API group is supported.",
 			},
 		},
 		{
@@ -553,8 +585,9 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) {
 							Type:   egv1a1.LuaValueTypeInline,
 							Inline: ptr.To("function envoy_on_response(response_handle) -- Do something -- end"),
 							ValueRef: &gwapiv1a2.LocalObjectReference{
-								Kind: gwapiv1a2.Kind("ConfigMap"),
-								Name: gwapiv1a2.ObjectName("eg"),
+								Kind:  gwapiv1a2.Kind("ConfigMap"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+								Group: gwapiv1a2.Group("v1"),
 							},
 						},
 					},
diff --git a/test/e2e/testdata/backend-traffic-policy-match-expression.yaml b/test/e2e/testdata/backend-traffic-policy-match-expression.yaml
new file mode 100644
index 000000000..21820c0e0
--- /dev/null
+++ b/test/e2e/testdata/backend-traffic-policy-match-expression.yaml
@@ -0,0 +1,43 @@
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: backend-traffic-match-expression-normal
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  hostnames: ["normal.example.com"]
+  rules:
+  - backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: backend-traffic-match-expression-injected
+  namespace: gateway-conformance-infra
+  labels:
+    inject: me
+spec:
+  parentRefs:
+  - name: same-namespace
+  hostnames: ["injected.example.com"]
+  rules:
+  - backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: backend-traffic-match-expression
+  namespace: gateway-conformance-infra
+spec:
+  targetSelectors:
+  - kind: HTTPRoute
+    matchExpressions: [{key: inject, operator: Exists}]
+  faultInjection:
+    abort:
+      httpStatus: 418
diff --git a/test/e2e/testdata/lua-http.yaml b/test/e2e/testdata/lua-http.yaml
new file mode 100644
index 000000000..37e425214
--- /dev/null
+++ b/test/e2e/testdata/lua-http.yaml
@@ -0,0 +1,100 @@
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: example-route-1-with-lua
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: same-namespace
+  hostnames: ["www.example.com"]
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /route1
+      backendRefs:
+        - name: infra-backend-v1
+          port: 8080
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: example-route-2-with-lua
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: same-namespace
+  hostnames: ["www.example.com"]
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /route2
+      backendRefs:
+        - name: infra-backend-v1
+          port: 8080
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: example-route-3-without-lua
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: same-namespace
+  hostnames: ["www.example.com"]
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /route3
+      backendRefs:
+        - name: infra-backend-v1
+          port: 8080
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cm-example-lua
+  namespace: gateway-conformance-infra
+data:
+  lua: |
+    function envoy_on_response(response_handle)
+      response_handle:headers():add("X-Custom-Lua-Header", "lua_value_1")
+    end
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyExtensionPolicy
+metadata:
+  name: example-lua-1
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: example-route-1-with-lua
+  lua:
+    - type: ValueRef
+      valueRef:
+        name: cm-example-lua
+        kind: ConfigMap
+        group: v1
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyExtensionPolicy
+metadata:
+  name: example-lua-2
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: example-route-2-with-lua
+  lua:
+    - type: Inline
+      inline: |
+        function envoy_on_response(response_handle)
+          response_handle:headers():add("X-Custom-Lua-Header", "lua_value_2")
+          response_handle:headers():add("X-Custom-Lua-Another-Header", "lua_another_value")
+        end
diff --git a/test/e2e/tests/backend_traffic_policy_match_expression.go b/test/e2e/tests/backend_traffic_policy_match_expression.go
new file mode 100644
index 000000000..1f35dc204
--- /dev/null
+++ b/test/e2e/tests/backend_traffic_policy_match_expression.go
@@ -0,0 +1,98 @@
+// 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
+
+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"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, BackendTrafficPolicyMatchExpressionTest)
+}
+
+var BackendTrafficPolicyMatchExpressionTest = suite.ConformanceTest{
+	ShortName:   "BackendTrafficPolicyMatchExpression",
+	Description: "Use match expression to select one HTTPRoute",
+	Manifests:   []string{"testdata/backend-traffic-policy-match-expression.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		t.Run("BackendTrafficPolicyMatchExpression", func(t *testing.T) {
+			ns := "gateway-conformance-infra"
+
+			routeNormal := types.NamespacedName{Name: "backend-traffic-match-expression-normal", Namespace: ns}
+			routeInjected := types.NamespacedName{Name: "backend-traffic-match-expression-injected", Namespace: ns}
+			gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+
+			gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t,
+				suite.Client,
+				suite.TimeoutConfig,
+				suite.ControllerName,
+				kubernetes.NewGatewayRef(gwNN),
+				routeNormal,
+				routeInjected,
+			)
+
+			BackendTrafficPolicyMustBeAccepted(t,
+				suite.Client,
+				types.NamespacedName{Name: "backend-traffic-match-expression", Namespace: ns},
+				suite.ControllerName,
+				gwapiv1a2.ParentReference{
+					Group:     gatewayapi.GroupPtr(gwapiv1.GroupName),
+					Kind:      gatewayapi.KindPtr(resource.KindGateway),
+					Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+					Name:      gwapiv1.ObjectName(gwNN.Name),
+				},
+			)
+
+			normalResponse := http.ExpectedResponse{
+				Namespace: ns,
+				Request: http.Request{
+					Host: "normal.example.com",
+					Path: "/",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t,
+				suite.RoundTripper,
+				suite.TimeoutConfig,
+				gwAddr,
+				normalResponse,
+			)
+
+			injectedResponse := http.ExpectedResponse{
+				Namespace: ns,
+				Request: http.Request{
+					Host: "injected.example.com",
+					Path: "/",
+				},
+				Response: http.Response{
+					StatusCode: 418,
+				},
+			}
+
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t,
+				suite.RoundTripper,
+				suite.TimeoutConfig,
+				gwAddr,
+				injectedResponse,
+			)
+		})
+	},
+}
diff --git a/test/e2e/tests/lua_http.go b/test/e2e/tests/lua_http.go
new file mode 100644
index 000000000..40df80973
--- /dev/null
+++ b/test/e2e/tests/lua_http.go
@@ -0,0 +1,154 @@
+// 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
+
+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"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, HTTPLuaTest)
+}
+
+// HTTPLuaTest tests Lua extension for a http route with HTTP Lua configured.
+var HTTPLuaTest = suite.ConformanceTest{
+	ShortName:   "LuaHTTP",
+	Description: "Test Lua extension that adds response headers",
+	Manifests:   []string{"testdata/lua-http.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		t.Run("http route with lua filter 1", func(t *testing.T) {
+			ns := "gateway-conformance-infra"
+			routeNN := types.NamespacedName{Name: "example-route-1-with-lua", Namespace: ns}
+			gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+			gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+
+			ancestorRef := gwapiv1a2.ParentReference{
+				Group:     gatewayapi.GroupPtr(gwapiv1.GroupName),
+				Kind:      gatewayapi.KindPtr(resource.KindGateway),
+				Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+				Name:      gwapiv1.ObjectName(gwNN.Name),
+			}
+			EnvoyExtensionPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "example-lua-1", Namespace: ns}, suite.ControllerName, ancestorRef)
+			EnvoyExtensionPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "example-lua-2", Namespace: ns}, suite.ControllerName, ancestorRef)
+
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Host: "www.example.com",
+					Path: "/route1",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+					Headers: map[string]string{
+						"X-Custom-Lua-Header": "lua_value_1",
+					},
+					AbsentHeaders: []string{"X-Custom-Lua-Another-Header"},
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+
+		t.Run("http route with lua filter 2", func(t *testing.T) {
+			ns := "gateway-conformance-infra"
+			routeNN := types.NamespacedName{Name: "example-route-2-with-lua", Namespace: ns}
+			gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+			gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+
+			ancestorRef := gwapiv1a2.ParentReference{
+				Group:     gatewayapi.GroupPtr(gwapiv1.GroupName),
+				Kind:      gatewayapi.KindPtr(resource.KindGateway),
+				Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+				Name:      gwapiv1.ObjectName(gwNN.Name),
+			}
+			EnvoyExtensionPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "example-lua-1", Namespace: ns}, suite.ControllerName, ancestorRef)
+			EnvoyExtensionPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "example-lua-2", Namespace: ns}, suite.ControllerName, ancestorRef)
+
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Host: "www.example.com",
+					Path: "/route2",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+					Headers: map[string]string{
+						"X-Custom-Lua-Header":         "lua_value_2",
+						"X-Custom-Lua-Another-Header": "lua_another_value",
+					},
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+
+		t.Run("http route without lua filter", func(t *testing.T) {
+			ns := "gateway-conformance-infra"
+			routeNN := types.NamespacedName{Name: "example-route-3-without-lua", Namespace: ns}
+			gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+			gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+
+			ancestorRef := gwapiv1a2.ParentReference{
+				Group:     gatewayapi.GroupPtr(gwapiv1.GroupName),
+				Kind:      gatewayapi.KindPtr(resource.KindGateway),
+				Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+				Name:      gwapiv1.ObjectName(gwNN.Name),
+			}
+			EnvoyExtensionPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "example-lua-1", Namespace: ns}, suite.ControllerName, ancestorRef)
+			EnvoyExtensionPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "example-lua-2", Namespace: ns}, suite.ControllerName, ancestorRef)
+
+			expectedResponse := http.ExpectedResponse{
+				Request: http.Request{
+					Host: "www.example.com",
+					Path: "/route3",
+				},
+				Response: http.Response{
+					StatusCode:    200,
+					AbsentHeaders: []string{"X-Custom-Lua-Header", "X-Custom-Lua-Another-Header"},
+				},
+				Namespace: ns,
+			}
+
+			req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http")
+			cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req)
+			if err != nil {
+				t.Errorf("failed to get expected response: %v", err)
+			}
+
+			if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil {
+				t.Errorf("failed to compare request and response: %v", err)
+			}
+		})
+	},
+}
diff --git a/test/e2e/tests/utils.go b/test/e2e/tests/utils.go
index a674e5fc6..786cc7fcd 100644
--- a/test/e2e/tests/utils.go
+++ b/test/e2e/tests/utils.go
@@ -330,11 +330,11 @@ func EnvoyExtensionPolicyMustBeAccepted(t *testing.T, client client.Client, poli
 		}
 
 		if policyAcceptedByAncestor(policy.Status.Ancestors, controllerName, ancestorRef) {
-			tlog.Logf(t, "EnvoyExtensionPolicy has been accepted: %v", policy)
+			tlog.Logf(t, "EnvoyExtensionPolicy has been accepted: %+v", policy)
 			return true, nil
 		}
 
-		tlog.Logf(t, "EnvoyExtensionPolicy not yet accepted: %v", policy)
+		tlog.Logf(t, "EnvoyExtensionPolicy not yet accepted: %+v", policy)
 		return false, nil
 	})