Repository: envoyproxy/gateway
Pull request #4030 (2024-08-12 05:01:55)

build(deps): bump github/codeql-action from 3.25.15 to 3.26.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.15 to 3.26.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action's changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a href="https://github.com/github/codeql-action/releases">releases page</a> for the relevant changes to the CodeQL CLI and language packs.</p>
<p>Note that the only difference between <code>v2</code> and <code>v3</code> of the CodeQL Action is the node version they support, with <code>v3</code> running on node 20 while we continue to release <code>v2</code> to support running on node 16. For example <code>3.22.11</code> was the first <code>v3</code> release and is functionally identical to <code>2.22.11</code>. This approach ensures an easy way to track exactly which features are included in different versions, indicated by the minor and patch version numbers.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>3.26.0 - 06 Aug 2024</h2>
<ul>
<li><em>Deprecation:</em> Swift analysis on Ubuntu runner images is no longer supported. Please migrate to a macOS runner if this affects you. <a href="https://redirect.github.com/github/codeql-action/pull/2403">#2403</a></li>
<li>Bump the minimum CodeQL bundle version to 2.13.5. <a href="https://redirect.github.com/github/codeql-action/pull/2408">#2408</a></li>
</ul>
<h2>3.25.15 - 26 Jul 2024</h2>
<ul>
<li>Update default CodeQL bundle version to 2.18.1. <a href="https://redirect.github.com/github/codeql-action/pull/2385">#2385</a></li>
</ul>
<h2>3.25.14 - 25 Jul 2024</h2>
<ul>
<li>Experimental: add a new <code>start-proxy</code> action which starts the same HTTP proxy as used by <a href="https://github.com/github/dependabot-action"><code>github/dependabot-action</code></a>. Do not use this in production as it is part of an internal experiment and subject to change at any time. <a href="https://redirect.github.com/github/codeql-action/pull/2376">#2376</a></li>
</ul>
<h2>3.25.13 - 19 Jul 2024</h2>
<ul>
<li>Add <code>codeql-version</code> to outputs. <a href="https://redirect.github.com/github/codeql-action/pull/2368">#2368</a></li>
<li>Add a deprecation warning for customers using CodeQL version 2.13.4 and earlier. These versions of CodeQL were discontinued on 9 July 2024 alongside GitHub Enterprise Server 3.9, and will be unsupported by CodeQL Action versions 3.26.0 and later and versions 2.26.0 and later. <a href="https://redirect.github.com/github/codeql-action/pull/2375">#2375</a>
<ul>
<li>If you are using one of these versions, please update to CodeQL CLI version 2.13.5 or later. For instance, if you have specified a custom version of the CLI using the 'tools' input to the 'init' Action, you can remove this input to use the default version.</li>
<li>Alternatively, if you want to continue using a version of the CodeQL CLI between 2.12.6 and 2.13.4, you can replace <code>github/codeql-action/*@v3</code> by <code>github/codeql-action/*@v3.25.13</code> and <code>github/codeql-action/*@v2</code> by <code>github/codeql-action/*@v2.25.13</code> in your code scanning workflow to ensure you continue using this version of the CodeQL Action.</li>
</ul>
</li>
</ul>
<h2>3.25.12 - 12 Jul 2024</h2>
<ul>
<li>Improve the reliability and performance of analyzing code when analyzing a compiled language with the <code>autobuild</code> <a href="https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages#codeql-build-modes">build mode</a> on GitHub Enterprise Server. This feature is already available to GitHub.com users. <a href="https://redirect.github.com/github/codeql-action/pull/2353">#2353</a></li>
<li>Update default CodeQL bundle version to 2.18.0. <a href="https://redirect.github.com/github/codeql-action/pull/2364">#2364</a></li>
</ul>
<h2>3.25.11 - 28 Jun 2024</h2>
<ul>
<li>Avoid failing the workflow run if there is an error while uploading debug artifacts. <a href="https://redirect.github.com/github/codeql-action/pull/2349">#2349</a></li>
<li>Update default CodeQL bundle version to 2.17.6. <a href="https://redirect.github.com/github/codeql-action/pull/2352">#2352</a></li>
</ul>
<h2>3.25.10 - 13 Jun 2024</h2>
<ul>
<li>Update default CodeQL bundle version to 2.17.5. <a href="https://redirect.github.com/github/codeql-action/pull/2327">#2327</a></li>
</ul>
<h2>3.25.9 - 12 Jun 2024</h2>
<ul>
<li>Avoid failing database creation if the database folder already exists and contains some unexpected files. Requires CodeQL 2.18.0 or higher. <a href="https://redirect.github.com/github/codeql-action/pull/2330">#2330</a></li>
<li>The init Action will attempt to clean up the database cluster directory before creating a new database and at the end of the job. This will help to avoid issues where the database cluster directory is left in an inconsistent state. <a href="https://redirect.github.com/github/codeql-action/pull/2332">#2332</a></li>
</ul>
<h2>3.25.8 - 04 Jun 2024</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/github/codeql-action/commit/eb055d739abdc2e8de2e5f4ba1a8b246daa779aa"><code>eb055d7</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2410">#2410</a> from github/update-v3.26.0-c24926b73</li>
<li><a href="https://github.com/github/codeql-action/commit/3884d04c1155b714391ad663c28d71f332778d0c"><code>3884d04</code></a> Update changelog for v3.26.0</li>
<li><a href="https://github.com/github/codeql-action/commit/c24926b73412ecf51b27bc8e3cacb43a6b3f02ae"><code>c24926b</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2407">#2407</a> from github/dependabot/npm_and_yarn/npm-7954a73ad2</li>
<li><a href="https://github.com/github/codeql-action/commit/68ba39bacf27d8362ac9c671e45a970d776578f9"><code>68ba39b</code></a> Merge branch 'main' into dependabot/npm_and_yarn/npm-7954a73ad2</li>
<li><a href="https://github.com/github/codeql-action/commit/8dd17734677bad12f20a39b470d1bd45acbadc48"><code>8dd1773</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2408">#2408</a> from github/henrymercer/deprecate-codeql-2.13.4</li>
<li><a href="https://github.com/github/codeql-action/commit/441c9d90e44d79449d7f555d97a5f04f0970b927"><code>441c9d9</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2409">#2409</a> from github/henrymercer/fix-required-checks</li>
<li><a href="https://github.com/github/codeql-action/commit/f03da1345462bf7292a562681f136608588272c5"><code>f03da13</code></a> Exclude push-only unit tests job from required PR checks script</li>
<li><a href="https://github.com/github/codeql-action/commit/29a5cfc75d38b3893eb5b8f72652c57a9b5231be"><code>29a5cfc</code></a> Bump version to 3.26.0</li>
<li><a href="https://github.com/github/codeql-action/commit/9e440ad4c728db26c19f33cb713b9404dcc4d0ca"><code>9e440ad</code></a> Add changelog note</li>
<li><a href="https://github.com/github/codeql-action/commit/136f5a589b6f389231c51cb03d17bb60f22ccda6"><code>136f5a5</code></a> Add CodeQL v2.17.6 to default test versions</li>
<li>Additional commits viewable in <a href="https://github.com/github/codeql-action/compare/afb54ba388a7dca6ecae48f608c4ff05ff4cc77a...eb055d739abdc2e8de2e5f4ba1a8b246daa779aa">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3.25.15&new-version=3.26.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/4030?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.62%. 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 [(`a425105`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/a425105b5599e4520c50bae3498cf256be5d52fb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

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


```diff
@@            Coverage Diff             @@
##             main    #4030      +/-   ##
==========================================
+ Coverage   67.59%   67.62%   +0.03%     
==========================================
  Files         186      186              
  Lines       22757    22757              
==========================================
+ Hits        15383    15390       +7     
+ Misses       6268     6264       -4     
+ Partials     1106     1103       -3     
```

</details>

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

zirain: /retest

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/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/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/ir/xds.go b/internal/ir/xds.go
index f2807da48..41dd93b3b 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -1226,9 +1226,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
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index 5e3398a06..f0d5e2965 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
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 == "",
+				})
+			}
 		}
 	}
 


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/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/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/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/site/content/en/latest/install/migrating-to-envoy.md b/site/content/en/latest/install/migrating-to-envoy.md
new file mode 100644
index 000000000..470c759ab
--- /dev/null
+++ b/site/content/en/latest/install/migrating-to-envoy.md
@@ -0,0 +1,143 @@
+---
+title: Migrating from Ingress Resources
+---
+
+## Introduction
+
+Migrating from Ingress to Envoy Gateway involves converting existing Ingress resources into resources compatible with Envoy Gateway. The `ingress2gateway` tool simplifies this migration by transforming Ingress resources into Gateway API resources that Envoy Gateway can use. This guide will walk you through the prerequisites, installation of the `ingress2gateway` tool, and provide an example migration process.
+
+## Prerequisites
+
+Before you start the migration, ensure you have the following:
+
+1. **Envoy Gateway Installed**: You need Envoy Gateway set up in your Kubernetes cluster. Follow the [Envoy Gateway installation guide](../install) for details.
+2. **Kubernetes Cluster Access**: Ensure you have access to your Kubernetes cluster and necessary permissions to manage resources.
+3. **Installation of `ingress2gateway` Tool**: You need to install the `ingress2gateway` tool in your Kubernetes cluster and configure it accordingly. Follow the [ingress2gateway tool installation guide](https://github.com/kubernetes-sigs/ingress2gateway/blob/main/README.md#installation) for details.
+
+## Example Migration
+
+Here’s a step-by-step example of migrating from Ingress to Envoy Gateway using `ingress2gateway`:
+
+### 1. Install and Configure Envoy Gateway
+
+Ensure that Envoy Gateway is installed and running in your cluster. Follow the [official Envoy Gateway installation guide](../install) for setup instructions.
+
+### 2. Create a GatewayClass
+
+To ensure the generated HTTPRoutes are programmed correctly in the Envoy Gateway data plane, create a GatewayClass that links to the Envoy Gateway controller.
+
+Create a `GatewayClass` resource:
+
+```yaml
+apiVersion: gateway.networking.k8s.io/v1beta1
+kind: GatewayClass
+metadata:
+  name: envoy-gateway-class
+spec:
+  controllerName: gateway.envoyproxy.io/controller
+```
+
+Apply this resource:
+
+```sh
+kubectl apply -f gatewayclass.yaml
+```
+
+### 3. Install Ingress2gateway
+
+Ensure you have the Ingress2gateway package installed. If not, follow the package’s installation instructions.
+
+### 4. Run Ingress2gateway
+
+Use Ingress2gateway to read your existing Ingress resources and translate them into Gateway API resources.
+
+```sh
+./ingress2gateway print
+```
+
+This command will:
+1. Read your Kube config file to extract the cluster credentials and the current active namespace.
+2. Search for Ingress and provider-specific resources in that namespace.
+3. Convert them to Gateway API resources (Gateways and HTTPRoutes).
+
+#### Example Ingress Configuration
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: example-ingress
+  namespace: default
+  annotations:
+    nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+  rules:
+  - host: example.com
+    http:
+      paths:
+      - path: /foo
+        pathType: Prefix
+        backend:
+          service:
+            name: foo-service
+            port:
+              number: 80
+```
+
+### 5. Save the Output
+
+The command will output the equivalent Gateway API resources in YAML/JSON format to stdout. Save this output to a file for further use.
+
+```sh
+./ingress2gateway print > gateway-resources.yaml
+```
+
+### 6. Apply the Translated Resources
+
+Apply the translated Gateway API resources to your cluster.
+
+```sh
+kubectl apply -f gateway-resources.yaml
+```
+
+### 7. Create a Gateway Resource
+
+Create a `Gateway` resource specifying the `GatewayClass` created earlier and including the necessary listeners.
+
+```yaml
+apiVersion: gateway.networking.k8s.io/v1beta1
+kind: Gateway
+metadata:
+  name: example-gateway
+  namespace: default
+spec:
+  gatewayClassName: envoy-gateway-class
+  listeners:
+  - name: http
+    protocol: HTTP
+    port: 80
+    hostname: example.com
+```
+
+Apply this resource:
+
+```sh
+kubectl apply -f gateway.yaml
+```
+
+### 8. Validate the Migration
+
+Ensure the HTTPRoutes and Gateways are correctly set up and that traffic is being routed as expected. Validate the new configuration by checking the status of the Gateway and HTTPRoute resources.
+
+```sh
+kubectl get gateways
+kubectl get httproutes
+```
+
+### 9. Monitor and Troubleshoot
+
+Monitor the Envoy Gateway logs and metrics to ensure everything is functioning correctly. Troubleshoot any issues by reviewing the Gateway and HTTPRoute statuses and Envoy Gateway controller logs.
+
+## Summary
+
+By following this guide, users can effectively migrate their existing Ingress resources to Envoy Gateway using the Ingress2gateway package. Creating a GatewayClass and linking it to the Envoy Gateway controller ensures that the translated resources are properly programmed in the data plane, providing a seamless transition to the Envoy Gateway environment.
\ No newline at end of file