Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
18faedc
API: host rewrite from path
zhaohuabing May 12, 2026
b274ae3
implementation
zhaohuabing Jun 8, 2026
4b0aebd
e2e tests
zhaohuabing Jun 8, 2026
6c1f212
update e2e test
zhaohuabing Jun 8, 2026
5d9bbb8
reject PathRegex host rewrite with DFP backend
zhaohuabing Jun 9, 2026
ddad677
address comment
zhaohuabing Jun 9, 2026
79982b7
update test files
zhaohuabing Jun 9, 2026
030d9d9
update test files
zhaohuabing Jun 9, 2026
27a62f7
update test files
zhaohuabing Jun 9, 2026
8fc1d63
update test files
zhaohuabing Jun 9, 2026
a497ca3
address comment
zhaohuabing Jun 9, 2026
40c156b
update
zhaohuabing Jun 9, 2026
c8e3c6c
Potential fix for pull request finding
zhaohuabing Jun 9, 2026
5a9ceb9
Potential fix for pull request finding
zhaohuabing Jun 9, 2026
8c1f054
address comment
zhaohuabing Jun 9, 2026
fe7118a
update
zhaohuabing Jun 9, 2026
71fc618
Merge branch 'main' into host-rewrite-from-path
zhaohuabing Jun 10, 2026
cbb19cb
Merge branch 'main' into host-rewrite-from-path
zhaohuabing Jun 15, 2026
d2d0c02
Merge branch 'main' into host-rewrite-from-path
zhaohuabing Jun 17, 2026
155e2cc
Merge remote-tracking branch 'upstream/main' into host-rewrite-from-path
zhaohuabing Jun 24, 2026
3fd95e2
Merge branch 'main' into host-rewrite-from-path
zhaohuabing Jul 21, 2026
8496b19
api,test: reject CR/LF/NUL in PathRegex hostname rewrite substitution
zhaohuabing Jul 24, 2026
021b5c6
fix gen
zhaohuabing Jul 24, 2026
ea63384
gatewayapi: reject PathRegex host rewrite with a dynamic resolver on …
zhaohuabing Jul 30, 2026
d9824e8
Merge branch 'main' into host-rewrite-from-path
zhaohuabing Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion api/v1alpha1/httproutefilter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ const (
// BackendHTTPHostnameModifier indicates that the Host header value would be replaced by the DNS name of the backend if it exists.
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-auto-host-rewrite
BackendHTTPHostnameModifier HTTPHostnameModifierType = "Backend"
// PathRegexHTTPHostnameModifier indicates that the Host header value would be rewritten by applying a regex
// match and substitution to the request path.
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-host-rewrite-path-regex
PathRegexHTTPHostnameModifier HTTPHostnameModifierType = "PathRegex"
)

type ReplaceRegexMatch struct {
Expand All @@ -139,6 +143,26 @@ type ReplaceRegexMatch struct {
Substitution string `json:"substitution"`
}

// HostnamePathRegexRewrite defines a hostname rewrite computed from the request path using regex.
type HostnamePathRegexRewrite struct {
// Pattern matches a regular expression against the value of the HTTP Path. The regex string must
// adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax.
// +kubebuilder:validation:MinLength=1
Pattern string `json:"pattern"`
// Substitution is an expression that replaces the matched portion. The expression may include numbered
// capture groups that adhere to syntax documented in https://github.com/google/re2/wiki/Syntax.
//
// The resulting value is used as the upstream Host header and should be constrained to a valid
// DNS hostname by using explicit regex capture groups in Pattern.
//
// The NUL, CR, and LF characters are not allowed: they are invalid in an HTTP header value and are
// rejected by the Envoy proto (well_known_regex HTTP_HEADER_VALUE), which would otherwise cause the
// generated configuration to be rejected by the data plane.
// +kubebuilder:validation:MinLength=1
Comment thread
zhaohuabing marked this conversation as resolved.
// +kubebuilder:validation:Pattern=`^[^\r\n\x00]*$`
Substitution string `json:"substitution"`
}

// +kubebuilder:validation:XValidation:rule="self.type == 'ReplaceRegexMatch' ? has(self.replaceRegexMatch) : !has(self.replaceRegexMatch)",message="If HTTPPathModifier type is ReplaceRegexMatch, replaceRegexMatch field needs to be set."
type HTTPPathModifier struct {
// +kubebuilder:validation:Enum=ReplaceRegexMatch
Expand Down Expand Up @@ -169,14 +193,28 @@ type HTTPPathModifier struct {

// +kubebuilder:validation:XValidation:message="header must be nil if the type is not Header",rule="!(has(self.header) && self.type != 'Header')"
// +kubebuilder:validation:XValidation:message="header must be specified for Header type",rule="!(!has(self.header) && self.type == 'Header')"
// +kubebuilder:validation:XValidation:message="pathRegex must be nil if the type is not PathRegex",rule="!(has(self.pathRegex) && self.type != 'PathRegex')"
// +kubebuilder:validation:XValidation:message="pathRegex must be specified for PathRegex type",rule="!(!has(self.pathRegex) && self.type == 'PathRegex')"
type HTTPHostnameModifier struct {
// +kubebuilder:validation:Enum=Header;Backend
// +kubebuilder:validation:Enum=Header;Backend;PathRegex
Comment thread
zhaohuabing marked this conversation as resolved.
// +kubebuilder:validation:Required
Type HTTPHostnameModifierType `json:"type"`

// Header is the name of the header whose value would be used to rewrite the Host header
// +optional
Header *string `json:"header,omitempty"`

// PathRegex defines a regex match and substitution applied to the request path to compute
// the rewritten Host header.
// For example, with:
// pathRegex:
// pattern: "^/tenant/([a-z0-9-]+)/.*"
// substitution: "\\1.example.internal"
// a request to "http://foo.bar.com/tenant/tenant1/api/v1" has its upstream Host header rewritten
// to "tenant1.example.internal" (the request path "/tenant/tenant1/api/v1" is preserved).
//
// +optional
PathRegex *HostnamePathRegexRewrite `json:"pathRegex,omitempty"`
}

// HTTPCredentialInjectionFilter defines the configuration to inject credentials into the request.
Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -424,12 +424,48 @@ spec:
description: Header is the name of the header whose value
would be used to rewrite the Host header
type: string
pathRegex:
description: |-
PathRegex defines a regex match and substitution applied to the request path to compute
the rewritten Host header.
For example, with:
pathRegex:
pattern: "^/tenant/([a-z0-9-]+)/.*"
substitution: "\\1.example.internal"
a request to "http://foo.bar.com/tenant/tenant1/api/v1" has its upstream Host header rewritten
to "tenant1.example.internal" (the request path "/tenant/tenant1/api/v1" is preserved).
properties:
pattern:
description: |-
Pattern matches a regular expression against the value of the HTTP Path. The regex string must
adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax.
minLength: 1
type: string
substitution:
description: |-
Substitution is an expression that replaces the matched portion. The expression may include numbered
capture groups that adhere to syntax documented in https://github.com/google/re2/wiki/Syntax.

The resulting value is used as the upstream Host header and should be constrained to a valid
DNS hostname by using explicit regex capture groups in Pattern.

The NUL, CR, and LF characters are not allowed: they are invalid in an HTTP header value and are
rejected by the Envoy proto (well_known_regex HTTP_HEADER_VALUE), which would otherwise cause the
generated configuration to be rejected by the data plane.
minLength: 1
pattern: ^[^\r\n\x00]*$
type: string
required:
- pattern
- substitution
type: object
type:
description: HTTPPathModifierType defines the type of Hostname
rewrite.
enum:
- Header
- Backend
- PathRegex
type: string
required:
- type
Expand All @@ -439,6 +475,10 @@ spec:
rule: '!(has(self.header) && self.type != ''Header'')'
- message: header must be specified for Header type
rule: '!(!has(self.header) && self.type == ''Header'')'
- message: pathRegex must be nil if the type is not PathRegex
rule: '!(has(self.pathRegex) && self.type != ''PathRegex'')'
- message: pathRegex must be specified for PathRegex type
rule: '!(!has(self.pathRegex) && self.type == ''PathRegex'')'
path:
description: Path defines a path rewrite.
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,48 @@ spec:
description: Header is the name of the header whose value
would be used to rewrite the Host header
type: string
pathRegex:
description: |-
PathRegex defines a regex match and substitution applied to the request path to compute
the rewritten Host header.
For example, with:
pathRegex:
pattern: "^/tenant/([a-z0-9-]+)/.*"
substitution: "\\1.example.internal"
a request to "http://foo.bar.com/tenant/tenant1/api/v1" has its upstream Host header rewritten
to "tenant1.example.internal" (the request path "/tenant/tenant1/api/v1" is preserved).
properties:
pattern:
description: |-
Pattern matches a regular expression against the value of the HTTP Path. The regex string must
adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax.
minLength: 1
type: string
substitution:
description: |-
Substitution is an expression that replaces the matched portion. The expression may include numbered
capture groups that adhere to syntax documented in https://github.com/google/re2/wiki/Syntax.

The resulting value is used as the upstream Host header and should be constrained to a valid
DNS hostname by using explicit regex capture groups in Pattern.

The NUL, CR, and LF characters are not allowed: they are invalid in an HTTP header value and are
rejected by the Envoy proto (well_known_regex HTTP_HEADER_VALUE), which would otherwise cause the
generated configuration to be rejected by the data plane.
minLength: 1
pattern: ^[^\r\n\x00]*$
type: string
required:
- pattern
- substitution
type: object
type:
description: HTTPPathModifierType defines the type of Hostname
rewrite.
enum:
- Header
- Backend
- PathRegex
type: string
required:
- type
Expand All @@ -438,6 +474,10 @@ spec:
rule: '!(has(self.header) && self.type != ''Header'')'
- message: header must be specified for Header type
rule: '!(!has(self.header) && self.type == ''Header'')'
- message: pathRegex must be nil if the type is not PathRegex
rule: '!(has(self.pathRegex) && self.type != ''PathRegex'')'
- message: pathRegex must be specified for PathRegex type
rule: '!(!has(self.pathRegex) && self.type == ''PathRegex'')'
path:
description: Path defines a path rewrite.
properties:
Expand Down
27 changes: 25 additions & 2 deletions internal/gatewayapi/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ func hasMultipleCoreRewrites(rewrite *gwapiv1.HTTPURLRewriteFilter, contextRewri
// Checks if the context and the rewrite both contain a envoy-gateway extended HTTP URL rewrite
func hasMultipleExtensionRewrites(rewrite *egv1a1.HTTPURLRewriteFilter, contextRewrite *ir.URLRewrite) bool {
contextHasExtensionRewrites := (contextRewrite.Path != nil && contextRewrite.Path.RegexMatchReplace != nil) ||
(contextRewrite.Host != nil && (contextRewrite.Host.Header != nil || contextRewrite.Host.Backend != nil))
(contextRewrite.Host != nil && (contextRewrite.Host.Header != nil || contextRewrite.Host.Backend != nil ||
contextRewrite.Host.PathRegex != nil))

return contextHasExtensionRewrites && (rewrite.Hostname != nil || rewrite.Path != nil)
}
Expand All @@ -259,7 +260,7 @@ func hasMultipleExtensionRewrites(rewrite *egv1a1.HTTPURLRewriteFilter, contextR
func hasConflictingCoreAndExtensionRewrites(rewrite *gwapiv1.HTTPURLRewriteFilter, contextRewrite *ir.URLRewrite) bool {
contextHasExtensionPathRewrites := contextRewrite.Path != nil && contextRewrite.Path.RegexMatchReplace != nil
contextHasExtensionHostRewrites := contextRewrite.Host != nil && (contextRewrite.Host.Header != nil ||
contextRewrite.Host.Backend != nil)
contextRewrite.Host.Backend != nil || contextRewrite.Host.PathRegex != nil)
return (rewrite.Hostname != nil && contextHasExtensionHostRewrites) || (rewrite.Path != nil && contextHasExtensionPathRewrites)
}

Expand Down Expand Up @@ -902,6 +903,28 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec
hm = &ir.HTTPHostModifier{
Backend: new(true),
}
case egv1a1.PathRegexHTTPHostnameModifier:
if hrf.Spec.URLRewrite.Hostname.PathRegex == nil ||
hrf.Spec.URLRewrite.Hostname.PathRegex.Pattern == "" ||
hrf.Spec.URLRewrite.Hostname.PathRegex.Substitution == "" {
return status.NewRouteStatusError(
errors.New("PathRegex Pattern and Substitution must be set when rewrite hostname type is \"PathRegex\""),
gwapiv1.RouteReasonUnsupportedValue,
).WithType(gwapiv1.RouteConditionAccepted)
} else if _, err := regexp.Compile(hrf.Spec.URLRewrite.Hostname.PathRegex.Pattern); err != nil {
// Avoid envoy NACKs due to invalid regex.
// Go's regexp syntax is RE2: https://pkg.go.dev/regexp/syntax
return status.NewRouteStatusError(
errors.New("PathRegex must be a valid RE2 regular expression"),
gwapiv1.RouteReasonUnsupportedValue,
).WithType(gwapiv1.RouteConditionAccepted)
}
hm = &ir.HTTPHostModifier{
PathRegex: &ir.RegexMatchReplace{
Pattern: hrf.Spec.URLRewrite.Hostname.PathRegex.Pattern,
Comment thread
zhaohuabing marked this conversation as resolved.
Substitution: hrf.Spec.URLRewrite.Hostname.PathRegex.Substitution,
},
}
}

if filterContext.URLRewrite != nil {
Expand Down
Loading
Loading