Repository: envoyproxy/gateway
Pull request #4493 (2024-10-22 07:24:03)

feat: implement Backend API for TLSRoute
impl: https://github.com/envoyproxy/gateway/issues/4035

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/4493?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 `9.09091%` with `10 lines` in your changes missing coverage. Please review.
> Project coverage is 65.98%. Comparing base [(`04fc944`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/04fc944f2cae7e15c23167c581389c958f4eaea1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`6671335`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/6671335614e008e53ce2c7bea9d3ee12539d39ef?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

| [Files with missing lines](https://app.codecov.io/gh/envoyproxy/gateway/pull/4493?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/provider/kubernetes/predicates.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/4493?src=pr&el=tree&filepath=internal%2Fprovider%2Fkubernetes%2Fpredicates.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvcHJvdmlkZXIva3ViZXJuZXRlcy9wcmVkaWNhdGVzLmdv) | 0.00% | [5 Missing and 5 partials :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/4493?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |

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


```diff
@@            Coverage Diff             @@
##             main    #4493      +/-   ##
==========================================
+ Coverage   65.97%   65.98%   +0.01%     
==========================================
  Files         203      203              
  Lines       31154    31154              
==========================================
+ Hits        20554    20558       +4     
+ Misses       9415     9413       -2     
+ Partials     1185     1183       -2     
```

</details>

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


Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 8dc586ce1..c50e9d08e 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,6 @@
 **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 
+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"
@@ -23,3 +23,9 @@ a new PR, and we will review the API part first. It will save you lots of implem
 Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
 -->
 Fixes #
+
+<!--
+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: Yes/No
diff --git a/api/v1alpha1/connection_types.go b/api/v1alpha1/connection_types.go
index 6f2779474..efb24dc3b 100644
--- a/api/v1alpha1/connection_types.go
+++ b/api/v1alpha1/connection_types.go
@@ -66,10 +66,9 @@ type BackendConnection struct {
 type ConnectionLimit struct {
 	// Value of the maximum concurrent connections limit.
 	// When the limit is reached, incoming connections will be closed after the CloseDelay duration.
-	// Default: unlimited.
 	//
-	// +kubebuilder:validation:Minimum=0
-	Value int64 `json:"value,omitempty"`
+	// +kubebuilder:validation:Minimum=1
+	Value int64 `json:"value"`
 
 	// CloseDelay defines the delay to use before closing connections that are rejected
 	// once the limit value is reached.
diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go
index d7a437510..72382d699 100644
--- a/api/v1alpha1/ratelimit_types.go
+++ b/api/v1alpha1/ratelimit_types.go
@@ -166,7 +166,6 @@ type HeaderMatch struct { // TODO: zhaohuabing this type could be replaced with
 	//
 	// +optional
 	// +kubebuilder:default=false
-	// +notImplementedHide
 	Invert *bool `json:"invert,omitempty"`
 }
 
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 582486e70..3e626f3f8 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
@@ -125,10 +125,11 @@ spec:
                         description: |-
                           Value of the maximum concurrent connections limit.
                           When the limit is reached, incoming connections will be closed after the CloseDelay duration.
-                          Default: unlimited.
                         format: int64
-                        minimum: 0
+                        minimum: 1
                         type: integer
+                    required:
+                    - value
                     type: object
                   socketBufferLimit:
                     allOf:
diff --git a/go.mod b/go.mod
index a10aa68bb..4d03c6822 100644
--- a/go.mod
+++ b/go.mod
@@ -63,7 +63,7 @@ require (
 
 require (
 	github.com/docker/docker v27.3.1+incompatible
-	github.com/replicatedhq/troubleshoot v0.107.0
+	github.com/replicatedhq/troubleshoot v0.107.1
 	google.golang.org/grpc v1.67.1
 	sigs.k8s.io/kubectl-validate v0.0.5-0.20240827210056-ce13d95db263
 )
diff --git a/go.sum b/go.sum
index dcf087e08..d11cb03bb 100644
--- a/go.sum
+++ b/go.sum
@@ -717,8 +717,8 @@ github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb
 github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=
 github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY=
 github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c=
-github.com/replicatedhq/troubleshoot v0.107.0 h1:AzaKBxNKuIQpERyitjJ4EINZTz4vlUacW5QguZZkTww=
-github.com/replicatedhq/troubleshoot v0.107.0/go.mod h1:AjaLpi9kidsFAuyAvBrvcG04w49WIDm7Iy6XyMVMv6U=
+github.com/replicatedhq/troubleshoot v0.107.1 h1:Hx9VbVv1r3M5fiH2fPTeoZ8LNIxh5R/e6vpe2jBgPfc=
+github.com/replicatedhq/troubleshoot v0.107.1/go.mod h1:6mZzcO/EWVBNXVnFdSHfPaoTnjcQdV3sq61NkBF60YE=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
diff --git a/internal/gatewayapi/validate.go b/internal/gatewayapi/validate.go
index 6b9a488b8..fb5d49957 100644
--- a/internal/gatewayapi/validate.go
+++ b/internal/gatewayapi/validate.go
@@ -288,7 +288,7 @@ func (t *Translator) validateBackendRefBackend(backendRef *gwapiv1a2.BackendRef,
 		return false
 	}
 
-	if kind != resource.KindHTTPRoute {
+	if kind != resource.KindHTTPRoute && kind != resource.KindTLSRoute {
 		status.SetRouteStatusCondition(routeStatus,
 			parentRef.routeParentStatusIdx,
 			route.GetGeneration(),
diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go
index a885d58ca..bfdecbd6e 100644
--- a/internal/provider/kubernetes/predicates.go
+++ b/internal/provider/kubernetes/predicates.go
@@ -353,40 +353,40 @@ func (r *gatewayAPIReconciler) isRouteReferencingBackend(nsName *types.Namespace
 	httpRouteList := &gwapiv1.HTTPRouteList{}
 	if err := r.client.List(ctx, httpRouteList, &client.ListOptions{
 		FieldSelector: fields.OneTermEqualSelector(backendHTTPRouteIndex, nsName.String()),
-	}); err != nil {
-		r.log.Error(err, "unable to find associated HTTPRoutes")
+	}); err != nil && !kerrors.IsNotFound(err) {
+		r.log.Error(err, "failed to find associated HTTPRoutes")
 		return false
 	}
 
 	grpcRouteList := &gwapiv1.GRPCRouteList{}
 	if err := r.client.List(ctx, grpcRouteList, &client.ListOptions{
 		FieldSelector: fields.OneTermEqualSelector(backendGRPCRouteIndex, nsName.String()),
-	}); err != nil {
-		r.log.Error(err, "unable to find associated GRPCRoutes")
+	}); err != nil && !kerrors.IsNotFound(err) {
+		r.log.Error(err, "failed to find associated GRPCRoutes")
 		return false
 	}
 
 	tlsRouteList := &gwapiv1a2.TLSRouteList{}
 	if err := r.client.List(ctx, tlsRouteList, &client.ListOptions{
 		FieldSelector: fields.OneTermEqualSelector(backendTLSRouteIndex, nsName.String()),
-	}); err != nil {
-		r.log.Error(err, "unable to find associated TLSRoutes")
+	}); err != nil && !kerrors.IsNotFound(err) {
+		r.log.Error(err, "failed to find associated TLSRoutes")
 		return false
 	}
 
 	tcpRouteList := &gwapiv1a2.TCPRouteList{}
 	if err := r.client.List(ctx, tcpRouteList, &client.ListOptions{
 		FieldSelector: fields.OneTermEqualSelector(backendTCPRouteIndex, nsName.String()),
-	}); err != nil {
-		r.log.Error(err, "unable to find associated TCPRoutes")
+	}); err != nil && !kerrors.IsNotFound(err) {
+		r.log.Error(err, "failed to find associated TCPRoutes")
 		return false
 	}
 
 	udpRouteList := &gwapiv1a2.UDPRouteList{}
 	if err := r.client.List(ctx, udpRouteList, &client.ListOptions{
 		FieldSelector: fields.OneTermEqualSelector(backendUDPRouteIndex, nsName.String()),
-	}); err != nil {
-		r.log.Error(err, "unable to find associated UDPRoutes")
+	}); err != nil && !kerrors.IsNotFound(err) {
+		r.log.Error(err, "failed to find associated UDPRoutes")
 		return false
 	}
 
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
new file mode 100644
index 000000000..524802636
--- /dev/null
+++ b/release-notes/current.yaml
@@ -0,0 +1,29 @@
+date: Pending
+
+# Changes that are expected to cause an incompatibility with previous versions, such as deletions or modifications to existing APIs.
+breaking changes: |
+  - Add a breaking change here
+
+# Updates addressing vulnerabilities, security flaws, or compliance requirements.
+security updates: |
+  - Add a security update here
+
+# New features or capabilities added in this release.
+new features: |
+  - Add a new feature here
+
+# Fixes for bugs identified in previous versions.
+bug fixes: |
+  - Add a bug fix here
+
+# Enhancements that improve performance.
+performance improvements: |
+  - Add a performance improvement here
+
+# Deprecated features or APIs.
+deprecations: |
+  - Add a deprecation here
+
+# Other notable changes not covered by the above sections.
+Other changes: |
+  - Add other changes here
diff --git a/site/content/en/contributions/RELEASING.md b/site/content/en/contributions/RELEASING.md
index 319bb73d0..70fff1c08 100644
--- a/site/content/en/contributions/RELEASING.md
+++ b/site/content/en/contributions/RELEASING.md
@@ -29,7 +29,13 @@ export GITHUB_REMOTE=origin
 ```
 
 1. Clone the repo, checkout the `main` branch, ensure it’s up-to-date, and your local branch is clean.
-2. Create a topic branch for adding the release notes and updating the [VERSION][] file with the release version. Refer to previous [release notes][] and [VERSION][] for additional details.
+2. Create a topic branch for adding the release notes and updating the [VERSION][] file with the release version. Refer to previous [release notes][] and [VERSION][] for additional details. The latest changes are already accumulated in the current.yaml file. Copy the content of the current.yaml file to the release notes file and clear the current.yaml file.
+
+   ```shell
+   echo "${MAJOR_VERSION}.${MINOR_VERSION}.0-rc.${RELEASE_CANDIDATE_NUMBER}" > VERSION
+   ```
+
+   __Note:__ The release candidate version should be in the format `${MAJOR_VERSION}.${MINOR_VERSION}.0-rc.${RELEASE_CANDIDATE_NUMBER}`.
 3. Sign, commit, and push your changes to your fork.
 4. Submit a [Pull Request][] to merge the changes into the `main` branch. Do not proceed until your PR has merged and
    the [Build and Test][] has successfully completed.
@@ -106,7 +112,7 @@ export GITHUB_REMOTE=origin
       ```
 
    1. Update the `Documentation` referred link on the menu in `site/hugo.toml`:
-   
+
       **DON'T FORGOT TO MOVE IT UNDER `LATEST`**
 
       ```shell
@@ -118,7 +124,7 @@ export GITHUB_REMOTE=origin
       ```
 
    1. Update `site/layouts/shortcodes/helm-version.html` base on latest minor version.
-   
+
       ```console
       {{- $pagePrefix := (index (split $.Page.File.Dir "/") 0) -}}
       {{- with (eq $pagePrefix "latest") -}}
@@ -133,7 +139,7 @@ export GITHUB_REMOTE=origin
       ```
 
    1. Update `site/layouts/shortcodes/yaml-version.html` base on latest minor version.
-      
+
       ```console
       {{- $pagePrefix := (index (split $.Page.File.Dir "/") 0) -}}
       {{- with (eq $pagePrefix "latest") -}}


Test patch:
diff --git a/internal/gatewayapi/testdata/tlsroute-with-backend.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-backend.out.yaml
index 13c3ef41c..97bce6d0a 100644
--- a/internal/gatewayapi/testdata/tlsroute-with-backend.out.yaml
+++ b/internal/gatewayapi/testdata/tlsroute-with-backend.out.yaml
@@ -100,10 +100,9 @@ tlsRoutes:
         status: "True"
         type: Accepted
       - lastTransitionTime: null
-        message: Resource default/backend-ip of type Backend is not supported for
-          TLSRoute routes
-        reason: UnsupportedValue
-        status: "False"
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
         type: ResolvedRefs
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
       parentRef:
@@ -122,7 +121,11 @@ xdsIR:
       - destination:
           name: tlsroute/default/tlsroute-1/rule/-1
           settings:
-          - weight: 1
+          - addressType: IP
+            endpoints:
+            - host: 1.1.1.1
+              port: 3001
+            weight: 1
         name: tlsroute/default/tlsroute-1
         tls:
           inspector:
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index 4f562fce6..c183a4f0b 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -782,7 +782,7 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `value` | _integer_ |  true  | Value of the maximum concurrent connections limit.<br />When the limit is reached, incoming connections will be closed after the CloseDelay duration.<br />Default: unlimited. |
+| `value` | _integer_ |  true  | Value of the maximum concurrent connections limit.<br />When the limit is reached, incoming connections will be closed after the CloseDelay duration. |
 | `closeDelay` | _[Duration](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Duration)_ |  false  | CloseDelay defines the delay to use before closing connections that are rejected<br />once the limit value is reached.<br />Default: none. |
 
 
diff --git a/site/content/en/latest/tasks/traffic/global-rate-limit.md b/site/content/en/latest/tasks/traffic/global-rate-limit.md
index 15cc462db..47eac33bc 100644
--- a/site/content/en/latest/tasks/traffic/global-rate-limit.md
+++ b/site/content/en/latest/tasks/traffic/global-rate-limit.md
@@ -433,11 +433,11 @@ server: envoy
 
 ```
 
-## Rate Limit Distinct Users 
+## Rate Limit Distinct Users Except Admin 
 
 Here is an example of a rate limit implemented by the application developer to limit distinct users who can be differentiated based on the 
 value in the `x-user-id` header. Here, user `one` (recognised from the traffic flow using the header `x-user-id` and value `one`) will be rate limited at 3 requests/hour
-and so will user `two` (recognised from the traffic flow using the header `x-user-id` and value `two`).
+and so will user `two` (recognised from the traffic flow using the header `x-user-id` and value `two`). But if `x-user-id` is `admin`, it will not be rate limited even beyond 3 requests/hour.
 
 {{< tabpane text=true >}}
 {{% tab header="Apply from stdin" %}}
@@ -461,6 +461,9 @@ spec:
         - headers:
           - type: Distinct
             name: x-user-id
+          - name: x-user-id
+            value: admin
+            invert: true
         limit:
           requests: 3
           unit: Hour
@@ -636,6 +639,47 @@ transfer-encoding: chunked
 
 ```
 
+But when the value for header `x-user-id` is set to `admin` and 4 requests are sent, all 4 of them should respond with 200 OK.
+
+```shell
+for i in {1..4}; do curl -I --header "Host: ratelimit.example" --header "x-user-id: admin" http://${GATEWAY_HOST}/get ; sleep 1; done
+```
+
+```console
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:31 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:32 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:33 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:33 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+```
+
 ## Rate Limit All Requests 
 
 This example shows you how to rate limit all requests matching the HTTPRoute rule at 3 requests/Hour by leaving the `clientSelectors` field unset.
@@ -809,7 +853,7 @@ spec:
   targetRefs:
   - group: gateway.networking.k8s.io
     kind: HTTPRoute
-    name: http-ratelimit 
+    name: http-ratelimit
   rateLimit:
     type: Global
     global:
@@ -858,7 +902,7 @@ spec:
   targetRefs:
   - group: gateway.networking.k8s.io
     kind: HTTPRoute
-    name: http-ratelimit 
+    name: http-ratelimit
   rateLimit:
     type: Global
     global:
diff --git a/site/content/en/latest/tasks/traffic/local-rate-limit.md b/site/content/en/latest/tasks/traffic/local-rate-limit.md
index 62e369044..05caf0725 100644
--- a/site/content/en/latest/tasks/traffic/local-rate-limit.md
+++ b/site/content/en/latest/tasks/traffic/local-rate-limit.md
@@ -245,6 +245,227 @@ server: envoy
 
 ```
 
+## Rate Limit Specific User Unless within Test Org
+
+Here is an example of a rate limit implemented by the application developer to limit a specific user by matching on a custom `x-user-id` header
+with a value set to `one`. But the user must not be limited if logging in within Test org, determined by custom header `x-org-id` set to `test`.
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Local
+    local:
+      rules:
+      - clientSelectors:
+        - headers:
+          - name: x-user-id
+            value: one
+          - name: x-org-id
+            value: test
+            invert: true
+        limit:
+          requests: 3
+          unit: Hour
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Local
+    local:
+      rules:
+      - clientSelectors:
+        - headers:
+          - name: x-user-id
+            value: one
+          - name: x-org-id
+            value: test
+            invert: true
+        limit:
+          requests: 3
+          unit: Hour
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+### HTTPRoute
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+The HTTPRoute status should indicate that it has been accepted and is bound to the example Gateway.
+
+```shell
+kubectl get httproute/http-ratelimit -o yaml
+```
+
+Get the Gateway's address:
+
+```shell
+export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')
+```
+
+Let's query `ratelimit.example/get` 4 times with `x-user-id` set to `one` and `x-org-id` set to `org1`. We should receive a `200` response from the example Gateway for the first 3 requests and the last request should be rate limited.
+
+```shell
+for i in {1..4}; do curl -I --header "Host: ratelimit.example" --header "x-user-id: one" --header "x-org-id: org1" http://${GATEWAY_HOST}/get ; sleep 1; done
+```
+
+```console
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:31 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:32 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:33 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 429 Too Many Requests
+x-envoy-ratelimited: true
+date: Wed, 08 Feb 2023 02:33:34 GMT
+server: envoy
+transfer-encoding: chunked
+
+```
+
+Let's query `ratelimit.example/get` 4 times with `x-user-id` set to `one` and `x-org-id` set to `test`. We should receive a `200` response from the example Gateway for all the 4 requests, unlike previous example where the last request was rate limited.
+
+```shell
+for i in {1..4}; do curl -I --header "Host: ratelimit.example" --header "x-user-id: one" --header "x-org-id: test" http://${GATEWAY_HOST}/get ; sleep 1; done
+```
+
+```console
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:31 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:32 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:33 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:33 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+```
+
 ## Rate Limit All Requests 
 
 This example shows you how to rate limit all requests matching the HTTPRoute rule at 3 requests/Hour by leaving the `clientSelectors` field unset.
diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md
index 4f562fce6..c183a4f0b 100644
--- a/site/content/zh/latest/api/extension_types.md
+++ b/site/content/zh/latest/api/extension_types.md
@@ -782,7 +782,7 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `value` | _integer_ |  true  | Value of the maximum concurrent connections limit.<br />When the limit is reached, incoming connections will be closed after the CloseDelay duration.<br />Default: unlimited. |
+| `value` | _integer_ |  true  | Value of the maximum concurrent connections limit.<br />When the limit is reached, incoming connections will be closed after the CloseDelay duration. |
 | `closeDelay` | _[Duration](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Duration)_ |  false  | CloseDelay defines the delay to use before closing connections that are rejected<br />once the limit value is reached.<br />Default: none. |
 
 
diff --git a/test/cel-validation/clienttrafficpolicy_test.go b/test/cel-validation/clienttrafficpolicy_test.go
index 14dd915d7..3558d1848 100644
--- a/test/cel-validation/clienttrafficpolicy_test.go
+++ b/test/cel-validation/clienttrafficpolicy_test.go
@@ -314,6 +314,52 @@ func TestClientTrafficPolicyTarget(t *testing.T) {
 				"spec.connection.bufferLimit: Invalid value: \"15m\": spec.connection.bufferLimit in body should match '^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$', <nil>: Invalid value: \"\"",
 			},
 		},
+		{
+			desc: "invalid Connection Limit Empty",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					Connection: &egv1a1.ClientConnection{
+						ConnectionLimit: &egv1a1.ConnectionLimit{},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.connection.connectionLimit.value: Invalid value: 0: spec.connection.connectionLimit.value in body should be greater than or equal to 1",
+			},
+		},
+		{
+			desc: "invalid Connection Limit < 1",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					Connection: &egv1a1.ClientConnection{
+						ConnectionLimit: &egv1a1.ConnectionLimit{
+							Value: -1, // Value: 0 is covered by existence test, as 0 is the nil value.
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.connection.connectionLimit.value: Invalid value: -1: spec.connection.connectionLimit.value in body should be greater than or equal to 1",
+			},
+		},
 		{
 			desc: "invalid InitialStreamWindowSize format",
 			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
diff --git a/test/e2e/base/manifests.yaml b/test/e2e/base/manifests.yaml
index db9a265cb..714dd2960 100644
--- a/test/e2e/base/manifests.yaml
+++ b/test/e2e/base/manifests.yaml
@@ -27,6 +27,14 @@ spec:
       allowedRoutes:
         namespaces:
           from: Same
+    - name: tls
+      protocol: TLS
+      port: 443
+      tls:
+        mode: Passthrough
+      allowedRoutes:
+        namespaces:
+          from: Same
 ---
 apiVersion: gateway.networking.k8s.io/v1
 kind: Gateway
diff --git a/test/e2e/testdata/ratelimit-header-invert-match.yaml b/test/e2e/testdata/ratelimit-header-invert-match-global.yaml
similarity index 100%
rename from test/e2e/testdata/ratelimit-header-invert-match.yaml
rename to test/e2e/testdata/ratelimit-header-invert-match-global.yaml
diff --git a/test/e2e/testdata/ratelimit-header-invert-match-local.yaml b/test/e2e/testdata/ratelimit-header-invert-match-local.yaml
new file mode 100644
index 000000000..0b390af22
--- /dev/null
+++ b/test/e2e/testdata/ratelimit-header-invert-match-local.yaml
@@ -0,0 +1,41 @@
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: ratelimit-specific-user
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit-specific-user
+  rateLimit:
+    type: Local
+    local:
+      rules:
+      - clientSelectors:
+        - headers:
+          - name: x-user-id
+            value: one
+          - name: x-org-id
+            value: test
+            invert: true
+        limit:
+          requests: 3
+          unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit-specific-user
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+    matches:
+    - path:
+        type: Exact
+        value: /ratelimit-specific-user
diff --git a/test/e2e/testdata/tlsroute-to-backend-fqdn.yaml b/test/e2e/testdata/tlsroute-to-backend-fqdn.yaml
new file mode 100644
index 000000000..cf02dc10e
--- /dev/null
+++ b/test/e2e/testdata/tlsroute-to-backend-fqdn.yaml
@@ -0,0 +1,27 @@
+apiVersion: gateway.networking.k8s.io/v1alpha2
+kind: TLSRoute
+metadata:
+  name: tlsroute-to-backend-fqdn
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+    sectionName: tls
+  hostnames:
+  - "example.com"
+  rules:
+  - backendRefs:
+    - group: gateway.envoyproxy.io
+      kind: Backend
+      name: backend-fqdn
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: Backend
+metadata:
+  name: backend-fqdn
+  namespace: gateway-conformance-infra
+spec:
+  endpoints:
+  - fqdn:
+      hostname: tls-backend-2.gateway-conformance-infra.svc.cluster.local
+      port: 443
diff --git a/test/e2e/testdata/tlsroute-to-backend-ip.yaml b/test/e2e/testdata/tlsroute-to-backend-ip.yaml
new file mode 100644
index 000000000..7206078e2
--- /dev/null
+++ b/test/e2e/testdata/tlsroute-to-backend-ip.yaml
@@ -0,0 +1,41 @@
+apiVersion: gateway.networking.k8s.io/v1alpha2
+kind: TLSRoute
+metadata:
+  name: tlsroute-to-backend-ip
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+    sectionName: tls
+  hostnames:
+  - "example.com"
+  rules:
+  - backendRefs:
+    - group: gateway.envoyproxy.io
+      kind: Backend
+      name: backend-ip
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: Backend
+metadata:
+  name: backend-ip
+  namespace: gateway-conformance-infra
+spec:
+  endpoints:
+  - ip:
+      address: 10.96.96.96
+      port: 443
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: tls-backend-2-clusterip
+  namespace: gateway-conformance-infra
+spec:
+  selector:
+    app: tls-backend-2
+  clusterIP: 10.96.96.96
+  ports:
+  - protocol: TCP
+    port: 443
+    targetPort: 8443
diff --git a/test/e2e/tests/route_with_backend.go b/test/e2e/tests/httproute_with_backend.go
similarity index 100%
rename from test/e2e/tests/route_with_backend.go
rename to test/e2e/tests/httproute_with_backend.go
diff --git a/test/e2e/tests/local_ratelimit.go b/test/e2e/tests/local_ratelimit.go
index 4a630d1ed..f35782de8 100644
--- a/test/e2e/tests/local_ratelimit.go
+++ b/test/e2e/tests/local_ratelimit.go
@@ -25,6 +25,7 @@ func init() {
 	ConformanceTests = append(ConformanceTests, LocalRateLimitSpecificUserTest)
 	ConformanceTests = append(ConformanceTests, LocalRateLimitAllTrafficTest)
 	ConformanceTests = append(ConformanceTests, LocalRateLimitNoLimitRouteTest)
+	ConformanceTests = append(ConformanceTests, LocalRateLimitHeaderInvertMatchTest)
 }
 
 var LocalRateLimitSpecificUserTest = suite.ConformanceTest{
@@ -207,3 +208,91 @@ var LocalRateLimitNoLimitRouteTest = suite.ConformanceTest{
 		})
 	},
 }
+
+var LocalRateLimitHeaderInvertMatchTest = suite.ConformanceTest{
+	ShortName:   "LocalRateLimitHeaderInvertMatch",
+	Description: "Limit a specific user unless in a specific org",
+	Manifests:   []string{"testdata/ratelimit-header-invert-match-local.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		t.Run("limit a specific user", func(t *testing.T) {
+			ns := "gateway-conformance-infra"
+			routeNN := types.NamespacedName{Name: "http-ratelimit-specific-user", 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),
+			}
+			BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "ratelimit-specific-user", Namespace: ns}, suite.ControllerName, ancestorRef)
+
+			expectOkResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/ratelimit-specific-user",
+					Headers: map[string]string{
+						"x-user-id": "one",
+						"x-org-id":  "org1",
+					},
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+
+			expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
+
+			expectLimitResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/ratelimit-specific-user",
+					Headers: map[string]string{
+						"x-user-id": "one",
+						"x-org-id":  "org1",
+					},
+				},
+				Response: http.Response{
+					StatusCode: 429,
+				},
+				Namespace: ns,
+			}
+			expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http")
+
+			// should just send exactly 4 requests, and expect 429
+
+			// keep sending requests till get 200 first, that will cost one 200
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)
+
+			// fire the rest request
+			if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
+				t.Errorf("fail to get expected response at first three request: %v", err)
+			}
+
+			// this request should be limited because the user is one and org is not test and the limit is 3
+			if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil {
+				t.Errorf("fail to get expected response at last fourth request: %v", err)
+			}
+
+			// with test org
+			expectOkResp = http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/ratelimit-specific-user",
+					Headers: map[string]string{
+						"x-user-id": "one",
+						"x-org-id":  "test",
+					},
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			expectOkReq = http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
+			// the requests should not be limited because the user is one but org is test
+			if err := GotExactExpectedResponse(t, 4, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
+				t.Errorf("fail to get expected response at first three request: %v", err)
+			}
+		})
+	},
+}
diff --git a/test/e2e/tests/ratelimit.go b/test/e2e/tests/ratelimit.go
index f0082d77e..d1e18f74b 100644
--- a/test/e2e/tests/ratelimit.go
+++ b/test/e2e/tests/ratelimit.go
@@ -26,7 +26,7 @@ import (
 func init() {
 	ConformanceTests = append(ConformanceTests, RateLimitCIDRMatchTest)
 	ConformanceTests = append(ConformanceTests, RateLimitHeaderMatchTest)
-	ConformanceTests = append(ConformanceTests, RateLimitHeaderInvertMatchTest)
+	ConformanceTests = append(ConformanceTests, GlobalRateLimitHeaderInvertMatchTest)
 	ConformanceTests = append(ConformanceTests, RateLimitHeadersDisabled)
 	ConformanceTests = append(ConformanceTests, RateLimitBasedJwtClaimsTest)
 	ConformanceTests = append(ConformanceTests, RateLimitMultipleListenersTest)
@@ -172,10 +172,10 @@ var RateLimitHeaderMatchTest = suite.ConformanceTest{
 	},
 }
 
-var RateLimitHeaderInvertMatchTest = suite.ConformanceTest{
-	ShortName:   "RateLimitHeaderInvertMatch",
+var GlobalRateLimitHeaderInvertMatchTest = suite.ConformanceTest{
+	ShortName:   "GlobalRateLimitHeaderInvertMatch",
 	Description: "Limit all requests that match distinct headers except for which invert is set to true",
-	Manifests:   []string{"testdata/ratelimit-header-invert-match.yaml"},
+	Manifests:   []string{"testdata/ratelimit-header-invert-match-global.yaml"},
 	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
 		ns := "gateway-conformance-infra"
 		routeNN := types.NamespacedName{Name: "header-ratelimit", Namespace: ns}
diff --git a/test/e2e/tests/tlsroute_with_backend.go b/test/e2e/tests/tlsroute_with_backend.go
new file mode 100644
index 000000000..c43162ee9
--- /dev/null
+++ b/test/e2e/tests/tlsroute_with_backend.go
@@ -0,0 +1,89 @@
+// 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"
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, TLSRouteBackendFQDNTest)
+	ConformanceTests = append(ConformanceTests, TLSRouteBackendIPTest)
+}
+
+var TLSRouteBackendFQDNTest = suite.ConformanceTest{
+	ShortName:   "TLSRouteBackendFQDNTest",
+	Description: "TLSRoutes with a backend ref to a Backend",
+	Manifests: []string{
+		"testdata/tlsroute-to-backend-fqdn.yaml",
+	},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		t.Run("TLSRoute with a FQDN type Backend", func(t *testing.T) {
+			testTLSRouteWithBackend(t, suite, "tlsroute-to-backend-fqdn", "backend-fqdn")
+		})
+	},
+}
+
+var TLSRouteBackendIPTest = suite.ConformanceTest{
+	ShortName:   "TLSRouteBackendIPTest",
+	Description: "TLSRoutes with a backend ref to a Backend",
+	Manifests: []string{
+		"testdata/tlsroute-to-backend-ip.yaml",
+	},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		t.Run("TLSRoute with a IP type Backend", func(t *testing.T) {
+			testTLSRouteWithBackend(t, suite, "tlsroute-to-backend-ip", "backend-ip")
+		})
+	},
+}
+
+func testTLSRouteWithBackend(t *testing.T, suite *suite.ConformanceTestSuite, route, backend string) {
+	ns := "gateway-conformance-infra"
+	routeNN := types.NamespacedName{Name: route, Namespace: ns}
+	gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+	gwAddr, _ := kubernetes.GatewayAndTLSRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "tls"), routeNN)
+	certNN := types.NamespacedName{Name: "backend-tls-certificate", Namespace: ns}
+
+	BackendMustBeAccepted(t, suite.Client, types.NamespacedName{Name: backend, Namespace: ns})
+
+	expected := http.ExpectedResponse{
+		Request: http.Request{
+			Host: "example.com",
+			Path: "/",
+		},
+		Response: http.Response{
+			StatusCode: 200,
+		},
+		Namespace: ns,
+	}
+
+	req := http.MakeRequest(t, &expected, gwAddr, "HTTPS", "https")
+
+	// This test uses the same key/cert pair as both a client cert and server cert
+	// Both backend and client treat the self-signed cert as a trusted CA
+	cPem, keyPem, err := GetTLSSecret(suite.Client, certNN)
+	if err != nil {
+		t.Fatalf("unexpected error finding TLS secret: %v", err)
+	}
+
+	WaitForConsistentMTLSResponse(
+		t,
+		suite.RoundTripper,
+		req,
+		expected,
+		suite.TimeoutConfig.RequiredConsecutiveSuccesses,
+		suite.TimeoutConfig.MaxTimeToConsistency,
+		cPem,
+		keyPem,
+		"example.com")
+}