Skip to content

Commit fc42045

Browse files
sboulkouralbsga4
andcommitted
feat(api): add host normalization to ClientTrafficPolicy
Add a new `host` section to `ClientTrafficPolicy` to normalize the Host/Authority header on the listener, parallel to the existing `path` section: - `host.stripTrailingHostDot` maps to Envoy `strip_trailing_host_dot`, so requests with `Host: example.com.` match routes for `example.com` (NGINX-parity behavior). - `host.stripPortMode` (`Any`/`Matching`) maps to Envoy `strip_any_host_port` / `strip_matching_host_port`. Both were previously only achievable via fragile EnvoyPatchPolicy. This change is split out of #8825 per maintainer review, which scoped that PR to `maxRequestHeaderBytes`. Fixes #8832 Co-authored-by: albsga4 <asalvador@newrelic.com> Signed-off-by: Salim Boulkour <salim.boulkour@algolia.com>
1 parent 0edf51a commit fc42045

26 files changed

Lines changed: 1254 additions & 0 deletions

api/v1alpha1/clienttrafficpolicy_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ type ClientTrafficPolicySpec struct {
7979
//
8080
// +optional
8181
Path *PathSettings `json:"path,omitempty"`
82+
// Host enables managing how the Host/Authority header set by clients can be normalized.
83+
//
84+
// +optional
85+
Host *HostSettings `json:"host,omitempty"`
8286
// HeaderSettings provides configuration for header management.
8387
//
8488
// +optional

api/v1alpha1/hostsettings_types.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright Envoy Gateway Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
// The full text of the Apache license is available in the LICENSE file at
4+
// the root of the repo.
5+
6+
package v1alpha1
7+
8+
// StripPortMode determines how the port is stripped from the Host/Authority header
9+
// before route matching.
10+
// +kubebuilder:validation:Enum=Any;Matching
11+
type StripPortMode string
12+
13+
const (
14+
// StripPortModeAny strips the port from the Host/Authority header unconditionally.
15+
StripPortModeAny StripPortMode = "Any"
16+
// StripPortModeMatching strips the port from the Host/Authority header only when it
17+
// matches the listener's port.
18+
StripPortModeMatching StripPortMode = "Matching"
19+
)
20+
21+
// HostSettings provides settings that manage how the incoming Host/Authority header
22+
// set by clients is normalized.
23+
type HostSettings struct {
24+
// StripPortMode determines how the port is stripped from the Host/Authority header
25+
// before route matching.
26+
// "Any" strips the port unconditionally, "Matching" strips it only when it matches
27+
// the listener's port.
28+
// If not set, no port stripping is performed (Envoy default).
29+
//
30+
// +optional
31+
StripPortMode *StripPortMode `json:"stripPortMode,omitempty"`
32+
// StripTrailingHostDot determines if the trailing dot of the host should be removed
33+
// from the Host/Authority header before any processing of the request.
34+
// This affects the upstream host header as well. Without this option, incoming requests
35+
// with host "example.com." will not match routes with domains set to "example.com".
36+
// When the host includes a port (for example "example.com.:443"), only the trailing dot
37+
// from the host section is stripped, leaving the port as-is ("example.com:443").
38+
// Defaults to false.
39+
//
40+
// +optional
41+
StripTrailingHostDot *bool `json:"stripTrailingHostDot,omitempty"`
42+
}

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,32 @@ spec:
808808
required:
809809
- path
810810
type: object
811+
host:
812+
description: Host enables managing how the Host/Authority header set
813+
by clients can be normalized.
814+
properties:
815+
stripPortMode:
816+
description: |-
817+
StripPortMode determines how the port is stripped from the Host/Authority header
818+
before route matching.
819+
"Any" strips the port unconditionally, "Matching" strips it only when it matches
820+
the listener's port.
821+
If not set, no port stripping is performed (Envoy default).
822+
enum:
823+
- Any
824+
- Matching
825+
type: string
826+
stripTrailingHostDot:
827+
description: |-
828+
StripTrailingHostDot determines if the trailing dot of the host should be removed
829+
from the Host/Authority header before any processing of the request.
830+
This affects the upstream host header as well. Without this option, incoming requests
831+
with host "example.com." will not match routes with domains set to "example.com".
832+
When the host includes a port (for example "example.com.:443"), only the trailing dot
833+
from the host section is stripped, leaving the port as-is ("example.com:443").
834+
Defaults to false.
835+
type: boolean
836+
type: object
811837
http1:
812838
description: HTTP1 provides HTTP/1 configuration on the listener.
813839
properties:

charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,32 @@ spec:
807807
required:
808808
- path
809809
type: object
810+
host:
811+
description: Host enables managing how the Host/Authority header set
812+
by clients can be normalized.
813+
properties:
814+
stripPortMode:
815+
description: |-
816+
StripPortMode determines how the port is stripped from the Host/Authority header
817+
before route matching.
818+
"Any" strips the port unconditionally, "Matching" strips it only when it matches
819+
the listener's port.
820+
If not set, no port stripping is performed (Envoy default).
821+
enum:
822+
- Any
823+
- Matching
824+
type: string
825+
stripTrailingHostDot:
826+
description: |-
827+
StripTrailingHostDot determines if the trailing dot of the host should be removed
828+
from the Host/Authority header before any processing of the request.
829+
This affects the upstream host header as well. Without this option, incoming requests
830+
with host "example.com." will not match routes with domains set to "example.com".
831+
When the host includes a port (for example "example.com.:443"), only the trailing dot
832+
from the host section is stripped, leaving the port as-is ("example.com:443").
833+
Defaults to false.
834+
type: boolean
835+
type: object
810836
http1:
811837
description: HTTP1 provides HTTP/1 configuration on the listener.
812838
properties:

internal/gatewayapi/clienttrafficpolicy.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,9 @@ func (t *Translator) translateClientTrafficPolicyForListener(
542542
// Translate Path Settings
543543
translatePathSettings(policy.Spec.Path, httpIR)
544544

545+
// Translate Host Settings
546+
translateHostSettings(policy.Spec.Host, httpIR)
547+
545548
// Translate HTTP1 Settings
546549
if err = translateHTTP1Settings(policy.Spec.HTTP1, connection, httpIR); err != nil {
547550
err = perr.WithMessage(err, "HTTP1")
@@ -684,6 +687,21 @@ func translatePathSettings(pathSettings *egv1a1.PathSettings, httpIR *ir.HTTPLis
684687
}
685688
}
686689

690+
func translateHostSettings(hostSettings *egv1a1.HostSettings, httpIR *ir.HTTPListener) {
691+
if hostSettings == nil {
692+
return
693+
}
694+
if hostSettings.StripPortMode == nil && hostSettings.StripTrailingHostDot == nil {
695+
return
696+
}
697+
httpIR.Host = &ir.HostSettings{
698+
StripTrailingHostDot: ptr.Deref(hostSettings.StripTrailingHostDot, false),
699+
}
700+
if hostSettings.StripPortMode != nil {
701+
httpIR.Host.StripPortMode = ir.StripPortMode(*hostSettings.StripPortMode)
702+
}
703+
}
704+
687705
func buildClientTimeout(clientTimeout *egv1a1.ClientTimeout) (*ir.ClientTimeout, error) {
688706
// Return early if not set
689707
if clientTimeout == nil {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
clientTrafficPolicies:
2+
- apiVersion: gateway.envoyproxy.io/v1alpha1
3+
kind: ClientTrafficPolicy
4+
metadata:
5+
namespace: envoy-gateway
6+
name: target-gateway-1
7+
spec:
8+
host: {}
9+
targetRef:
10+
group: gateway.networking.k8s.io
11+
kind: Gateway
12+
name: gateway-1
13+
gateways:
14+
- apiVersion: gateway.networking.k8s.io/v1
15+
kind: Gateway
16+
metadata:
17+
namespace: envoy-gateway
18+
name: gateway-1
19+
spec:
20+
gatewayClassName: envoy-gateway-class
21+
listeners:
22+
- name: http-1
23+
protocol: HTTP
24+
port: 80
25+
allowedRoutes:
26+
namespaces:
27+
from: Same
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
clientTrafficPolicies:
2+
- apiVersion: gateway.envoyproxy.io/v1alpha1
3+
kind: ClientTrafficPolicy
4+
metadata:
5+
name: target-gateway-1
6+
namespace: envoy-gateway
7+
spec:
8+
host: {}
9+
targetRef:
10+
group: gateway.networking.k8s.io
11+
kind: Gateway
12+
name: gateway-1
13+
status:
14+
ancestors:
15+
- ancestorRef:
16+
group: gateway.networking.k8s.io
17+
kind: Gateway
18+
name: gateway-1
19+
namespace: envoy-gateway
20+
conditions:
21+
- lastTransitionTime: null
22+
message: Policy has been accepted.
23+
reason: Accepted
24+
status: "True"
25+
type: Accepted
26+
- lastTransitionTime: null
27+
message: spec.targetRef is deprecated, use spec.targetRefs instead
28+
reason: DeprecatedField
29+
status: "True"
30+
type: Warning
31+
controllerName: gateway.envoyproxy.io/gatewayclass-controller
32+
gateways:
33+
- apiVersion: gateway.networking.k8s.io/v1
34+
kind: Gateway
35+
metadata:
36+
name: gateway-1
37+
namespace: envoy-gateway
38+
spec:
39+
gatewayClassName: envoy-gateway-class
40+
listeners:
41+
- allowedRoutes:
42+
namespaces:
43+
from: Same
44+
name: http-1
45+
port: 80
46+
protocol: HTTP
47+
status:
48+
listeners:
49+
- attachedRoutes: 0
50+
conditions:
51+
- lastTransitionTime: null
52+
message: Sending translated listener configuration to the data plane
53+
reason: Programmed
54+
status: "True"
55+
type: Programmed
56+
- lastTransitionTime: null
57+
message: Listener has been successfully translated
58+
reason: Accepted
59+
status: "True"
60+
type: Accepted
61+
- lastTransitionTime: null
62+
message: Listener references have been resolved
63+
reason: ResolvedRefs
64+
status: "True"
65+
type: ResolvedRefs
66+
name: http-1
67+
supportedKinds:
68+
- group: gateway.networking.k8s.io
69+
kind: HTTPRoute
70+
- group: gateway.networking.k8s.io
71+
kind: GRPCRoute
72+
infraIR:
73+
envoy-gateway/gateway-1:
74+
proxy:
75+
listeners:
76+
- name: envoy-gateway/gateway-1/http-1
77+
ports:
78+
- containerPort: 10080
79+
name: http-80
80+
protocol: HTTP
81+
servicePort: 80
82+
metadata:
83+
labels:
84+
gateway.envoyproxy.io/owning-gateway-name: gateway-1
85+
gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
86+
ownerReference:
87+
kind: GatewayClass
88+
name: envoy-gateway-class
89+
name: envoy-gateway/gateway-1
90+
namespace: envoy-gateway-system
91+
xdsIR:
92+
envoy-gateway/gateway-1:
93+
accessLog:
94+
json:
95+
- path: /dev/stdout
96+
globalResources:
97+
proxyServiceCluster:
98+
metadata:
99+
kind: Service
100+
name: envoy-envoy-gateway-gateway-1-196ae069
101+
namespace: envoy-gateway-system
102+
sectionName: "8080"
103+
name: envoy-gateway/gateway-1
104+
settings:
105+
- addressType: IP
106+
endpoints:
107+
- host: 7.6.5.4
108+
port: 8080
109+
zone: zone1
110+
metadata:
111+
kind: Service
112+
name: envoy-envoy-gateway-gateway-1-196ae069
113+
namespace: envoy-gateway-system
114+
sectionName: "8080"
115+
name: envoy-gateway/gateway-1
116+
protocol: TCP
117+
http:
118+
- address: 0.0.0.0
119+
externalPort: 80
120+
hostnames:
121+
- '*'
122+
metadata:
123+
kind: Gateway
124+
name: gateway-1
125+
namespace: envoy-gateway
126+
sectionName: http-1
127+
name: envoy-gateway/gateway-1/http-1
128+
path:
129+
escapedSlashesAction: UnescapeAndRedirect
130+
mergeSlashes: true
131+
port: 10080
132+
readyListener:
133+
address: 0.0.0.0
134+
ipFamily: IPv4
135+
path: /ready
136+
port: 19003
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
clientTrafficPolicies:
2+
- apiVersion: gateway.envoyproxy.io/v1alpha1
3+
kind: ClientTrafficPolicy
4+
metadata:
5+
namespace: envoy-gateway
6+
name: target-gateway-1
7+
spec:
8+
host:
9+
stripPortMode: Matching
10+
targetRef:
11+
group: gateway.networking.k8s.io
12+
kind: Gateway
13+
name: gateway-1
14+
gateways:
15+
- apiVersion: gateway.networking.k8s.io/v1
16+
kind: Gateway
17+
metadata:
18+
namespace: envoy-gateway
19+
name: gateway-1
20+
spec:
21+
gatewayClassName: envoy-gateway-class
22+
listeners:
23+
- name: http-1
24+
protocol: HTTP
25+
port: 80
26+
allowedRoutes:
27+
namespaces:
28+
from: Same

0 commit comments

Comments
 (0)