Repository: envoyproxy/gateway
Pull request #4040 (2024-08-13 02:11:47)

docs: correct concepts page

zirain: @missBerg PTAL
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/4040?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 67.61%. Comparing base [(`eeb62c8`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/eeb62c88f8949f8da8a1278ec5515ffa1a004444?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`b8380f4`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/b8380f4f2f0761f1b7bf18a27bc1a8b9679b5a48?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).
> Report is 1 commits behind head on main.

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


```diff
@@            Coverage Diff             @@
##             main    #4040      +/-   ##
==========================================
+ Coverage   67.59%   67.61%   +0.01%     
==========================================
  Files         186      186              
  Lines       22757    22757              
==========================================
+ Hits        15383    15387       +4     
+ Misses       6268     6266       -2     
+ Partials     1106     1104       -2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/4040?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/workflows/codeql.yml b/.github/workflows/codeql.yml
index d3e4cd0a4..7cb7462e0 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@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a  # v3.25.15
+      uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa  # v3.26.0
       with:
         languages: ${{ matrix.language }}
 
     - name: Autobuild
-      uses: github/codeql-action/autobuild@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a  # v3.25.15
+      uses: github/codeql-action/autobuild@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa  # v3.26.0
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a  # v3.25.15
+      uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa  # v3.26.0
       with:
         category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/license-scan.yml b/.github/workflows/license-scan.yml
index 31014adf8..68143314e 100644
--- a/.github/workflows/license-scan.yml
+++ b/.github/workflows/license-scan.yml
@@ -18,7 +18,7 @@ jobs:
     - name: Checkout code
       uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332  # v4.1.7
     - name: Run scanner
-      uses: google/osv-scanner-action/osv-scanner-action@7ac94f9d40028db4cacf8d53adec6626f5d3d2f7  # v1.8.2
+      uses: google/osv-scanner-action/osv-scanner-action@c615bb556a9a61495d218c7d439e7c8abbbfb151  # v1.8.3
       with:
         scan-args: |-
           --skip-git
diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml
index 424ff41a1..8e2117a97 100644
--- a/.github/workflows/osv-scanner.yml
+++ b/.github/workflows/osv-scanner.yml
@@ -16,7 +16,7 @@ on:
 jobs:
   scan-scheduled:
     if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }}
-    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@7ac94f9d40028db4cacf8d53adec6626f5d3d2f7"  # v1.8.2
+    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@c615bb556a9a61495d218c7d439e7c8abbbfb151"  # v1.8.3
     permissions:
       actions: read
       contents: read
@@ -32,7 +32,7 @@ jobs:
 
   scan-pr:
     if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
-    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@7ac94f9d40028db4cacf8d53adec6626f5d3d2f7"  # v1.8.2
+    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@c615bb556a9a61495d218c7d439e7c8abbbfb151"  # v1.8.3
     permissions:
       actions: read
       contents: read
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 99a10164e..2af996a99 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@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a  # v3.25.15
+      uses: github/codeql-action/upload-sarif@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa  # v3.26.0
       with:
         sarif_file: results.sarif
diff --git a/api/v1alpha1/envoypatchpolicy_types.go b/api/v1alpha1/envoypatchpolicy_types.go
index 22effb697..b23002e67 100644
--- a/api/v1alpha1/envoypatchpolicy_types.go
+++ b/api/v1alpha1/envoypatchpolicy_types.go
@@ -111,7 +111,12 @@ type JSONPatchOperation struct {
 	Op JSONPatchOperationType `json:"op"`
 	// Path is the location of the target document/field where the operation will be performed
 	// Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details.
-	Path string `json:"path"`
+	// +optional
+	Path *string `json:"path,omitempty"`
+	// JSONPath specifies the locations of the target document/field where the operation will be performed
+	// Refer to https://datatracker.ietf.org/doc/rfc9535/ for more details.
+	// +optional
+	JSONPath *string `json:"jsonPath,omitempty"`
 	// From is the source location of the value to be copied or moved. Only valid
 	// for move or copy operations
 	// Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details.
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index ad7236580..79c8e98a5 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -2809,6 +2809,16 @@ func (in *ImageWasmCodeSource) DeepCopy() *ImageWasmCodeSource {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *JSONPatchOperation) DeepCopyInto(out *JSONPatchOperation) {
 	*out = *in
+	if in.Path != nil {
+		in, out := &in.Path, &out.Path
+		*out = new(string)
+		**out = **in
+	}
+	if in.JSONPath != nil {
+		in, out := &in.JSONPath, &out.JSONPath
+		*out = new(string)
+		**out = **in
+	}
 	if in.From != nil {
 		in, out := &in.From, &out.From
 		*out = new(string)
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml
index e385b0d4b..f57a64406 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml
@@ -71,6 +71,11 @@ spec:
                             for move or copy operations
                             Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details.
                           type: string
+                        jsonPath:
+                          description: |-
+                            JSONPath specifies the locations of the target document/field where the operation will be performed
+                            Refer to https://datatracker.ietf.org/doc/rfc9535/ for more details.
+                          type: string
                         op:
                           description: Op is the type of operation to perform
                           enum:
@@ -93,7 +98,6 @@ spec:
                           x-kubernetes-preserve-unknown-fields: true
                       required:
                       - op
-                      - path
                       type: object
                     type:
                       description: Type is the typed URL of the Envoy xDS Resource
diff --git a/go.mod b/go.mod
index e23ce14ff..6fbae3f4d 100644
--- a/go.mod
+++ b/go.mod
@@ -27,6 +27,7 @@ require (
 	github.com/grafana/tempo v1.5.0
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/miekg/dns v1.1.61
+	github.com/ohler55/ojg v1.22.1
 	github.com/prometheus/client_golang v1.19.1
 	github.com/prometheus/common v0.55.0
 	github.com/spf13/cobra v1.8.1
diff --git a/go.sum b/go.sum
index cdfd32616..1b08c27c2 100644
--- a/go.sum
+++ b/go.sum
@@ -899,6 +899,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/ohler55/ojg v1.22.1 h1:MvUieaWTwksoYk47GYyP9kzXIAkxHYX6rxeLjUEeq/8=
+github.com/ohler55/ojg v1.22.1/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
 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=
diff --git a/internal/cmd/egctl/translate.go b/internal/cmd/egctl/translate.go
index 2bf4cda17..ceb4e9dee 100644
--- a/internal/cmd/egctl/translate.go
+++ b/internal/cmd/egctl/translate.go
@@ -63,47 +63,48 @@ func newTranslateCommand() *cobra.Command {
 		addMissingResources                  bool
 		outTypes                             []string
 		dnsDomain                            string
+		namespace                            string
 	)
 
 	translateCommand := &cobra.Command{
 		Use:   "translate",
 		Short: "Translate Configuration from an input type to an output type",
 		Example: `  # Translate Gateway API Resources into All xDS Resources.
-  egctl experimental translate --from gateway-api --to xds --file <input file>
+  egctl experimental translate --from gateway-api --to xds --file <input file> -n <namespace>
 
   # Translate Gateway API Resources into All xDS Resources in JSON output.
-  egctl experimental translate --from gateway-api --to xds --type all --output json --file <input file>
+  egctl experimental translate --from gateway-api --to xds --type all --output json --file <input file> -n <namespace>
 
   # Translate Gateway API Resources into All xDS Resources in YAML output.
-  egctl experimental translate --from gateway-api --to xds --type all --output yaml --file <input file>
+  egctl experimental translate --from gateway-api --to xds --type all --output yaml --file <input file> -n <namespace>
 
   # Translate Gateway API Resources into Bootstrap xDS Resources.
-  egctl experimental translate --from gateway-api --to xds --type bootstrap --file <input file>
+  egctl experimental translate --from gateway-api --to xds --type bootstrap --file <input file> -n <namespace>
 
   # Translate Gateway API Resources into Cluster xDS Resources.
-  egctl experimental translate --from gateway-api --to xds --type cluster --file <input file>
+  egctl experimental translate --from gateway-api --to xds --type cluster --file <input file> -n <namespace>
 
   # Translate Gateway API Resources into Listener xDS Resources.
-  egctl experimental translate --from gateway-api --to xds --type listener --file <input file>
+  egctl experimental translate --from gateway-api --to xds --type listener --file <input file> -n <namespace>
 
   # Translate Gateway API Resources into Route xDS Resources.
-  egctl experimental translate --from gateway-api --to xds --type route --file <input file>
+  egctl experimental translate --from gateway-api --to xds --type route --file <input file> -n <namespace>
 
   # Translate Gateway API Resources into Cluster xDS Resources with short syntax.
-  egctl x translate --from gateway-api --to xds -t cluster -o yaml -f <input file>
+  egctl x translate --from gateway-api --to xds -t cluster -o yaml -f <input file> -n <namespace>
 
   # Translate Gateway API Resources into All xDS Resources with dummy resources added.
-  egctl x translate --from gateway-api --to xds -t cluster --add-missing-resources -f <input file>
+  egctl x translate --from gateway-api --to xds -t cluster --add-missing-resources -f <input file> -n <namespace>
 
   # Translate Gateway API Resources into All xDS Resources in YAML output,
   # also print the Gateway API Resources with updated status in the same output.
-  egctl experimental translate --from gateway-api --to gateway-api,xds --type all --output yaml --file <input file>
+  egctl experimental translate --from gateway-api --to gateway-api,xds --type all --output yaml --file <input file> -n <namespace>
 
   # Translate Gateway API Resources into IR in YAML output,
   egctl experimental translate --from gateway-api --to ir --output yaml --file <input file>
 	`,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return translate(cmd.OutOrStdout(), inFile, inType, outTypes, output, resourceType, addMissingResources, dnsDomain)
+			return translate(cmd.OutOrStdout(), inFile, inType, outTypes, output, resourceType, addMissingResources, namespace, dnsDomain)
 		},
 	}
 
@@ -117,6 +118,8 @@ func newTranslateCommand() *cobra.Command {
 	translateCommand.PersistentFlags().StringVarP(&resourceType, "type", "t", string(AllEnvoyConfigType), getValidResourceTypesStr())
 	translateCommand.PersistentFlags().BoolVarP(&addMissingResources, "add-missing-resources", "", false, "Provides dummy resources if missed")
 	translateCommand.PersistentFlags().StringVarP(&dnsDomain, "dns-domain", "", "cluster.local", "DNS domain used by k8s services, default is cluster.local")
+	translateCommand.PersistentFlags().StringVarP(&namespace, "namespace", "n", "envoy-gateway-system", "Namespace where envoy gateway is installed.")
+
 	return translateCommand
 }
 
@@ -220,7 +223,7 @@ func validate(inFile, inType string, outTypes []string, resourceType string) err
 	return nil
 }
 
-func translate(w io.Writer, inFile, inType string, outTypes []string, output, resourceType string, addMissingResources bool, dnsDomain string) error {
+func translate(w io.Writer, inFile, inType string, outTypes []string, output, resourceType string, addMissingResources bool, namespace, dnsDomain string) error {
 	if err := validate(inFile, inType, outTypes, resourceType); err != nil {
 		return err
 	}
@@ -247,7 +250,7 @@ func translate(w io.Writer, inFile, inType string, outTypes []string, output, re
 				}
 			}
 			if outType == xdsType {
-				res, err := translateGatewayAPIToXds(dnsDomain, resourceType, resources)
+				res, err := translateGatewayAPIToXds(namespace, dnsDomain, resourceType, resources)
 				if err != nil {
 					return err
 				}
@@ -333,7 +336,7 @@ func translateGatewayAPIToGatewayAPI(resources *gatewayapi.Resources) (gatewayap
 	return gRes.Resources, nil
 }
 
-func translateGatewayAPIToXds(dnsDomain string, resourceType string, resources *gatewayapi.Resources) (map[string]any, error) {
+func translateGatewayAPIToXds(namespace, dnsDomain string, resourceType string, resources *gatewayapi.Resources) (map[string]any, error) {
 	if resources.GatewayClass == nil {
 		return nil, fmt.Errorf("the GatewayClass resource is required")
 	}
@@ -363,7 +366,7 @@ func translateGatewayAPIToXds(dnsDomain string, resourceType string, resources *
 		xTranslator := &translator.Translator{
 			// Set some default settings for translation
 			GlobalRateLimit: &translator.GlobalRateLimitSettings{
-				ServiceURL: ratelimit.GetServiceURL("envoy-gateway", dnsDomain),
+				ServiceURL: ratelimit.GetServiceURL(namespace, dnsDomain),
 			},
 		}
 		if resources.EnvoyProxyForGatewayClass != nil {
diff --git a/internal/gatewayapi/envoypatchpolicy.go b/internal/gatewayapi/envoypatchpolicy.go
index 9ea9102ac..5d2480f5d 100644
--- a/internal/gatewayapi/envoypatchpolicy.go
+++ b/internal/gatewayapi/envoypatchpolicy.go
@@ -114,6 +114,7 @@ func (t *Translator) ProcessEnvoyPatchPolicies(envoyPatchPolicies []*egv1a1.Envo
 			irPatch.Name = patch.Name
 			irPatch.Operation.Op = string(patch.Operation.Op)
 			irPatch.Operation.Path = patch.Operation.Path
+			irPatch.Operation.JSONPath = patch.Operation.JSONPath
 			irPatch.Operation.From = patch.Operation.From
 			irPatch.Operation.Value = patch.Operation.Value
 
diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go
index b3d2ddb40..aecc2e811 100644
--- a/internal/gatewayapi/filters.go
+++ b/internal/gatewayapi/filters.go
@@ -445,7 +445,7 @@ func (t *Translator) processRequestHeaderModifierFilter(
 			newHeader := ir.AddHeader{
 				Name:   headerKey,
 				Append: true,
-				Value:  addHeader.Value,
+				Value:  strings.Split(addHeader.Value, ","),
 			}
 
 			filterContext.AddRequestHeaders = append(filterContext.AddRequestHeaders, newHeader)
@@ -500,7 +500,7 @@ func (t *Translator) processRequestHeaderModifierFilter(
 			newHeader := ir.AddHeader{
 				Name:   string(setHeader.Name),
 				Append: false,
-				Value:  setHeader.Value,
+				Value:  strings.Split(setHeader.Value, ","),
 			}
 
 			filterContext.AddRequestHeaders = append(filterContext.AddRequestHeaders, newHeader)
@@ -617,7 +617,7 @@ func (t *Translator) processResponseHeaderModifierFilter(
 			newHeader := ir.AddHeader{
 				Name:   headerKey,
 				Append: true,
-				Value:  addHeader.Value,
+				Value:  strings.Split(addHeader.Value, ","),
 			}
 
 			filterContext.AddResponseHeaders = append(filterContext.AddResponseHeaders, newHeader)
@@ -672,7 +672,7 @@ func (t *Translator) processResponseHeaderModifierFilter(
 			newHeader := ir.AddHeader{
 				Name:   string(setHeader.Name),
 				Append: false,
-				Value:  setHeader.Value,
+				Value:  strings.Split(setHeader.Value, ","),
 			}
 
 			filterContext.AddResponseHeaders = append(filterContext.AddResponseHeaders, newHeader)
diff --git a/internal/infrastructure/kubernetes/infra_resource.go b/internal/infrastructure/kubernetes/infra_resource.go
index fc471f8af..bf32ecfd1 100644
--- a/internal/infrastructure/kubernetes/infra_resource.go
+++ b/internal/infrastructure/kubernetes/infra_resource.go
@@ -7,13 +7,18 @@ package kubernetes
 
 import (
 	"context"
+	"fmt"
 	"time"
 
 	appsv1 "k8s.io/api/apps/v1"
 	autoscalingv2 "k8s.io/api/autoscaling/v2"
 	corev1 "k8s.io/api/core/v1"
 	policyv1 "k8s.io/api/policy/v1"
+	"k8s.io/apimachinery/pkg/api/equality"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/types"
 
 	"github.com/envoyproxy/gateway/internal/metrics"
 )
@@ -116,6 +121,45 @@ func (i *Infra) createOrUpdateDeployment(ctx context.Context, r ResourceRender)
 		}
 	}()
 
+	old := &appsv1.Deployment{}
+	err = i.Client.Get(ctx, types.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, old)
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			// It's the deployment creation.
+			return i.Client.ServerSideApply(ctx, deployment)
+		}
+		return err
+	}
+
+	if !equality.Semantic.DeepEqual(old.Spec.Selector, deployment.Spec.Selector) {
+		// Note: Deployment created by the old gateway controller may have a different selector generated based on a custom label feature,
+		// and it caused the issue that the gateway controller cannot update the deployment when users change the custom labels.
+		// Therefore, we changed the gateway to always use the same selector, independent of the custom labels -
+		// https://github.com/envoyproxy/gateway/issues/1818
+		//
+		// But, the change could break an existing deployment with custom labels initiated by the old gateway controller
+		// because the selector would be different.
+		//
+		// Here, as a workaround, we always copy the selector from the old deployment to the new deployment
+		// so that the update can be always applied successfully.
+		deployment.Spec.Selector = old.Spec.Selector
+
+		match, err := isSelectorMatch(deployment.Spec.Selector, deployment.Spec.Template.Labels)
+		if err != nil {
+			return err
+		}
+		if !match {
+			// If the selector now doesn't match with labels of the pod template, return an error.
+			// It could happen, for example, when users changed the custom label from {"foo": "bar"} to {"foo": "barv2"}
+			// because the pod's labels have {"foo": "barv2"} while the selector keeps {"foo": "bar"}.
+			// We cannot help this case, and just error it out.
+			// In this case, users should recreate the envoy proxy with the new custom label, instead of upgrading it.
+			// Once they recreate the envoy proxy, the envoy gateway of this version doesn't generate the selector based on the custom label,
+			// and the issue won't happen again, even if they have to the custom label again.
+			return fmt.Errorf("an illegal change in a custom label of EnvoyProxy is detected when updating %s/%s. The custom label config of deployment in EnvoyProxy, which is initiated with the envoy gateway of v1.1 or earlier, is immutable. Please recreate an envoy proxy with a new custom label if you need to change the custom label. This issue won't happen with the envoy proxy resource initialized by the envoygateway v1.2 or later", deployment.Namespace, deployment.Name)
+		}
+	}
+
 	return i.Client.ServerSideApply(ctx, deployment)
 }
 
@@ -153,9 +197,56 @@ func (i *Infra) createOrUpdateDaemonSet(ctx context.Context, r ResourceRender) (
 		}
 	}()
 
+	old := &appsv1.DaemonSet{}
+	err = i.Client.Get(ctx, types.NamespacedName{Name: daemonSet.Name, Namespace: daemonSet.Namespace}, old)
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			// It's the daemonset creation.
+			return i.Client.ServerSideApply(ctx, daemonSet)
+		}
+		return err
+	}
+
+	if !equality.Semantic.DeepEqual(old.Spec.Selector, daemonSet.Spec.Selector) {
+		// Note: Daemonset created by the old gateway controller may have a different selector generated based on a custom label feature,
+		// and it caused the issue that the gateway controller cannot update the daemonset when users change the custom labels.
+		// Therefore, we changed the gateway to always use the same selector, independent of the custom labels -
+		// https://github.com/envoyproxy/gateway/issues/1818
+		//
+		// But, the change could break an existing daemonset with custom labels initiated by the old gateway controller
+		// because the selector would be different.
+		//
+		// Here, as a workaround, we always copy the selector from the old daemonset to the new daemonset
+		// so that the update can be always applied successfully.
+		daemonSet.Spec.Selector = old.Spec.Selector
+		match, err := isSelectorMatch(daemonSet.Spec.Selector, daemonSet.Spec.Template.Labels)
+		if err != nil {
+			return err
+		}
+		if !match {
+			// If the selector now doesn't match with labels of the pod template, return an error.
+			// It could happen, for example, when users changed the custom label from {"foo": "bar"} to {"foo": "barv2"}
+			// because the pod's labels have {"foo": "barv2"} while the selector keeps {"foo": "bar"}.
+			// We cannot help this case, and just error it out.
+			// In this case, users should recreate the envoy proxy with the new custom label, instead of upgrading it.
+			// Once they recreate the envoy proxy, the envoy gateway of this version doesn't generate the selector based on the custom label,
+			// and the issue won't happen again, even if they have to the custom label again.
+			return fmt.Errorf("an illegal change in a custom label of EnvoyProxy is detected when updating %s/%s. The custom label config of daemonset in EnvoyProxy, which is initiated with the envoy gateway of v1.1 or earlier, is immutable. Please recreate an envoy proxy with a new custom label if you need to change the custom label. This issue won't happen with the envoy proxy resource initialized by the envoygateway v1.2 or later", daemonSet.Namespace, daemonSet.Name)
+		}
+	}
+
 	return i.Client.ServerSideApply(ctx, daemonSet)
 }
 
+func isSelectorMatch(labelselector *metav1.LabelSelector, l map[string]string) (bool, error) {
+	selector, err := metav1.LabelSelectorAsSelector(labelselector)
+	if err != nil {
+		return false, fmt.Errorf("invalid label selector is generated: %w", err)
+	}
+
+	return selector.Matches(labels.Set(l)), nil
+}
+
 func (i *Infra) createOrUpdatePodDisruptionBudget(ctx context.Context, r ResourceRender) (err error) {
 	var (
 		pdb       *policyv1.PodDisruptionBudget
diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider.go b/internal/infrastructure/kubernetes/proxy/resource_provider.go
index e48122b62..dd5907b7d 100644
--- a/internal/infrastructure/kubernetes/proxy/resource_provider.go
+++ b/internal/infrastructure/kubernetes/proxy/resource_provider.go
@@ -192,6 +192,19 @@ func (r *ResourceRender) ConfigMap() (*corev1.ConfigMap, error) {
 	}, nil
 }
 
+// stableSelector returns a stable selector based on the owning gateway labels.
+// "stable" here means the selector doesn't change when the infra is updated.
+func (r *ResourceRender) stableSelector() *metav1.LabelSelector {
+	labels := map[string]string{}
+	for k, v := range r.infra.GetProxyMetadata().Labels {
+		if k == gatewayapi.OwningGatewayNameLabel || k == gatewayapi.OwningGatewayNamespaceLabel || k == gatewayapi.OwningGatewayClassLabel {
+			labels[k] = v
+		}
+	}
+
+	return resource.GetSelector(envoyLabels(labels))
+}
+
 // Deployment returns the expected Deployment based on the provided infra.
 func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) {
 	proxyConfig := r.infra.GetProxyConfig()
@@ -222,8 +235,6 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) {
 	if err != nil {
 		return nil, err
 	}
-	podLabels := r.getPodLabels(deploymentConfig.Pod)
-	selector := resource.GetSelector(podLabels)
 
 	deployment := &appsv1.Deployment{
 		TypeMeta: metav1.TypeMeta{
@@ -238,10 +249,11 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) {
 		Spec: appsv1.DeploymentSpec{
 			Replicas: deploymentConfig.Replicas,
 			Strategy: *deploymentConfig.Strategy,
-			Selector: selector,
+			// Deployment's selector is immutable.
+			Selector: r.stableSelector(),
 			Template: corev1.PodTemplateSpec{
 				ObjectMeta: metav1.ObjectMeta{
-					Labels:      selector.MatchLabels,
+					Labels:      r.getPodLabels(deploymentConfig.Pod),
 					Annotations: podAnnotations,
 				},
 				Spec: corev1.PodSpec{
@@ -317,8 +329,6 @@ func (r *ResourceRender) DaemonSet() (*appsv1.DaemonSet, error) {
 	if err != nil {
 		return nil, err
 	}
-	podLabels := r.getPodLabels(daemonSetConfig.Pod)
-	selector := resource.GetSelector(podLabels)
 
 	daemonSet := &appsv1.DaemonSet{
 		TypeMeta: metav1.TypeMeta{
@@ -331,11 +341,12 @@ func (r *ResourceRender) DaemonSet() (*appsv1.DaemonSet, error) {
 			Annotations: dsAnnotations,
 		},
 		Spec: appsv1.DaemonSetSpec{
-			Selector:       selector,
+			// Daemonset's selector is immutable.
+			Selector:       r.stableSelector(),
 			UpdateStrategy: *daemonSetConfig.Strategy,
 			Template: corev1.PodTemplateSpec{
 				ObjectMeta: metav1.ObjectMeta{
-					Labels:      selector.MatchLabels,
+					Labels:      r.getPodLabels(daemonSetConfig.Pod),
 					Annotations: podAnnotations,
 				},
 				Spec: r.getPodSpec(containers, nil, daemonSetConfig.Pod, proxyConfig),
@@ -369,11 +380,6 @@ func (r *ResourceRender) PodDisruptionBudget() (*policyv1.PodDisruptionBudget, e
 		return nil, nil
 	}
 
-	labels, err := r.getLabels()
-	if err != nil {
-		return nil, err
-	}
-
 	return &policyv1.PodDisruptionBudget{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      r.Name(),
@@ -385,9 +391,7 @@ func (r *ResourceRender) PodDisruptionBudget() (*policyv1.PodDisruptionBudget, e
 		},
 		Spec: policyv1.PodDisruptionBudgetSpec{
 			MinAvailable: &intstr.IntOrString{IntVal: ptr.Deref(podDisruptionBudget.MinAvailable, 0)},
-			Selector: &metav1.LabelSelector{
-				MatchLabels: labels,
-			},
+			Selector:     r.stableSelector(),
 		},
 	}, nil
 }
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index f2807da48..56c1bef09 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -25,6 +25,10 @@ import (
 	egv1a1validation "github.com/envoyproxy/gateway/api/v1alpha1/validation"
 )
 
+const (
+	EmptyPath = ""
+)
+
 var (
 	ErrListenerNameEmpty                       = errors.New("field Name must be specified")
 	ErrListenerAddressInvalid                  = errors.New("field Address must be a valid IP address")
@@ -1226,9 +1230,9 @@ func NewDestEndpoint(host string, port uint32) *DestinationEndpoint {
 // AddHeader configures a header to be added to a request or response.
 // +k8s:deepcopy-gen=true
 type AddHeader struct {
-	Name   string `json:"name" yaml:"name"`
-	Value  string `json:"value" yaml:"value"`
-	Append bool   `json:"append" yaml:"append"`
+	Name   string   `json:"name" yaml:"name"`
+	Value  []string `json:"value" yaml:"value"`
+	Append bool     `json:"append" yaml:"append"`
 }
 
 // Validate the fields within the AddHeader structure
@@ -1751,7 +1755,12 @@ type JSONPatchOperation struct {
 	Op string `json:"op" yaml:"op"`
 	// Path is the location of the target document/field where the operation will be performed
 	// Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details.
-	Path string `json:"path" yaml:"path"`
+	// +optional
+	Path *string `json:"path,omitempty"  yaml:"path,omitempty"`
+	// JSONPath specifies the locations of the target document/field where the operation will be performed
+	// Refer to https://datatracker.ietf.org/doc/rfc9535/ for more details.
+	// +optional
+	JSONPath *string `json:"jsonPath,omitempty"  yaml:"jsonPath,omitempty"`
 	// From is the source location of the value to be copied or moved. Only valid
 	// for move or copy operations
 	// Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details.
@@ -1761,6 +1770,14 @@ type JSONPatchOperation struct {
 	Value *apiextensionsv1.JSON `json:"value,omitempty" yaml:"value,omitempty"`
 }
 
+func (o *JSONPatchOperation) IsPathNilOrEmpty() bool {
+	return o.Path == nil || *o.Path == EmptyPath
+}
+
+func (o *JSONPatchOperation) IsJSONPathNilOrEmpty() bool {
+	return o.JSONPath == nil || *o.JSONPath == EmptyPath
+}
+
 // Tracing defines the configuration for tracing a Envoy xDS Resource
 // +k8s:deepcopy-gen=true
 type Tracing struct {
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index 5e3398a06..5f8f879e6 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -196,6 +196,11 @@ func (in *ActiveHealthCheck) DeepCopy() *ActiveHealthCheck {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *AddHeader) DeepCopyInto(out *AddHeader) {
 	*out = *in
+	if in.Value != nil {
+		in, out := &in.Value, &out.Value
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddHeader.
@@ -638,7 +643,9 @@ func (in *DestinationFilters) DeepCopyInto(out *DestinationFilters) {
 	if in.AddRequestHeaders != nil {
 		in, out := &in.AddRequestHeaders, &out.AddRequestHeaders
 		*out = make([]AddHeader, len(*in))
-		copy(*out, *in)
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
 	}
 	if in.RemoveRequestHeaders != nil {
 		in, out := &in.RemoveRequestHeaders, &out.RemoveRequestHeaders
@@ -648,7 +655,9 @@ func (in *DestinationFilters) DeepCopyInto(out *DestinationFilters) {
 	if in.AddResponseHeaders != nil {
 		in, out := &in.AddResponseHeaders, &out.AddResponseHeaders
 		*out = make([]AddHeader, len(*in))
-		copy(*out, *in)
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
 	}
 	if in.RemoveResponseHeaders != nil {
 		in, out := &in.RemoveResponseHeaders, &out.RemoveResponseHeaders
@@ -1299,7 +1308,9 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {
 	if in.AddRequestHeaders != nil {
 		in, out := &in.AddRequestHeaders, &out.AddRequestHeaders
 		*out = make([]AddHeader, len(*in))
-		copy(*out, *in)
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
 	}
 	if in.RemoveRequestHeaders != nil {
 		in, out := &in.RemoveRequestHeaders, &out.RemoveRequestHeaders
@@ -1309,7 +1320,9 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {
 	if in.AddResponseHeaders != nil {
 		in, out := &in.AddResponseHeaders, &out.AddResponseHeaders
 		*out = make([]AddHeader, len(*in))
-		copy(*out, *in)
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
 	}
 	if in.RemoveResponseHeaders != nil {
 		in, out := &in.RemoveResponseHeaders, &out.RemoveResponseHeaders
@@ -1640,6 +1653,16 @@ func (in *JSONPatchConfig) DeepCopy() *JSONPatchConfig {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *JSONPatchOperation) DeepCopyInto(out *JSONPatchOperation) {
 	*out = *in
+	if in.Path != nil {
+		in, out := &in.Path, &out.Path
+		*out = new(string)
+		**out = **in
+	}
+	if in.JSONPath != nil {
+		in, out := &in.JSONPath, &out.JSONPath
+		*out = new(string)
+		**out = **in
+	}
 	if in.From != nil {
 		in, out := &in.From, &out.From
 		*out = new(string)
diff --git a/internal/metrics/otel_metric_gauge.go b/internal/metrics/otel_metric_gauge.go
index 49e02395b..7fe0ac3dc 100644
--- a/internal/metrics/otel_metric_gauge.go
+++ b/internal/metrics/otel_metric_gauge.go
@@ -29,15 +29,19 @@ type GaugeValues struct {
 
 func (f *Gauge) Record(value float64) {
 	f.mutex.Lock()
+	defer f.mutex.Unlock()
+
 	if f.current == nil {
 		f.current = &GaugeValues{}
 		f.stores[attribute.NewSet()] = f.current
 	}
 	f.current.val = value
-	f.mutex.Unlock()
 }
 
 func (f *Gauge) With(labelValues ...LabelValue) *Gauge {
+	f.mutex.Lock()
+	defer f.mutex.Unlock()
+
 	attrs, set := mergeLabelValues(f.attrs, labelValues)
 	m := &Gauge{
 		g:      f.g,
diff --git a/internal/xds/translator/jsonpatch.go b/internal/xds/translator/jsonpatch.go
index e7808abe0..83db9103b 100644
--- a/internal/xds/translator/jsonpatch.go
+++ b/internal/xds/translator/jsonpatch.go
@@ -84,9 +84,9 @@ func processJSONPatches(tCtx *types.ResourceVersionTable, envoyPatchPolicies []*
 				}
 			}
 
-			// If Path is "" and op is "add", unmarshal and add the patch as a complete
+			// If Path and JSONPath is "" and op is "add", unmarshal and add the patch as a complete
 			// resource
-			if p.Operation.Op == AddOperation && p.Operation.Path == EmptyPath {
+			if p.Operation.Op == AddOperation && p.Operation.IsPathNilOrEmpty() && p.Operation.IsJSONPathNilOrEmpty() {
 				// Convert patch to JSON
 				// The patch library expects an array so convert it into one
 				y, err := yaml.Marshal(p.Operation.Value)
@@ -240,125 +240,150 @@ func processJSONPatches(tCtx *types.ResourceVersionTable, envoyPatchPolicies []*
 				}
 			}
 
-			// Convert patch to JSON
-			// The patch library expects an array so convert it into one
-			y, err := yaml.Marshal([]ir.JSONPatchOperation{p.Operation})
-			if err != nil {
-				tErr := fmt.Errorf("unable to marshal patch %+v, err: %s", p.Operation, err.Error())
-				tErrs = errors.Join(tErrs, tErr)
-				continue
-			}
-			jsonBytes, err := yaml.YAMLToJSON(y)
-			if err != nil {
-				tErr := fmt.Errorf("unable to convert patch to json %s, err: %s", string(y), err.Error())
-				tErrs = errors.Join(tErrs, tErr)
-				continue
-			}
-			patchObj, err := jsonpatchv5.DecodePatch(jsonBytes)
-			if err != nil {
-				tErr := fmt.Errorf("unable to decode patch %s, err: %s", string(jsonBytes), err.Error())
-				tErrs = errors.Join(tErrs, tErr)
-				continue
-			}
-
-			// Apply patch
-			opts := jsonpatchv5.NewApplyOptions()
-			opts.EnsurePathExistsOnAdd = true
-			modifiedJSON, err := patchObj.ApplyWithOptions(resourceJSON, opts)
-			if err != nil {
-				tErr := fmt.Errorf("unable to apply patch:\n%s on resource:\n%s, err: %s", string(jsonBytes), string(resourceJSON), err.Error())
-				tErrs = errors.Join(tErrs, tErr)
-				continue
-			}
-
-			// Unmarshal back to typed resource
-			// Use a temp staging variable that can be marshalled
-			// into and validated before saving it into the xds output resource
-			switch p.Type {
-			case resourcev3.ListenerType:
-				temp := &listenerv3.Listener{}
-				if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
-					tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
-					tErrs = errors.Join(tErrs, tErr)
-					continue
+			var jsonPointers []string
+			if p.Operation.JSONPath != nil {
+				path := ""
+				if p.Operation.Path != nil {
+					path = *p.Operation.Path
 				}
-				if err = temp.Validate(); err != nil {
-					tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
-					tErrs = errors.Join(tErrs, tErr)
-					continue
-				}
-				if err = deepCopyPtr(temp, listener); err != nil {
-					tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
-					tErrs = errors.Join(tErrs, tErr)
-					continue
-				}
-			case resourcev3.RouteType:
-				temp := &routev3.RouteConfiguration{}
-				if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
-					tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
-					tErrs = errors.Join(tErrs, tErr)
-					continue
-				}
-				if err = temp.Validate(); err != nil {
-					tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
-					tErrs = errors.Join(tErrs, tErr)
-					continue
-				}
-				if err = deepCopyPtr(temp, routeConfig); err != nil {
-					tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
-					tErrs = errors.Join(tErrs, tErr)
-					continue
-				}
-			case resourcev3.ClusterType:
-				temp := &clusterv3.Cluster{}
-				if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
-					tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
-					tErrs = errors.Join(tErrs, tErr)
-					continue
-				}
-				if err = temp.Validate(); err != nil {
-					tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
-					tErrs = errors.Join(tErrs, tErr)
-					continue
-				}
-				if err = deepCopyPtr(temp, cluster); err != nil {
-					tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
+				jsonPointers, err = ConvertPathToPointers(resourceJSON, *p.Operation.JSONPath, path)
+				if err != nil {
+					tErr := fmt.Errorf("unable to convert jsonPath: '%s' into jsonPointers, err: %s", *p.Operation.JSONPath, err.Error())
 					tErrs = errors.Join(tErrs, tErr)
 					continue
 				}
-			case resourcev3.EndpointType:
-				temp := &endpointv3.ClusterLoadAssignment{}
-				if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
-					tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
-					tErrs = errors.Join(tErrs, tErr)
-					continue
+			} else {
+				jsonPointers = []string{*p.Operation.Path}
+			}
+
+			for _, path := range jsonPointers {
+				op := ir.JSONPatchOperation{
+					Path:  &path,
+					Op:    p.Operation.Op,
+					Value: p.Operation.Value,
+					From:  p.Operation.From,
 				}
-				if err = temp.Validate(); err != nil {
-					tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
+
+				// Convert patch to JSON
+				// The patch library expects an array so convert it into one
+				y, err := yaml.Marshal([]ir.JSONPatchOperation{op})
+				if err != nil {
+					tErr := fmt.Errorf("unable to marshal patch %+v, err: %s", op, err.Error())
 					tErrs = errors.Join(tErrs, tErr)
 					continue
 				}
-				if err = deepCopyPtr(temp, endpoint); err != nil {
-					tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
+				jsonBytes, err := yaml.YAMLToJSON(y)
+				if err != nil {
+					tErr := fmt.Errorf("unable to convert patch to json %s, err: %s", string(y), err.Error())
 					tErrs = errors.Join(tErrs, tErr)
 					continue
 				}
-			case resourcev3.SecretType:
-				temp := &tlsv3.Secret{}
-				if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
-					tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
+				patchObj, err := jsonpatchv5.DecodePatch(jsonBytes)
+				if err != nil {
+					tErr := fmt.Errorf("unable to decode patch %s, err: %s", string(jsonBytes), err.Error())
 					tErrs = errors.Join(tErrs, tErr)
 					continue
 				}
-				if err = temp.Validate(); err != nil {
-					tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
+
+				// Apply patch
+				opts := jsonpatchv5.NewApplyOptions()
+				opts.EnsurePathExistsOnAdd = true
+				modifiedJSON, err := patchObj.ApplyWithOptions(resourceJSON, opts)
+				if err != nil {
+					tErr := fmt.Errorf("unable to apply patch:\n%s on resource:\n%s, err: %s", string(jsonBytes), string(resourceJSON), err.Error())
 					tErrs = errors.Join(tErrs, tErr)
 					continue
 				}
-				if err = deepCopyPtr(temp, secret); err != nil {
-					tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
-					tErrs = errors.Join(tErrs, tErr)
-					continue
+
+				// Unmarshal back to typed resource
+				// Use a temp staging variable that can be marshalled
+				// into and validated before saving it into the xds output resource
+				switch p.Type {
+				case resourcev3.ListenerType:
+					temp := &listenerv3.Listener{}
+					if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
+						tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = temp.Validate(); err != nil {
+						tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = deepCopyPtr(temp, listener); err != nil {
+						tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+				case resourcev3.RouteType:
+					temp := &routev3.RouteConfiguration{}
+					if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
+						tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = temp.Validate(); err != nil {
+						tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = deepCopyPtr(temp, routeConfig); err != nil {
+						tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+				case resourcev3.ClusterType:
+					temp := &clusterv3.Cluster{}
+					if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
+						tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = temp.Validate(); err != nil {
+						tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = deepCopyPtr(temp, cluster); err != nil {
+						tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+				case resourcev3.EndpointType:
+					temp := &endpointv3.ClusterLoadAssignment{}
+					if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
+						tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = temp.Validate(); err != nil {
+						tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = deepCopyPtr(temp, endpoint); err != nil {
+						tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+				case resourcev3.SecretType:
+					temp := &tlsv3.Secret{}
+					if err = protojson.Unmarshal(modifiedJSON, temp); err != nil {
+						tErr := fmt.Errorf(unmarshalErrorMessage(err, string(modifiedJSON)))
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = temp.Validate(); err != nil {
+						tErr := fmt.Errorf("validation failed for xds resource %s, err:%s", string(modifiedJSON), err.Error())
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
+					if err = deepCopyPtr(temp, secret); err != nil {
+						tErr := fmt.Errorf("unable to copy xds resource %s, err: %w", string(modifiedJSON), err)
+						tErrs = errors.Join(tErrs, tErr)
+						continue
+					}
 				}
 			}
 		}
diff --git a/internal/xds/translator/jsonpathtopointer.go b/internal/xds/translator/jsonpathtopointer.go
new file mode 100644
index 000000000..89d7fdf1c
--- /dev/null
+++ b/internal/xds/translator/jsonpathtopointer.go
@@ -0,0 +1,120 @@
+// 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 (
+	"reflect"
+	"strings"
+
+	"github.com/ohler55/ojg/jp"
+	"github.com/ohler55/ojg/oj"
+	"github.com/pkg/errors"
+)
+
+func ConvertPathToPointers(jsonDoc []byte, jsonPath string, path string) ([]string, error) {
+	var jsonPointers []string
+
+	jObj, err := oj.Parse(jsonDoc)
+	if err != nil {
+		return nil, errors.Wrap(err, "Error during parsing json")
+	}
+
+	jPath, err := jp.ParseString(jsonPath)
+	if err != nil {
+		return nil, errors.Wrap(err, "Error during parsing jpath")
+	}
+
+	if len(jPath) == 1 {
+		_, isRoot := jPath[0].(jp.Root)
+		if isRoot {
+			return nil, errors.New("Using only Root ('$') in json path expression is not allowed!")
+		}
+	}
+
+	locations := jPath.Locate(jObj, 0)
+	for _, l := range locations {
+		jsonPointer, err := expToPointer(l)
+		if err != nil {
+			return nil, errors.Wrap(err, "Error during converting path to pointer")
+		}
+		jsonPointers = append(jsonPointers, concat(jsonPointer, path))
+	}
+	return jsonPointers, nil
+}
+
+func concat(jsonPointer string, path string) string {
+	if path == "" {
+		return jsonPointer
+	}
+	const separator string = "/"
+	parts := []string{
+		strings.TrimSuffix(jsonPointer, separator),
+		strings.TrimPrefix(path, separator),
+	}
+	return strings.Join(parts, separator)
+}
+
+func expToPointer(e jp.Expr) (string, error) {
+	var buf []byte
+	for _, f := range e {
+		v, err := fragToPointer(f)
+		if err != nil {
+			return "", err
+		}
+		if v != nil {
+			buf = append(buf, '/')
+		}
+
+		buf = append(buf, v...)
+	}
+
+	return string(buf), nil
+}
+
+func fragToPointer(f jp.Frag) ([]byte, error) {
+	switch v := f.(type) {
+	case jp.Root:
+		return rootToPointer()
+	case jp.Nth:
+		return nthToPointer(v)
+	case jp.Child:
+		return toPointer(v)
+	default:
+		return nil, errors.New("There is no conversion implemented for " + reflect.TypeOf(v).Name())
+	}
+}
+
+func rootToPointer() ([]byte, error) {
+	return nil, nil
+}
+
+func nthToPointer(f jp.Nth) ([]byte, error) {
+	var buf []byte
+	i := int(f)
+	if i < 0 {
+		buf = append(buf, '-')
+		i = -i
+	}
+	num := [20]byte{}
+	cnt := 0
+	for ; i != 0; cnt++ {
+		num[cnt] = byte(i%10) + '0'
+		i /= 10
+	}
+	if 0 < cnt {
+		cnt--
+		for ; 0 <= cnt; cnt-- {
+			buf = append(buf, num[cnt])
+		}
+	} else {
+		buf = append(buf, '0')
+	}
+	return buf, nil
+}
+
+func toPointer(f jp.Frag) ([]byte, error) {
+	return f.Append(nil, false, true), nil
+}
diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go
index 8a56e9e42..6a9e72f49 100644
--- a/internal/xds/translator/route.go
+++ b/internal/xds/translator/route.go
@@ -438,9 +438,9 @@ func buildXdsRequestMirrorPolicies(mirrorDestinations []*ir.RouteDestination) []
 }
 
 func buildXdsAddedHeaders(headersToAdd []ir.AddHeader) []*corev3.HeaderValueOption {
-	headerValueOptions := make([]*corev3.HeaderValueOption, len(headersToAdd))
+	headerValueOptions := []*corev3.HeaderValueOption{}
 
-	for i, header := range headersToAdd {
+	for _, header := range headersToAdd {
 		var appendAction corev3.HeaderValueOption_HeaderAppendAction
 
 		if header.Append {
@@ -448,18 +448,26 @@ func buildXdsAddedHeaders(headersToAdd []ir.AddHeader) []*corev3.HeaderValueOpti
 		} else {
 			appendAction = corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD
 		}
-
-		headerValueOptions[i] = &corev3.HeaderValueOption{
-			Header: &corev3.HeaderValue{
-				Key:   header.Name,
-				Value: header.Value,
-			},
-			AppendAction: appendAction,
-		}
-
 		// Allow empty headers to be set, but don't add the config to do so unless necessary
-		if header.Value == "" {
-			headerValueOptions[i].KeepEmptyValue = true
+		if len(header.Value) == 0 {
+			headerValueOptions = append(headerValueOptions, &corev3.HeaderValueOption{
+				Header: &corev3.HeaderValue{
+					Key: header.Name,
+				},
+				AppendAction:   appendAction,
+				KeepEmptyValue: true,
+			})
+		} else {
+			for _, val := range header.Value {
+				headerValueOptions = append(headerValueOptions, &corev3.HeaderValueOption{
+					Header: &corev3.HeaderValue{
+						Key:   header.Name,
+						Value: val,
+					},
+					AppendAction:   appendAction,
+					KeepEmptyValue: val == "",
+				})
+			}
 		}
 	}
 
diff --git a/site/content/en/docs/concepts/concepts_overview.md b/site/content/en/docs/concepts/concepts_overview.md
index 31838b520..9af9a3fff 100644
--- a/site/content/en/docs/concepts/concepts_overview.md
+++ b/site/content/en/docs/concepts/concepts_overview.md
@@ -10,13 +10,12 @@ There are several resources that play a part in enabling you to meet your Kubern
 
 There are several resources that play a part in enabling you to meet your Kubernetes ingress traffic handling needs. This page provides a brief overview of the resources you’ll be working with.
 
-# Overview
-
-## Kubernetes Gateway API Resources
+### Kubernetes Gateway API Resources
 - **GatewayClass:** Defines a class of Gateways with common configuration.
 - **Gateway:** Specifies how traffic can enter the cluster.
 - **Routes:** **HTTPRoute, GRPCRoute, TLSRoute, TCPRoute, UDPRoute:** Define routing rules for different types of traffic.
-## Envoy Gateway (EG) API Resources
+
+### Envoy Gateway (EG) API Resources
 - **EnvoyProxy:** Represents the deployment and configuration of the Envoy proxy within a Kubernetes cluster, managing its lifecycle and settings.
 - **EnvoyPatchPolicy, ClientTrafficPolicy, SecurityPolicy, BackendTrafficPolicy, EnvoyExtensionPolicy, BackendTLSPolicy:** Additional policies and configurations specific to Envoy Gateway.
 - **Backend:** A resource that makes routing to cluster-external backends easier and makes access to external processes via Unix Domain Sockets possible.
diff --git a/site/content/en/docs/tasks/security/backend-tls.md b/site/content/en/docs/tasks/security/backend-tls.md
index 53e9ccbd4..3aadbc347 100644
--- a/site/content/en/docs/tasks/security/backend-tls.md
+++ b/site/content/en/docs/tasks/security/backend-tls.md
@@ -25,11 +25,30 @@ Create a root certificate and private key to sign certificates:
 openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout ca.key -out ca.crt
 ```
 
-Create a certificate and a private key for `www.example.com`:
+Create a certificate and a private key for `www.example.com`.
+
+First, create an openssl configuration file:
+
+```shell
+cat > openssl.conf  <<EOF
+[req]
+req_extensions = v3_req
+prompt = no
+
+[v3_req]
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage = serverAuth
+subjectAltName = @alt_names
+[alt_names]
+DNS.1 = www.example.com
+EOF
+```
+
+Then create a certificate using this openssl configuration file:
 
 ```shell
-openssl req -out www.example.com.csr -newkey rsa:2048 -nodes -keyout www.example.com.key -subj "/CN=www.example.com/O=example organization" -addext "subjectAltName = DNS:www.example.com"
-openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -set_serial 0 -in www.example.com.csr -out www.example.com.crt
+openssl req -out www.example.com.csr -newkey rsa:2048 -nodes -keyout www.example.com.key -subj "/CN=www.example.com/O=example organization"
+openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -set_serial 0 -in www.example.com.csr -out www.example.com.crt -extfile openssl.conf -extensions v3_req
 ```
 
 Note that the certificate must contain a DNS SAN for the relevant domain.
diff --git a/site/content/en/v1.0/tasks/security/backend-tls.md b/site/content/en/v1.0/tasks/security/backend-tls.md
index 1f84ea4db..5ba360e1d 100644
--- a/site/content/en/v1.0/tasks/security/backend-tls.md
+++ b/site/content/en/v1.0/tasks/security/backend-tls.md
@@ -25,11 +25,30 @@ Create a root certificate and private key to sign certificates:
 openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout ca.key -out ca.crt
 ```
 
-Create a certificate and a private key for `www.example.com`:
+Create a certificate and a private key for `www.example.com`.
+
+First, create an openssl configuration file:
+
+```shell
+cat > openssl.conf  <<EOF
+[req]
+req_extensions = v3_req
+prompt = no
+
+[v3_req]
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage = serverAuth
+subjectAltName = @alt_names
+[alt_names]
+DNS.1 = www.example.com
+EOF
+```
+
+Then create a certificate using this openssl configuration file:
 
 ```shell
-openssl req -out www.example.com.csr -newkey rsa:2048 -nodes -keyout www.example.com.key -subj "/CN=www.example.com/O=example organization" -addext "subjectAltName = DNS:www.example.com"
-openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -set_serial 0 -in www.example.com.csr -out www.example.com.crt
+openssl req -out www.example.com.csr -newkey rsa:2048 -nodes -keyout www.example.com.key -subj "/CN=www.example.com/O=example organization"
+openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -set_serial 0 -in www.example.com.csr -out www.example.com.crt -extfile openssl.conf -extensions v3_req
 ```
 
 Store the cert/key in a Secret:
diff --git a/site/content/en/v1.1/concepts/concepts_overview.md b/site/content/en/v1.1/concepts/concepts_overview.md
index 31838b520..9af9a3fff 100644
--- a/site/content/en/v1.1/concepts/concepts_overview.md
+++ b/site/content/en/v1.1/concepts/concepts_overview.md
@@ -10,13 +10,12 @@ There are several resources that play a part in enabling you to meet your Kubern
 
 There are several resources that play a part in enabling you to meet your Kubernetes ingress traffic handling needs. This page provides a brief overview of the resources you’ll be working with.
 
-# Overview
-
-## Kubernetes Gateway API Resources
+### Kubernetes Gateway API Resources
 - **GatewayClass:** Defines a class of Gateways with common configuration.
 - **Gateway:** Specifies how traffic can enter the cluster.
 - **Routes:** **HTTPRoute, GRPCRoute, TLSRoute, TCPRoute, UDPRoute:** Define routing rules for different types of traffic.
-## Envoy Gateway (EG) API Resources
+
+### Envoy Gateway (EG) API Resources
 - **EnvoyProxy:** Represents the deployment and configuration of the Envoy proxy within a Kubernetes cluster, managing its lifecycle and settings.
 - **EnvoyPatchPolicy, ClientTrafficPolicy, SecurityPolicy, BackendTrafficPolicy, EnvoyExtensionPolicy, BackendTLSPolicy:** Additional policies and configurations specific to Envoy Gateway.
 - **Backend:** A resource that makes routing to cluster-external backends easier and makes access to external processes via Unix Domain Sockets possible.
diff --git a/site/content/en/v1.1/tasks/security/backend-tls.md b/site/content/en/v1.1/tasks/security/backend-tls.md
index 53e9ccbd4..3aadbc347 100644
--- a/site/content/en/v1.1/tasks/security/backend-tls.md
+++ b/site/content/en/v1.1/tasks/security/backend-tls.md
@@ -25,11 +25,30 @@ Create a root certificate and private key to sign certificates:
 openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout ca.key -out ca.crt
 ```
 
-Create a certificate and a private key for `www.example.com`:
+Create a certificate and a private key for `www.example.com`.
+
+First, create an openssl configuration file:
+
+```shell
+cat > openssl.conf  <<EOF
+[req]
+req_extensions = v3_req
+prompt = no
+
+[v3_req]
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage = serverAuth
+subjectAltName = @alt_names
+[alt_names]
+DNS.1 = www.example.com
+EOF
+```
+
+Then create a certificate using this openssl configuration file:
 
 ```shell
-openssl req -out www.example.com.csr -newkey rsa:2048 -nodes -keyout www.example.com.key -subj "/CN=www.example.com/O=example organization" -addext "subjectAltName = DNS:www.example.com"
-openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -set_serial 0 -in www.example.com.csr -out www.example.com.crt
+openssl req -out www.example.com.csr -newkey rsa:2048 -nodes -keyout www.example.com.key -subj "/CN=www.example.com/O=example organization"
+openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -set_serial 0 -in www.example.com.csr -out www.example.com.crt -extfile openssl.conf -extensions v3_req
 ```
 
 Note that the certificate must contain a DNS SAN for the relevant domain.
diff --git a/tools/src/buf/go.mod b/tools/src/buf/go.mod
index de4034e91..ec8edf567 100644
--- a/tools/src/buf/go.mod
+++ b/tools/src/buf/go.mod
@@ -2,7 +2,7 @@ module local
 
 go 1.22.5
 
-require github.com/bufbuild/buf v1.35.1
+require github.com/bufbuild/buf v1.36.0
 
 require (
 	buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240508200655-46a4cf4ba109.2 // indirect
diff --git a/tools/src/buf/go.sum b/tools/src/buf/go.sum
index c6d949c31..3bdf925f2 100644
--- a/tools/src/buf/go.sum
+++ b/tools/src/buf/go.sum
@@ -14,8 +14,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
 github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
-github.com/bufbuild/buf v1.35.1 h1:aiCi/YFOg7eXKZeveWb2ZhnmLFwUMM/FnDCM0roFp+M=
-github.com/bufbuild/buf v1.35.1/go.mod h1:SM7b5QW3FkQPNkkqIa/9UWzLOoe51la+GGZpEgH9b68=
+github.com/bufbuild/buf v1.36.0 h1:sC/MRgAhwvcbLbUXlTY+zgLUT4PzHm19BnnEsgu/rgU=
+github.com/bufbuild/buf v1.36.0/go.mod h1:SM7b5QW3FkQPNkkqIa/9UWzLOoe51la+GGZpEgH9b68=
 github.com/bufbuild/protocompile v0.14.0 h1:z3DW4IvXE5G/uTOnSQn+qwQQxvhckkTWLS/0No/o7KU=
 github.com/bufbuild/protocompile v0.14.0/go.mod h1:N6J1NYzkspJo3ZwyL4Xjvli86XOj1xq4qAasUFxGups=
 github.com/bufbuild/protoplugin v0.0.0-20240323223605-e2735f6c31ee h1:E6ET8YUcYJ1lAe6ctR3as7yqzW2BNItDFnaB5zQq/8M=


Test patch:
diff --git a/internal/gatewayapi/testdata/grpcroute-with-request-header-modifier.in.yaml b/internal/gatewayapi/testdata/grpcroute-with-request-header-modifier.in.yaml
index 2c48dad58..29fcb5a75 100644
--- a/internal/gatewayapi/testdata/grpcroute-with-request-header-modifier.in.yaml
+++ b/internal/gatewayapi/testdata/grpcroute-with-request-header-modifier.in.yaml
@@ -26,6 +26,11 @@ grpcRoutes:
       sectionName: http
     rules:
     - filters:
+      - type: "RequestHeaderModifier"
+        requestHeaderModifier:
+          add:
+          - name: "my-header-multi-value"
+            value: "foo,bar"
       - type: "RequestHeaderModifier"
         requestHeaderModifier:
           add:
diff --git a/internal/gatewayapi/testdata/grpcroute-with-request-header-modifier.out.yaml b/internal/gatewayapi/testdata/grpcroute-with-request-header-modifier.out.yaml
index f36c9c969..110d404c4 100644
--- a/internal/gatewayapi/testdata/grpcroute-with-request-header-modifier.out.yaml
+++ b/internal/gatewayapi/testdata/grpcroute-with-request-header-modifier.out.yaml
@@ -56,6 +56,11 @@ grpcRoutes:
       - name: service-1
         port: 8080
       filters:
+      - requestHeaderModifier:
+          add:
+          - name: my-header-multi-value
+            value: foo,bar
+        type: RequestHeaderModifier
       - requestHeaderModifier:
           add:
           - name: my-header
@@ -117,9 +122,15 @@ xdsIR:
       port: 10080
       routes:
       - addRequestHeaders:
+        - append: true
+          name: my-header-multi-value
+          value:
+          - foo
+          - bar
         - append: true
           name: my-header
-          value: foo
+          value:
+          - foo
         destination:
           name: grpcroute/default/grpcroute-1/rule/0
           settings:
diff --git a/internal/gatewayapi/testdata/httproute-with-backendref-add-multiple-filters.out.yaml b/internal/gatewayapi/testdata/httproute-with-backendref-add-multiple-filters.out.yaml
index 78655fc84..122d09efd 100644
--- a/internal/gatewayapi/testdata/httproute-with-backendref-add-multiple-filters.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-backendref-add-multiple-filters.out.yaml
@@ -147,7 +147,8 @@ xdsIR:
               addRequestHeaders:
               - append: false
                 name: add-header-3
-                value: some-value
+                value:
+                - some-value
             protocol: HTTP
             weight: 1
         hostname: '*'
@@ -172,10 +173,12 @@ xdsIR:
               addRequestHeaders:
               - append: true
                 name: add-header-1
-                value: some-value
+                value:
+                - some-value
               - append: true
                 name: add-header-2
-                value: some-value
+                value:
+                - some-value
             protocol: HTTP
             weight: 8
           - addressType: IP
diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-duplicate-add-multiple-filters.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-duplicate-add-multiple-filters.out.yaml
index a86e71b45..605aa384f 100644
--- a/internal/gatewayapi/testdata/httproute-with-header-filter-duplicate-add-multiple-filters.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-header-filter-duplicate-add-multiple-filters.out.yaml
@@ -134,13 +134,16 @@ xdsIR:
       - addRequestHeaders:
         - append: true
           name: add-header-1
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: add-header-2
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: add-header-3
-          value: some-value
+          value:
+          - some-value
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-duplicate-adds.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-duplicate-adds.out.yaml
index 39cc44429..f122fc17d 100644
--- a/internal/gatewayapi/testdata/httproute-with-header-filter-duplicate-adds.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-header-filter-duplicate-adds.out.yaml
@@ -144,19 +144,24 @@ xdsIR:
       - addRequestHeaders:
         - append: true
           name: Set-Header-1
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: set-header-2
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: set-header-3
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: set-header-5
-          value: some-value
+          value:
+          - some-value
         - append: false
           name: set-header-4
-          value: some-value
+          value:
+          - some-value
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml
index b3814e2d4..67c14e133 100644
--- a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml
@@ -128,10 +128,12 @@ xdsIR:
       - addRequestHeaders:
         - append: true
           name: example-header-2
-          value: ""
+          value:
+          - ""
         - append: false
           name: example-header-1
-          value: ""
+          value:
+          - ""
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
diff --git a/internal/gatewayapi/testdata/httproute-with-mirror-filter-multiple.out.yaml b/internal/gatewayapi/testdata/httproute-with-mirror-filter-multiple.out.yaml
index 9aa6f0bf2..c6e534c9c 100644
--- a/internal/gatewayapi/testdata/httproute-with-mirror-filter-multiple.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-mirror-filter-multiple.out.yaml
@@ -144,13 +144,16 @@ xdsIR:
       - addRequestHeaders:
         - append: true
           name: X-Header-Add
-          value: header-val-1
+          value:
+          - header-val-1
         - append: true
           name: X-Header-Add-Append
-          value: header-val-2
+          value:
+          - header-val-2
         - append: false
           name: X-Header-Set
-          value: set-overwrites-values
+          value:
+          - set-overwrites-values
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-adds.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-adds.out.yaml
index 7b53542bd..6dcb4b287 100644
--- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-adds.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-adds.out.yaml
@@ -140,19 +140,24 @@ xdsIR:
       - addResponseHeaders:
         - append: true
           name: Set-Header-1
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: set-header-2
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: set-header-3
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: set-header-5
-          value: some-value
+          value:
+          - some-value
         - append: false
           name: set-header-4
-          value: some-value
+          value:
+          - some-value
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-duplicate-add-multiple-filters.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-duplicate-add-multiple-filters.out.yaml
index 459c42647..47d61c9fc 100644
--- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-duplicate-add-multiple-filters.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-duplicate-add-multiple-filters.out.yaml
@@ -134,13 +134,16 @@ xdsIR:
       - addResponseHeaders:
         - append: true
           name: add-header-1
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: add-header-2
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: add-header-3
-          value: some-value
+          value:
+          - some-value
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-duplicate-adds.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-duplicate-adds.out.yaml
index d2b4ffbe3..1d2f4f712 100644
--- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-duplicate-adds.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-duplicate-adds.out.yaml
@@ -144,19 +144,24 @@ xdsIR:
       - addResponseHeaders:
         - append: true
           name: Set-Header-1
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: set-header-2
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: set-header-3
-          value: some-value
+          value:
+          - some-value
         - append: true
           name: set-header-5
-          value: some-value
+          value:
+          - some-value
         - append: false
           name: set-header-4
-          value: some-value
+          value:
+          - some-value
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml
index 9d188a03d..723cabbe6 100644
--- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml
@@ -128,10 +128,12 @@ xdsIR:
       - addResponseHeaders:
         - append: true
           name: example-header-2
-          value: ""
+          value:
+          - ""
         - append: false
           name: example-header-1
-          value: ""
+          value:
+          - ""
         destination:
           name: httproute/default/httproute-1/rule/0
           settings:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml
index 992328202..72c5d026b 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml
@@ -16,7 +16,6 @@ spec:
       app.kubernetes.io/component: proxy
       app.kubernetes.io/managed-by: envoy-gateway
       app.kubernetes.io/name: envoy
-      foo.bar: custom-label
       gateway.envoyproxy.io/owning-gateway-name: default
       gateway.envoyproxy.io/owning-gateway-namespace: default
   template:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml
index 5391a4938..2088c4cb3 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml
@@ -23,8 +23,6 @@ spec:
       app.kubernetes.io/name: envoy
       gateway.envoyproxy.io/owning-gateway-name: default
       gateway.envoyproxy.io/owning-gateway-namespace: default
-      label1: value1-override
-      label2: value2
   template:
     metadata:
       annotations:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml
index 8af199519..7628caffe 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml
@@ -19,7 +19,6 @@ spec:
       app.kubernetes.io/component: proxy
       app.kubernetes.io/managed-by: envoy-gateway
       app.kubernetes.io/name: envoy
-      foo.bar: custom-label
       gateway.envoyproxy.io/owning-gateway-name: default
       gateway.envoyproxy.io/owning-gateway-namespace: default
   strategy:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml
index 902de5049..f017b2d26 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml
@@ -19,7 +19,6 @@ spec:
       app.kubernetes.io/component: proxy
       app.kubernetes.io/managed-by: envoy-gateway
       app.kubernetes.io/name: envoy
-      foo.bar: custom-label
       gateway.envoyproxy.io/owning-gateway-name: default
       gateway.envoyproxy.io/owning-gateway-namespace: default
   strategy:
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml
index f3fca2808..0fb0d4140 100644
--- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml
@@ -25,8 +25,6 @@ spec:
       app.kubernetes.io/name: envoy
       gateway.envoyproxy.io/owning-gateway-name: default
       gateway.envoyproxy.io/owning-gateway-namespace: default
-      label1: value1-override
-      label2: value2
   strategy:
     type: RollingUpdate
   template:
diff --git a/internal/infrastructure/kubernetes/proxy_daemonset_test.go b/internal/infrastructure/kubernetes/proxy_daemonset_test.go
new file mode 100644
index 000000000..4901caec8
--- /dev/null
+++ b/internal/infrastructure/kubernetes/proxy_daemonset_test.go
@@ -0,0 +1,267 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package kubernetes
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	appsv1 "k8s.io/api/apps/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/envoygateway"
+	"github.com/envoyproxy/gateway/internal/envoygateway/config"
+	"github.com/envoyproxy/gateway/internal/gatewayapi"
+	"github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/proxy"
+	resource2 "github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/resource"
+	"github.com/envoyproxy/gateway/internal/ir"
+)
+
+func daemonsetWithImage(ds *appsv1.DaemonSet, image string) *appsv1.DaemonSet {
+	dCopy := ds.DeepCopy()
+	for i, c := range dCopy.Spec.Template.Spec.Containers {
+		if c.Name == envoyContainerName {
+			dCopy.Spec.Template.Spec.Containers[i].Image = image
+		}
+	}
+	return dCopy
+}
+
+func daemonsetWithSelectorAndLabel(ds *appsv1.DaemonSet, selector *metav1.LabelSelector, additionalLabel map[string]string) *appsv1.DaemonSet {
+	dCopy := ds.DeepCopy()
+	if selector != nil {
+		dCopy.Spec.Selector = selector
+	}
+	for k, v := range additionalLabel {
+		dCopy.Spec.Template.Labels[k] = v
+	}
+	return dCopy
+}
+
+func TestCreateOrUpdateProxyDaemonSet(t *testing.T) {
+	cfg, err := config.New()
+	require.NoError(t, err)
+
+	infra := ir.NewInfra()
+	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default"
+	infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name
+	infra.Proxy.Config = &egv1a1.EnvoyProxy{
+		Spec: egv1a1.EnvoyProxySpec{
+			Provider: &egv1a1.EnvoyProxyProvider{
+				Type: egv1a1.ProviderTypeKubernetes,
+				Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+					// Use daemonset, instead of deployment.
+					EnvoyDaemonSet: egv1a1.DefaultKubernetesDaemonSet(egv1a1.DefaultEnvoyProxyImage),
+					EnvoyService:   egv1a1.DefaultKubernetesService(),
+				},
+			},
+		},
+	}
+
+	r := proxy.NewResourceRender(cfg.Namespace, infra.GetProxyInfra(), cfg.EnvoyGateway)
+	ds, err := r.DaemonSet()
+	require.NoError(t, err)
+
+	testCases := []struct {
+		name    string
+		in      *ir.Infra
+		current *appsv1.DaemonSet
+		want    *appsv1.DaemonSet
+		wantErr bool
+	}{
+		{
+			name: "create daemonset",
+			in:   infra,
+			want: ds,
+		},
+		{
+			name:    "daemonset exists",
+			in:      infra,
+			current: ds,
+			want:    ds,
+		},
+		{
+			name: "update daemonset image",
+			in: &ir.Infra{
+				Proxy: &ir.ProxyInfra{
+					Metadata: &ir.InfraMetadata{
+						Labels: map[string]string{
+							gatewayapi.OwningGatewayNamespaceLabel: "default",
+							gatewayapi.OwningGatewayNameLabel:      infra.Proxy.Name,
+						},
+					},
+					Config: &egv1a1.EnvoyProxy{
+						Spec: egv1a1.EnvoyProxySpec{
+							Provider: &egv1a1.EnvoyProxyProvider{
+								Type: egv1a1.ProviderTypeKubernetes,
+								Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+									EnvoyDaemonSet: &egv1a1.KubernetesDaemonSetSpec{
+										Container: &egv1a1.KubernetesContainerSpec{
+											Image: ptr.To("envoyproxy/envoy-dev:v1.2.3"),
+										},
+									},
+								},
+							},
+						},
+					},
+					Name:      ir.DefaultProxyName,
+					Listeners: ir.NewProxyListeners(),
+				},
+			},
+			current: ds,
+			want:    daemonsetWithImage(ds, "envoyproxy/envoy-dev:v1.2.3"),
+		},
+		{
+			name: "update daemonset label",
+			in: &ir.Infra{
+				Proxy: &ir.ProxyInfra{
+					Metadata: &ir.InfraMetadata{
+						Labels: map[string]string{
+							gatewayapi.OwningGatewayNamespaceLabel: "default",
+							gatewayapi.OwningGatewayNameLabel:      infra.Proxy.Name,
+						},
+					},
+					Config: &egv1a1.EnvoyProxy{
+						Spec: egv1a1.EnvoyProxySpec{
+							Provider: &egv1a1.EnvoyProxyProvider{
+								Type: egv1a1.ProviderTypeKubernetes,
+								Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+									EnvoyDaemonSet: &egv1a1.KubernetesDaemonSetSpec{
+										Pod: &egv1a1.KubernetesPodSpec{
+											Labels: map[string]string{
+												// Add a new label to the custom label config.
+												// It wouldn't break the daemonset because the selector would still match after this label update.
+												"custom-label": "version1",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+					Name:      ir.DefaultProxyName,
+					Listeners: ir.NewProxyListeners(),
+				},
+			},
+			current: ds,
+			// Selector is not updated with a custom label, only pod's label is updated.
+			want: daemonsetWithSelectorAndLabel(ds, nil, map[string]string{"custom-label": "version1"}),
+		},
+		{
+			name: "the daemonset originally has a selector and label, and an user add a new label to the custom label config",
+			in: &ir.Infra{
+				Proxy: &ir.ProxyInfra{
+					Metadata: &ir.InfraMetadata{
+						Labels: map[string]string{
+							gatewayapi.OwningGatewayNamespaceLabel: "default",
+							gatewayapi.OwningGatewayNameLabel:      infra.Proxy.Name,
+						},
+					},
+					Config: &egv1a1.EnvoyProxy{
+						Spec: egv1a1.EnvoyProxySpec{
+							Provider: &egv1a1.EnvoyProxyProvider{
+								Type: egv1a1.ProviderTypeKubernetes,
+								Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+									EnvoyDaemonSet: &egv1a1.KubernetesDaemonSetSpec{
+										Pod: &egv1a1.KubernetesPodSpec{
+											Labels: map[string]string{
+												"custom-label":         "version1",
+												"another-custom-label": "version1", // added.
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+					Name:      ir.DefaultProxyName,
+					Listeners: ir.NewProxyListeners(),
+				},
+			},
+			current: daemonsetWithSelectorAndLabel(ds, resource2.GetSelector(map[string]string{"custom-label": "version1"}), map[string]string{"custom-label": "version1"}),
+			// Only label is updated, selector is not updated.
+			want: daemonsetWithSelectorAndLabel(ds, resource2.GetSelector(map[string]string{"custom-label": "version1"}), map[string]string{"custom-label": "version1", "another-custom-label": "version1"}),
+		},
+		{
+			name: "the daemonset originally has a selector and label, and an user update an existing custom label",
+			in: &ir.Infra{
+				Proxy: &ir.ProxyInfra{
+					Metadata: &ir.InfraMetadata{
+						Labels: map[string]string{
+							gatewayapi.OwningGatewayNamespaceLabel: "default",
+							gatewayapi.OwningGatewayNameLabel:      infra.Proxy.Name,
+						},
+					},
+					Config: &egv1a1.EnvoyProxy{
+						Spec: egv1a1.EnvoyProxySpec{
+							Provider: &egv1a1.EnvoyProxyProvider{
+								Type: egv1a1.ProviderTypeKubernetes,
+								Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+									EnvoyDaemonSet: &egv1a1.KubernetesDaemonSetSpec{
+										Pod: &egv1a1.KubernetesPodSpec{
+											Labels: map[string]string{
+												// Update the label value which will break the daemonset
+												// because the selector cannot be updated while the user wants to update the label value.
+												// We cannot help this case, just emit an error and let the user recreate the envoy proxy by themselves.
+												"custom-label": "version2",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+					Name:      ir.DefaultProxyName,
+					Listeners: ir.NewProxyListeners(),
+				},
+			},
+			current: daemonsetWithSelectorAndLabel(ds, resource2.GetSelector(map[string]string{"custom-label": "version1"}), map[string]string{"custom-label": "version1"}),
+			wantErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			var cli client.Client
+			if tc.current != nil {
+				cli = fakeclient.NewClientBuilder().
+					WithScheme(envoygateway.GetScheme()).
+					WithObjects(tc.current).
+					WithInterceptorFuncs(interceptorFunc).
+					Build()
+			} else {
+				cli = fakeclient.NewClientBuilder().
+					WithScheme(envoygateway.GetScheme()).
+					WithInterceptorFuncs(interceptorFunc).
+					Build()
+			}
+
+			kube := NewInfra(cli, cfg)
+			r := proxy.NewResourceRender(kube.Namespace, tc.in.GetProxyInfra(), cfg.EnvoyGateway)
+			err := kube.createOrUpdateDaemonSet(context.Background(), r)
+			if tc.wantErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+
+			actual := &appsv1.DaemonSet{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: kube.Namespace,
+					Name:      proxy.ExpectedResourceHashedName(tc.in.Proxy.Name),
+				},
+			}
+			require.NoError(t, kube.Client.Get(context.Background(), client.ObjectKeyFromObject(actual), actual))
+			require.Equal(t, tc.want.Spec, actual.Spec)
+		})
+	}
+}
diff --git a/internal/infrastructure/kubernetes/proxy_deployment_test.go b/internal/infrastructure/kubernetes/proxy_deployment_test.go
index a1d595b75..88537981b 100644
--- a/internal/infrastructure/kubernetes/proxy_deployment_test.go
+++ b/internal/infrastructure/kubernetes/proxy_deployment_test.go
@@ -21,6 +21,7 @@ import (
 	"github.com/envoyproxy/gateway/internal/envoygateway/config"
 	"github.com/envoyproxy/gateway/internal/gatewayapi"
 	"github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/proxy"
+	resource2 "github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/resource"
 	"github.com/envoyproxy/gateway/internal/ir"
 )
 
@@ -39,6 +40,17 @@ func deploymentWithImage(deploy *appsv1.Deployment, image string) *appsv1.Deploy
 	return dCopy
 }
 
+func deploymentWithSelectorAndLabel(deploy *appsv1.Deployment, selector *metav1.LabelSelector, additionalLabel map[string]string) *appsv1.Deployment {
+	dCopy := deploy.DeepCopy()
+	if selector != nil {
+		dCopy.Spec.Selector = selector
+	}
+	for k, v := range additionalLabel {
+		dCopy.Spec.Template.Labels[k] = v
+	}
+	return dCopy
+}
+
 func TestCreateOrUpdateProxyDeployment(t *testing.T) {
 	cfg, err := config.New()
 	require.NoError(t, err)
@@ -56,6 +68,7 @@ func TestCreateOrUpdateProxyDeployment(t *testing.T) {
 		in      *ir.Infra
 		current *appsv1.Deployment
 		want    *appsv1.Deployment
+		wantErr bool
 	}{
 		{
 			name: "create deployment",
@@ -99,6 +112,113 @@ func TestCreateOrUpdateProxyDeployment(t *testing.T) {
 			current: deploy,
 			want:    deploymentWithImage(deploy, "envoyproxy/envoy-dev:v1.2.3"),
 		},
+		{
+			name: "update deployment label",
+			in: &ir.Infra{
+				Proxy: &ir.ProxyInfra{
+					Metadata: &ir.InfraMetadata{
+						Labels: map[string]string{
+							gatewayapi.OwningGatewayNamespaceLabel: "default",
+							gatewayapi.OwningGatewayNameLabel:      infra.Proxy.Name,
+						},
+					},
+					Config: &egv1a1.EnvoyProxy{
+						Spec: egv1a1.EnvoyProxySpec{
+							Provider: &egv1a1.EnvoyProxyProvider{
+								Type: egv1a1.ProviderTypeKubernetes,
+								Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+									EnvoyDeployment: &egv1a1.KubernetesDeploymentSpec{
+										Pod: &egv1a1.KubernetesPodSpec{
+											Labels: map[string]string{
+												"custom-label": "version1", // added.
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+					Name:      ir.DefaultProxyName,
+					Listeners: ir.NewProxyListeners(),
+				},
+			},
+			current: deploy,
+			// Selector is not updated with a custom label, only pod's label is updated.
+			want: deploymentWithSelectorAndLabel(deploy, nil, map[string]string{"custom-label": "version1"}),
+		},
+		{
+			name: "the daemonset originally has a selector and label, and an user add a new label to the custom label config",
+			in: &ir.Infra{
+				Proxy: &ir.ProxyInfra{
+					Metadata: &ir.InfraMetadata{
+						Labels: map[string]string{
+							gatewayapi.OwningGatewayNamespaceLabel: "default",
+							gatewayapi.OwningGatewayNameLabel:      infra.Proxy.Name,
+						},
+					},
+					Config: &egv1a1.EnvoyProxy{
+						Spec: egv1a1.EnvoyProxySpec{
+							Provider: &egv1a1.EnvoyProxyProvider{
+								Type: egv1a1.ProviderTypeKubernetes,
+								Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+									EnvoyDeployment: &egv1a1.KubernetesDeploymentSpec{
+										Pod: &egv1a1.KubernetesPodSpec{
+											Labels: map[string]string{
+												"custom-label": "version1",
+												// Add a new label to the custom label config.
+												// It wouldn't break the deployment because the selector would still match after this label update.
+												"another-custom-label": "version1", // added.
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+					Name:      ir.DefaultProxyName,
+					Listeners: ir.NewProxyListeners(),
+				},
+			},
+			current: deploymentWithSelectorAndLabel(deploy, resource2.GetSelector(map[string]string{"custom-label": "version1"}), map[string]string{"custom-label": "version1"}),
+			// Only label is updated, selector is not updated.
+			want: deploymentWithSelectorAndLabel(deploy, resource2.GetSelector(map[string]string{"custom-label": "version1"}), map[string]string{"custom-label": "version1", "another-custom-label": "version1"}),
+		},
+		{
+			name: "the deployment originally has a selector and label, and an user update an existing custom label",
+			in: &ir.Infra{
+				Proxy: &ir.ProxyInfra{
+					Metadata: &ir.InfraMetadata{
+						Labels: map[string]string{
+							gatewayapi.OwningGatewayNamespaceLabel: "default",
+							gatewayapi.OwningGatewayNameLabel:      infra.Proxy.Name,
+						},
+					},
+					Config: &egv1a1.EnvoyProxy{
+						Spec: egv1a1.EnvoyProxySpec{
+							Provider: &egv1a1.EnvoyProxyProvider{
+								Type: egv1a1.ProviderTypeKubernetes,
+								Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+									EnvoyDeployment: &egv1a1.KubernetesDeploymentSpec{
+										Pod: &egv1a1.KubernetesPodSpec{
+											Labels: map[string]string{
+												// Update the label value which will break the deployment
+												// because the selector cannot be updated while the user wants to update the label value.
+												// We cannot help this case, just emit an error and let the user recreate the envoy proxy by themselves.
+												"custom-label": "version2",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+					Name:      ir.DefaultProxyName,
+					Listeners: ir.NewProxyListeners(),
+				},
+			},
+			current: deploymentWithSelectorAndLabel(deploy, resource2.GetSelector(map[string]string{"custom-label": "version1"}), map[string]string{"custom-label": "version1"}),
+			wantErr: true,
+		},
 	}
 
 	for _, tc := range testCases {
@@ -121,6 +241,10 @@ func TestCreateOrUpdateProxyDeployment(t *testing.T) {
 			kube := NewInfra(cli, cfg)
 			r := proxy.NewResourceRender(kube.Namespace, tc.in.GetProxyInfra(), cfg.EnvoyGateway)
 			err := kube.createOrUpdateDeployment(context.Background(), r)
+			if tc.wantErr {
+				require.Error(t, err)
+				return
+			}
 			require.NoError(t, err)
 
 			actual := &appsv1.Deployment{
diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go
index 9492c3783..6724549e3 100644
--- a/internal/ir/xds_test.go
+++ b/internal/ir/xds_test.go
@@ -338,17 +338,16 @@ var (
 		AddRequestHeaders: []AddHeader{
 			{
 				Name:   "example-header",
-				Value:  "example-value",
+				Value:  []string{"example-value"},
 				Append: true,
 			},
 			{
 				Name:   "example-header-2",
-				Value:  "example-value-2",
+				Value:  []string{"example-value-2"},
 				Append: false,
 			},
 			{
 				Name:   "empty-header",
-				Value:  "",
 				Append: false,
 			},
 		},
@@ -376,12 +375,12 @@ var (
 		AddRequestHeaders: []AddHeader{
 			{
 				Name:   "example-header",
-				Value:  "example-value",
+				Value:  []string{"example-value"},
 				Append: true,
 			},
 			{
 				Name:   "example-header",
-				Value:  "example-value-2",
+				Value:  []string{"example-value-2"},
 				Append: false,
 			},
 		},
@@ -401,7 +400,7 @@ var (
 		AddRequestHeaders: []AddHeader{
 			{
 				Name:   "",
-				Value:  "example-value",
+				Value:  []string{"example-value"},
 				Append: true,
 			},
 		},
@@ -416,17 +415,16 @@ var (
 		AddResponseHeaders: []AddHeader{
 			{
 				Name:   "example-header",
-				Value:  "example-value",
+				Value:  []string{"example-value"},
 				Append: true,
 			},
 			{
 				Name:   "example-header-2",
-				Value:  "example-value-2",
+				Value:  []string{"example-value-2"},
 				Append: false,
 			},
 			{
 				Name:   "empty-header",
-				Value:  "",
 				Append: false,
 			},
 		},
@@ -454,12 +452,12 @@ var (
 		AddResponseHeaders: []AddHeader{
 			{
 				Name:   "example-header",
-				Value:  "example-value",
+				Value:  []string{"example-value"},
 				Append: true,
 			},
 			{
 				Name:   "example-header",
-				Value:  "example-value-2",
+				Value:  []string{"example-value-2"},
 				Append: false,
 			},
 		},
@@ -479,7 +477,7 @@ var (
 		AddResponseHeaders: []AddHeader{
 			{
 				Name:   "",
-				Value:  "example-value",
+				Value:  []string{"example-value"},
 				Append: true,
 			},
 		},
diff --git a/internal/xds/translator/jsonpathtopointer_test.go b/internal/xds/translator/jsonpathtopointer_test.go
new file mode 100644
index 000000000..cefb59258
--- /dev/null
+++ b/internal/xds/translator/jsonpathtopointer_test.go
@@ -0,0 +1,395 @@
+// 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 (
+	"sort"
+	"strconv"
+	"testing"
+
+	"github.com/ohler55/ojg/jp"
+	"github.com/stretchr/testify/require"
+)
+
+const case1Simple string = `{
+			"a": "b"
+		}`
+
+const case2Nested string = `{
+			"a": "b",
+			"v": [{
+				"x": "test1",
+				"y": "hello"
+			},
+			{
+				"x": "test2",
+				"y": "world"
+			}],
+			"f":{
+				"w": "hi",
+				"q": "welcome",
+				"y": "ciao"
+			},
+			"y": "c"
+		}`
+
+const case3Route string = `{
+  "name": "default/eg/http",
+  "virtual_hosts": [
+   {
+      "name": "default/eg/http/www_test_com",
+      "domains": [
+        "www.test.com"
+      ],
+      "routes": [
+        {
+          "name": "httproute/default/backend/rule/0/match/0/www_test_com",
+          "match": {
+            "prefix": "/"
+          },
+          "route": {
+            "cluster": "httproute/default/backend/rule/0",
+            "upgrade_configs": [
+              {
+                "upgrade_type": "websocket"
+              }
+            ]
+          }
+        }
+      ]
+    },
+    {
+      "name": "default/eg/http/www_example_com",
+      "domains": [
+        "www.example.com"
+      ],
+      "routes": [
+        {
+          "name": "httproute/default/backend/rule/1/match/1/www_example_com",
+          "match": {
+            "prefix": "/"
+          },
+          "route": {
+            "cluster": "httproute/default/backend/rule/1",
+            "upgrade_configs": [
+              {
+                "upgrade_type": "websocket"
+              }
+            ]
+          }
+        }
+      ]
+    }
+  ],
+  "ignore_port_in_host_matching": true
+}`
+
+func Test(t *testing.T) {
+	tests := []struct {
+		// Json Document
+		doc string
+
+		// JSONPath
+		jsonPath string
+
+		// path
+		path string
+
+		// List of expected pointers
+		expected []string
+	}{
+		{
+			doc:      case1Simple,
+			jsonPath: "$.a",
+			expected: []string{
+				"/a",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "$.v[?(@.x=='test2')]",
+			expected: []string{
+				"/v/1",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "..v[?(@.x=='test1')].y",
+			expected: []string{
+				"/v/0/y",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "$.v[?(@.x=='test2')].y",
+			expected: []string{
+				"/v/1/y",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "$.v[?(@.x=='test1')].y",
+			expected: []string{
+				"/v/0/y",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "$.v[*].y",
+			expected: []string{
+				"/v/0/y",
+				"/v/1/y",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "$.v[?(@.x=='UNKNOWN')].y",
+			expected: []string{},
+		},
+		{
+			doc:      case1Simple,
+			jsonPath: ".a",
+			expected: []string{
+				"/a",
+			},
+		},
+		{
+			doc:      case1Simple,
+			jsonPath: "a",
+			expected: []string{
+				"/a",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "f.w",
+			expected: []string{
+				"/f/w",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "f.*",
+			expected: []string{
+				"/f/w",
+				"/f/q",
+				"/f/y",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "v.*",
+			expected: []string{
+				"/v/0",
+				"/v/1",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "v.**",
+			expected: []string{
+				"/v/0/x",
+				"/v/0/y",
+				"/v/1/x",
+				"/v/1/y",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "$..y",
+			expected: []string{
+				"/f/y",
+				"/v/0/y",
+				"/v/1/y",
+				"/y",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "..y",
+			expected: []string{
+				"/f/y",
+				"/v/0/y",
+				"/v/1/y",
+				"/y",
+			},
+		},
+		{
+			doc:      case2Nested,
+			jsonPath: "**.y",
+			expected: []string{
+				"/v/0/y",
+				"/v/1/y",
+			},
+		},
+		{
+			doc:      case3Route,
+			jsonPath: "..routes[?(@.name =~ 'www_example_com')]",
+			expected: []string{
+				"/virtual_hosts/1/routes/0",
+			},
+		},
+		{
+			doc:      case3Route,
+			jsonPath: "..routes[?(@.name =~ 'www_test_com')]",
+			expected: []string{
+				"/virtual_hosts/0/routes/0",
+			},
+		},
+		{
+			doc:      case3Route,
+			jsonPath: "..routes[?(@.name =~ 'www')]",
+			expected: []string{
+				"/virtual_hosts/0/routes/0",
+				"/virtual_hosts/1/routes/0",
+			},
+		},
+		{
+			doc:      case3Route,
+			jsonPath: "..routes[?(@.name =~ 'www')].route.cluster",
+			expected: []string{
+				"/virtual_hosts/0/routes/0/route/cluster",
+				"/virtual_hosts/1/routes/0/route/cluster",
+			},
+		},
+		{
+			doc:      case3Route,
+			jsonPath: "..routes[?(@.name =~ 'www')]['route']['cluster']",
+			expected: []string{
+				"/virtual_hosts/0/routes/0/route/cluster",
+				"/virtual_hosts/1/routes/0/route/cluster",
+			},
+		},
+		{
+			doc:      case3Route,
+			jsonPath: "..routes[?(@.name=='httproute/default/backend/rule/1/match/1/www_example_com')].route.upgrade_configs",
+			expected: []string{
+				"/virtual_hosts/1/routes/0/route/upgrade_configs",
+			},
+		},
+		{
+			doc:      case3Route,
+			jsonPath: "..routes[?(@.name =~ 'www')]",
+			path:     "/abc",
+			expected: []string{
+				"/virtual_hosts/0/routes/0/abc",
+				"/virtual_hosts/1/routes/0/abc",
+			},
+		},
+		{
+			doc:      case3Route,
+			jsonPath: "..routes[?(@.name =~ 'www')]",
+			path:     "abc",
+			expected: []string{
+				"/virtual_hosts/0/routes/0/abc",
+				"/virtual_hosts/1/routes/0/abc",
+			},
+		},
+		{
+			doc:      case3Route,
+			jsonPath: "..routes[?(@.name =~ 'www')]",
+			path:     "/",
+			expected: []string{
+				"/virtual_hosts/0/routes/0/",
+				"/virtual_hosts/1/routes/0/",
+			},
+		},
+	}
+
+	for i, test := range tests {
+
+		testCasePrefix := "TestCase " + strconv.Itoa(i+1)
+		pointers, err := ConvertPathToPointers([]byte(test.doc), test.jsonPath, test.path)
+		if err != nil {
+			t.Error(testCasePrefix + ": Error during conversion:\n" + err.Error())
+			continue
+		}
+
+		expectedAsString := asString(test.expected)
+		pointersAsString := asString(pointers)
+
+		require.Equal(t, expectedAsString, pointersAsString)
+	}
+}
+
+func TestException(t *testing.T) {
+	tests := []struct {
+		// Json Document
+		doc string
+
+		// JSONPath
+		jsonPath string
+
+		// path
+		path string
+
+		// expected exception
+		expected string
+	}{
+		{
+			doc:      case1Simple,
+			jsonPath: ".$",
+			expected: "Error during parsing jpath",
+		},
+		{
+			doc:      case1Simple,
+			jsonPath: "$",
+			expected: "only Root",
+		},
+		{
+			doc:      "{",
+			jsonPath: ".$",
+			expected: "Error during parsing json",
+		},
+	}
+
+	for i, test := range tests {
+
+		testCasePrefix := "TestCase " + strconv.Itoa(i+1)
+		_, err := ConvertPathToPointers([]byte(test.doc), test.jsonPath, test.path)
+		if err == nil {
+			t.Error(testCasePrefix + ": Error expected, but no error found!")
+			continue
+		}
+
+		require.ErrorContains(t, err, test.expected)
+	}
+}
+
+func TestUnexpectedFrag(t *testing.T) {
+	expr := jp.Expr{}
+	expr = append(expr, jp.Union{})
+
+	_, err := expToPointer(expr)
+	if err == nil {
+		t.Error("Error expected, but no error found!")
+	}
+
+	require.ErrorContains(t, err, "There is no conversion implemented for Union")
+}
+
+func TestNegativeNth(t *testing.T) {
+	result, err := nthToPointer(jp.Nth(-1))
+	if err != nil {
+		t.Error(err)
+	}
+	test := string(result)
+	if test != "-1" {
+		t.Error("expected -1, but was " + test + "!")
+	}
+}
+
+func asString(values []string) string {
+	var buf []byte
+
+	sort.Strings(values)
+	for _, v := range values {
+		buf = append(buf, []byte(v)...)
+		buf = append(buf, []byte("\n")...)
+	}
+
+	return string(buf)
+}
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-request-headers.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-request-headers.yaml
index c3dc4417d..fb45b8db7 100644
--- a/internal/xds/translator/testdata/in/xds-ir/http-route-request-headers.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/http-route-request-headers.yaml
@@ -18,20 +18,30 @@ http:
         - host: "1.2.3.4"
           port: 50000
     addRequestHeaders:
+    - name: "some-header-multi-value"
+      value:
+      - "some-value"
+      - "some-additional-value"
+      append: true
     - name: "some-header"
-      value: "some-value"
+      value:
+      - "some-value"
       append: true
     - name: "some-header-2"
-      value: "some-value"
+      value:
+      - "some-value"
       append: true
     - name: "some-header3"
-      value: "some-value"
+      value:
+      - "some-value"
       append: false
     - name: "some-header4"
-      value: "some-value"
+      value:
+      - "some-value"
       append: false
     - name: "empty-header"
-      value: ""
+      value:
+      - ""
       append: false
     removeRequestHeaders:
     - "some-header5"
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-response-add-headers.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-response-add-headers.yaml
index e3114e2d2..3cfaf5e49 100644
--- a/internal/xds/translator/testdata/in/xds-ir/http-route-response-add-headers.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/http-route-response-add-headers.yaml
@@ -19,17 +19,22 @@ http:
           port: 50000
     addResponseHeaders:
     - name: "some-header"
-      value: "some-value"
+      value:
+      - "some-value"
       append: true
     - name: "some-header-2"
-      value: "some-value"
+      value:
+      - "some-value"
       append: true
     - name: "some-header3"
-      value: "some-value"
+      value:
+      - "some-value"
       append: false
     - name: "some-header4"
-      value: "some-value"
+      value:
+      - "some-value"
       append: false
     - name: "empty-header"
-      value: ""
+      value:
+      - ""
       append: false
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-response-add-remove-headers.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-response-add-remove-headers.yaml
index 0e59f8f12..c97d927df 100644
--- a/internal/xds/translator/testdata/in/xds-ir/http-route-response-add-remove-headers.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/http-route-response-add-remove-headers.yaml
@@ -19,19 +19,24 @@ http:
           port: 50000
     addResponseHeaders:
     - name: "some-header"
-      value: "some-value"
+      value:
+      - "some-value"
       append: true
     - name: "some-header-2"
-      value: "some-value"
+      value:
+      - "some-value"
       append: true
     - name: "some-header3"
-      value: "some-value"
+      value:
+      - "some-value"
       append: false
     - name: "some-header4"
-      value: "some-value"
+      value:
+      - "some-value"
       append: false
     - name: "empty-header"
-      value: ""
+      value:
+      - ""
       append: false
     removeResponseHeaders:
     - "some-header5"
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-weighted-backend-with-filters.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-weighted-backend-with-filters.yaml
index f8943d07f..8745e9893 100644
--- a/internal/xds/translator/testdata/in/xds-ir/http-route-weighted-backend-with-filters.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/http-route-weighted-backend-with-filters.yaml
@@ -19,7 +19,8 @@ http:
                 addRequestHeaders:
                   - append: false
                     name: add-header-3
-                    value: some-value
+                    value:
+                      - some-value
               protocol: HTTP
               weight: 1
         hostname: '*'
@@ -37,10 +38,12 @@ http:
                 addRequestHeaders:
                   - append: true
                     name: add-header-1
-                    value: some-value
+                    value:
+                      - some-value
                   - append: true
                     name: add-header-2
-                    value: some-value
+                    value:
+                      - some-value
               protocol: HTTP
               weight: 8
             - addressType: IP
diff --git a/internal/xds/translator/testdata/in/xds-ir/jsonpatch-add-op-empty-jsonpath.yaml b/internal/xds/translator/testdata/in/xds-ir/jsonpatch-add-op-empty-jsonpath.yaml
new file mode 100644
index 000000000..9c2487729
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/jsonpatch-add-op-empty-jsonpath.yaml
@@ -0,0 +1,63 @@
+envoyPatchPolicies:
+- status:
+    ancestors:
+    - ancestorRef:
+        group: "gateway.networking.k8s.io"
+        kind: "Gateway"
+        namespace: "default"
+        name: "foobar"
+  name: "first-policy"
+  namespace: "default"
+  jsonPatches:
+  - type: "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
+    name: second-listener
+    operation:
+      op: add
+      value:
+        clusterName: second-route-dest
+        endpoints:
+        - lbEndpoints:
+          - endpoint:
+              address:
+                socketAddress:
+                  address: 4.5.6.7
+                  portValue: 50000
+            loadBalancingWeight: 1
+          loadBalancingWeight: 1
+          locality:
+            region: second-route-dest/backend/0
+http:
+- name: "first-listener"
+  address: "0.0.0.0"
+  port: 10080
+  hostnames:
+  - "*"
+  path:
+    mergeSlashes: true
+    escapedSlashesAction: UnescapeAndRedirect
+  tls:
+    alpnProtocols:
+    - h2
+    - http/1.1
+    certificates:
+    - name: secret-1
+      # byte slice representation of "key-data"
+      serverCertificate: [99, 101, 114, 116, 45, 100, 97, 116, 97]
+      # byte slice representation of "key-data"
+      privateKey: [107, 101, 121, 45, 100, 97, 116, 97]
+    - name: secret-2
+      serverCertificate: [99, 101, 114, 116, 45, 100, 97, 116, 97]
+      privateKey: [107, 101, 121, 45, 100, 97, 116, 97]
+  routes:
+  - name: "first-route"
+    hostname: "*"
+    headerMatches:
+    - name: user
+      stringMatch:
+      exact: "jason"
+    destination:
+      name: "first-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
diff --git a/internal/xds/translator/testdata/in/xds-ir/jsonpatch-with-jsonpath.yaml b/internal/xds/translator/testdata/in/xds-ir/jsonpatch-with-jsonpath.yaml
new file mode 100644
index 000000000..a02cad99d
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/jsonpatch-with-jsonpath.yaml
@@ -0,0 +1,172 @@
+envoyPatchPolicies:
+- status:
+    ancestors:
+    - ancestorRef:
+        group: "gateway.networking.k8s.io"
+        kind: "Gateway"
+        namespace: "default"
+        name: "foobar"
+  name: "first-policy"
+  namespace: "default"
+  jsonPatches:
+  - type: "type.googleapis.com/envoy.config.listener.v3.Listener"
+    name: first-listener
+    operation:
+      op: "add"
+      jsonPath: "$.filter_chains[0].filters[0].typed_config"
+      path: "/preserve_external_request_id"
+      value: true
+  - type: "type.googleapis.com/envoy.config.listener.v3.Listener"
+    name: "first-listener"
+    operation:
+      op: "add"
+      jsonPath: "filter_chains[0].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: "eg-ratelimit"
+          failure_mode_deny: true
+          timeout: 1s
+          rate_limit_service:
+            grpc_service:
+              envoy_grpc:
+                cluster_name: rate-limit-cluster
+            transport_api_version: V3
+  - type: "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"
+    name: "first-listener"
+    operation:
+      op: "add"
+      jsonPath: "virtual_hosts[0]"
+      path: "rate_limits"
+      value:
+      - actions:
+        - remote_address: {}
+  - type: "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"
+    name: "first-listener"
+    operation:
+      op: "replace"
+      jsonPath: "..routes[?(@.name=='second-route')].route.upgrade_configs"
+      value:
+      - upgrade_type: CONNECT
+        connect_config:
+          {}
+  - type: "type.googleapis.com/envoy.config.cluster.v3.Cluster"
+    name: rate-limit-cluster
+    operation:
+      op: add
+      path: ""
+      value:
+        name: rate-limit-cluster
+        type: STRICT_DNS
+        connect_timeout: 10s
+        lb_policy: ROUND_ROBIN
+        http2_protocol_options: {}
+        load_assignment:
+          cluster_name: rate-limit-cluster
+          endpoints:
+          - lb_endpoints:
+            - endpoint:
+                address:
+                  socket_address:
+                    address: ratelimit.svc.cluster.local
+                    port_value: 8081
+  - type: "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
+    name: "first-route-dest"
+    operation:
+      op: "replace"
+      jsonPath: "..endpoints[*].load_balancing_weight"
+      value: "50"
+  - type: "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"
+    name: "secret-1"
+    operation:
+      op: "replace"
+      jsonPath: "$.tls_certificate.certificate_chain.inline_bytes"
+      value: "a2V5LWRhdGE="
+  - type: "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"
+    name: "test-secret"
+    operation:
+      op: "add"
+      path: ""
+      value:
+        name: test_secret
+        tls_certificate:
+          certificate_chain:
+            inline_bytes: Y2VydC1kYXRh
+  - type: "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
+    name: "first-route-dest"
+    operation:
+      op: add
+      jsonPath: "endpoints"
+      path: "/1"
+      value:
+        lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 1.2.3.4
+                portValue: 50000
+          loadBalancingWeight: 1
+  - type: "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
+    name: "first-route-dest"
+    operation:
+      op: "move"
+      from: "/endpoints/0/load_balancing_weight"
+      path: "/endpoints/1/load_balancing_weight"
+  - type: "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
+    name: "first-route-dest"
+    operation:
+      op: copy
+      from: "/endpoints/1/load_balancing_weight"
+      path: "/endpoints/0/load_balancing_weight"
+http:
+- name: "first-listener"
+  address: "0.0.0.0"
+  port: 10080
+  hostnames:
+  - "*"
+  path:
+    mergeSlashes: true
+    escapedSlashesAction: UnescapeAndRedirect
+  tls:
+    alpnProtocols:
+    - h2
+    - http/1.1
+    certificates:
+    - name: secret-1
+      # byte slice representation of "key-data"
+      serverCertificate: [99, 101, 114, 116, 45, 100, 97, 116, 97]
+      # byte slice representation of "key-data"
+      privateKey: [107, 101, 121, 45, 100, 97, 116, 97]
+    - name: secret-2
+      serverCertificate: [99, 101, 114, 116, 45, 100, 97, 116, 97]
+      privateKey: [107, 101, 121, 45, 100, 97, 116, 97]
+  routes:
+  - name: "first-route"
+    hostname: "*"
+    headerMatches:
+    - name: user
+      stringMatch:
+      exact: "jason"
+    destination:
+      name: "first-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
+  - name: "second-route"
+    hostname: "*"
+    headerMatches:
+    - name: user
+      stringMatch:
+      exact: "james"
+    - name: country
+      stringMatch:
+      exact: "US"
+    destination:
+      name: "second-route-dest"
+      settings:
+      - endpoints:
+        - host: "4.5.6.7"
+          port: 60000
+
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-request-headers.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-request-headers.routes.yaml
index f91a70cb2..1f2c6be40 100644
--- a/internal/xds/translator/testdata/out/xds-ir/http-route-request-headers.routes.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-request-headers.routes.yaml
@@ -9,6 +9,12 @@
         prefix: /
       name: request-header-route
       requestHeadersToAdd:
+      - header:
+          key: some-header-multi-value
+          value: some-value
+      - header:
+          key: some-header-multi-value
+          value: some-additional-value
       - header:
           key: some-header
           value: some-value
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.clusters.yaml
new file mode 100644
index 000000000..d53a7a1b2
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.clusters.yaml
@@ -0,0 +1,17 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: first-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: first-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.endpoints.yaml
new file mode 100644
index 000000000..9a6f5a46c
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.endpoints.yaml
@@ -0,0 +1,24 @@
+- clusterName: first-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: first-route-dest/backend/0
+- clusterName: second-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 4.5.6.7
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: second-route-dest/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.envoypatchpolicies.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.envoypatchpolicies.yaml
new file mode 100644
index 000000000..9508dd3e7
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.envoypatchpolicies.yaml
@@ -0,0 +1,16 @@
+- name: first-policy
+  namespace: default
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: foobar
+        namespace: default
+      conditions:
+      - lastTransitionTime: null
+        message: Patches have been successfully applied.
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      controllerName: ""
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.listeners.yaml
new file mode 100644
index 000000000..51c022c26
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.listeners.yaml
@@ -0,0 +1,52 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  drainType: MODIFY_ONLY
+  filterChains:
+  - filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.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: first-listener
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: https-10080
+        useRemoteAddress: true
+    name: first-listener
+    transportSocket:
+      name: envoy.transport_sockets.tls
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+        commonTlsContext:
+          alpnProtocols:
+          - h2
+          - http/1.1
+          tlsCertificateSdsSecretConfigs:
+          - name: secret-1
+            sdsConfig:
+              ads: {}
+              resourceApiVersion: V3
+          - name: secret-2
+            sdsConfig:
+              ads: {}
+              resourceApiVersion: V3
+  name: first-listener
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.routes.yaml
new file mode 100644
index 000000000..4a412b357
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.routes.yaml
@@ -0,0 +1,18 @@
+- ignorePortInHostMatching: true
+  name: first-listener
+  virtualHosts:
+  - domains:
+    - '*'
+    name: first-listener/*
+    routes:
+    - match:
+        headers:
+        - name: user
+          stringMatch:
+            exact: jason
+        prefix: /
+      name: first-route
+      route:
+        cluster: first-route-dest
+        upgradeConfigs:
+        - upgradeType: websocket
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.secrets.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.secrets.yaml
new file mode 100644
index 000000000..ad88ffe43
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-add-op-empty-jsonpath.secrets.yaml
@@ -0,0 +1,12 @@
+- name: secret-1
+  tlsCertificate:
+    certificateChain:
+      inlineBytes: Y2VydC1kYXRh
+    privateKey:
+      inlineBytes: a2V5LWRhdGE=
+- name: secret-2
+  tlsCertificate:
+    certificateChain:
+      inlineBytes: Y2VydC1kYXRh
+    privateKey:
+      inlineBytes: a2V5LWRhdGE=
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.clusters.yaml
new file mode 100644
index 000000000..b3842b6e5
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.clusters.yaml
@@ -0,0 +1,47 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: first-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: first-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: second-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: second-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+- connectTimeout: 10s
+  http2ProtocolOptions: {}
+  loadAssignment:
+    clusterName: rate-limit-cluster
+    endpoints:
+    - lbEndpoints:
+      - endpoint:
+          address:
+            socketAddress:
+              address: ratelimit.svc.cluster.local
+              portValue: 8081
+  name: rate-limit-cluster
+  type: STRICT_DNS
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.endpoints.yaml
new file mode 100644
index 000000000..131cd47c7
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.endpoints.yaml
@@ -0,0 +1,32 @@
+- clusterName: first-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 50
+    locality:
+      region: first-route-dest/backend/0
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 50
+- clusterName: second-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 4.5.6.7
+            portValue: 60000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: second-route-dest/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.envoypatchpolicies.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.envoypatchpolicies.yaml
new file mode 100644
index 000000000..9508dd3e7
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.envoypatchpolicies.yaml
@@ -0,0 +1,16 @@
+- name: first-policy
+  namespace: default
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: foobar
+        namespace: default
+      conditions:
+      - lastTransitionTime: null
+        message: Patches have been successfully applied.
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      controllerName: ""
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.listeners.yaml
new file mode 100644
index 000000000..08b5d410d
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.listeners.yaml
@@ -0,0 +1,64 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  drainType: MODIFY_ONLY
+  filterChains:
+  - filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.ratelimit
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
+            domain: eg-ratelimit
+            failureModeDeny: true
+            rateLimitService:
+              grpcService:
+                envoyGrpc:
+                  clusterName: rate-limit-cluster
+              transportApiVersion: V3
+            timeout: 1s
+        - 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
+        preserveExternalRequestId: true
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: first-listener
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: https-10080
+        useRemoteAddress: true
+    name: first-listener
+    transportSocket:
+      name: envoy.transport_sockets.tls
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+        commonTlsContext:
+          alpnProtocols:
+          - h2
+          - http/1.1
+          tlsCertificateSdsSecretConfigs:
+          - name: secret-1
+            sdsConfig:
+              ads: {}
+              resourceApiVersion: V3
+          - name: secret-2
+            sdsConfig:
+              ads: {}
+              resourceApiVersion: V3
+  name: first-listener
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.routes.yaml
new file mode 100644
index 000000000..a7273c7a1
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.routes.yaml
@@ -0,0 +1,36 @@
+- ignorePortInHostMatching: true
+  name: first-listener
+  virtualHosts:
+  - domains:
+    - '*'
+    name: first-listener/*
+    rateLimits:
+    - actions:
+      - remoteAddress: {}
+    routes:
+    - match:
+        headers:
+        - name: user
+          stringMatch:
+            exact: jason
+        prefix: /
+      name: first-route
+      route:
+        cluster: first-route-dest
+        upgradeConfigs:
+        - upgradeType: websocket
+    - match:
+        headers:
+        - name: user
+          stringMatch:
+            exact: james
+        - name: country
+          stringMatch:
+            exact: US
+        prefix: /
+      name: second-route
+      route:
+        cluster: second-route-dest
+        upgradeConfigs:
+        - connectConfig: {}
+          upgradeType: CONNECT
diff --git a/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.secrets.yaml b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.secrets.yaml
new file mode 100644
index 000000000..d1c4b32fd
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/jsonpatch-with-jsonpath.secrets.yaml
@@ -0,0 +1,16 @@
+- name: secret-1
+  tlsCertificate:
+    certificateChain:
+      inlineBytes: a2V5LWRhdGE=
+    privateKey:
+      inlineBytes: a2V5LWRhdGE=
+- name: secret-2
+  tlsCertificate:
+    certificateChain:
+      inlineBytes: Y2VydC1kYXRh
+    privateKey:
+      inlineBytes: a2V5LWRhdGE=
+- name: test_secret
+  tlsCertificate:
+    certificateChain:
+      inlineBytes: Y2VydC1kYXRh
diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go
index f5918b4bc..ec5e01c47 100644
--- a/internal/xds/translator/translator_test.go
+++ b/internal/xds/translator/translator_test.go
@@ -55,6 +55,12 @@ func TestTranslateXds(t *testing.T) {
 		"jsonpatch": {
 			requireEnvoyPatchPolicies: true,
 		},
+		"jsonpatch-with-jsonpath": {
+			requireEnvoyPatchPolicies: true,
+		},
+		"jsonpatch-add-op-empty-jsonpath": {
+			requireEnvoyPatchPolicies: true,
+		},
 		"jsonpatch-missing-resource": {
 			requireEnvoyPatchPolicies: true,
 		},
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index 2d2b75a3d..99caabc36 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -2070,7 +2070,8 @@ _Appears in:_
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
 | `op` | _[JSONPatchOperationType](#jsonpatchoperationtype)_ |  true  | Op is the type of operation to perform |
-| `path` | _string_ |  true  | Path is the location of the target document/field where the operation will be performed<br />Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. |
+| `path` | _string_ |  false  | Path is the location of the target document/field where the operation will be performed<br />Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. |
+| `jsonPath` | _string_ |  false  | JSONPath specifies the locations of the target document/field where the operation will be performed<br />Refer to https://datatracker.ietf.org/doc/rfc9535/ for more details. |
 | `from` | _string_ |  false  | From is the source location of the value to be copied or moved. Only valid<br />for move or copy operations<br />Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. |
 | `value` | _[JSON](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#json-v1-apiextensions-k8s-io)_ |  false  | Value is the new value of the path location. The value is only used by<br />the `add` and `replace` operations. |
 
diff --git a/site/content/en/latest/concepts/concepts_overview.md b/site/content/en/latest/concepts/concepts_overview.md
index 31838b520..9af9a3fff 100644
--- a/site/content/en/latest/concepts/concepts_overview.md
+++ b/site/content/en/latest/concepts/concepts_overview.md
@@ -10,13 +10,12 @@ There are several resources that play a part in enabling you to meet your Kubern
 
 There are several resources that play a part in enabling you to meet your Kubernetes ingress traffic handling needs. This page provides a brief overview of the resources you’ll be working with.
 
-# Overview
-
-## Kubernetes Gateway API Resources
+### Kubernetes Gateway API Resources
 - **GatewayClass:** Defines a class of Gateways with common configuration.
 - **Gateway:** Specifies how traffic can enter the cluster.
 - **Routes:** **HTTPRoute, GRPCRoute, TLSRoute, TCPRoute, UDPRoute:** Define routing rules for different types of traffic.
-## Envoy Gateway (EG) API Resources
+
+### Envoy Gateway (EG) API Resources
 - **EnvoyProxy:** Represents the deployment and configuration of the Envoy proxy within a Kubernetes cluster, managing its lifecycle and settings.
 - **EnvoyPatchPolicy, ClientTrafficPolicy, SecurityPolicy, BackendTrafficPolicy, EnvoyExtensionPolicy, BackendTLSPolicy:** Additional policies and configurations specific to Envoy Gateway.
 - **Backend:** A resource that makes routing to cluster-external backends easier and makes access to external processes via Unix Domain Sockets possible.
diff --git a/site/content/en/latest/tasks/security/backend-tls.md b/site/content/en/latest/tasks/security/backend-tls.md
index c30777f69..7fca5ef84 100644
--- a/site/content/en/latest/tasks/security/backend-tls.md
+++ b/site/content/en/latest/tasks/security/backend-tls.md
@@ -25,11 +25,30 @@ Create a root certificate and private key to sign certificates:
 openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout ca.key -out ca.crt
 ```
 
-Create a certificate and a private key for `www.example.com`:
+Create a certificate and a private key for `www.example.com`.
+
+First, create an openssl configuration file:
+
+```shell
+cat > openssl.conf  <<EOF
+[req]
+req_extensions = v3_req
+prompt = no
+
+[v3_req]
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage = serverAuth
+subjectAltName = @alt_names
+[alt_names]
+DNS.1 = www.example.com
+EOF
+```
+
+Then create a certificate using this openssl configuration file:
 
 ```shell
-openssl req -out www.example.com.csr -newkey rsa:2048 -nodes -keyout www.example.com.key -subj "/CN=www.example.com/O=example organization" -addext "subjectAltName = DNS:www.example.com"
-openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -set_serial 0 -in www.example.com.csr -out www.example.com.crt
+openssl req -out www.example.com.csr -newkey rsa:2048 -nodes -keyout www.example.com.key -subj "/CN=www.example.com/O=example organization"
+openssl x509 -req -days 365 -CA ca.crt -CAkey ca.key -set_serial 0 -in www.example.com.csr -out www.example.com.crt -extfile openssl.conf -extensions v3_req
 ```
 
 Note that the certificate must contain a DNS SAN for the relevant domain.
diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md
index 2d2b75a3d..99caabc36 100644
--- a/site/content/zh/latest/api/extension_types.md
+++ b/site/content/zh/latest/api/extension_types.md
@@ -2070,7 +2070,8 @@ _Appears in:_
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
 | `op` | _[JSONPatchOperationType](#jsonpatchoperationtype)_ |  true  | Op is the type of operation to perform |
-| `path` | _string_ |  true  | Path is the location of the target document/field where the operation will be performed<br />Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. |
+| `path` | _string_ |  false  | Path is the location of the target document/field where the operation will be performed<br />Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. |
+| `jsonPath` | _string_ |  false  | JSONPath specifies the locations of the target document/field where the operation will be performed<br />Refer to https://datatracker.ietf.org/doc/rfc9535/ for more details. |
 | `from` | _string_ |  false  | From is the source location of the value to be copied or moved. Only valid<br />for move or copy operations<br />Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. |
 | `value` | _[JSON](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#json-v1-apiextensions-k8s-io)_ |  false  | Value is the new value of the path location. The value is only used by<br />the `add` and `replace` operations. |