Skip to content

Commit a12f92f

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 39850aa commit a12f92f

25 files changed

Lines changed: 1253 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
@@ -816,6 +816,32 @@ spec:
816816
required:
817817
- path
818818
type: object
819+
host:
820+
description: Host enables managing how the Host/Authority header set
821+
by clients can be normalized.
822+
properties:
823+
stripPortMode:
824+
description: |-
825+
StripPortMode determines how the port is stripped from the Host/Authority header
826+
before route matching.
827+
"Any" strips the port unconditionally, "Matching" strips it only when it matches
828+
the listener's port.
829+
If not set, no port stripping is performed (Envoy default).
830+
enum:
831+
- Any
832+
- Matching
833+
type: string
834+
stripTrailingHostDot:
835+
description: |-
836+
StripTrailingHostDot determines if the trailing dot of the host should be removed
837+
from the Host/Authority header before any processing of the request.
838+
This affects the upstream host header as well. Without this option, incoming requests
839+
with host "example.com." will not match routes with domains set to "example.com".
840+
When the host includes a port (for example "example.com.:443"), only the trailing dot
841+
from the host section is stripped, leaving the port as-is ("example.com:443").
842+
Defaults to false.
843+
type: boolean
844+
type: object
819845
http1:
820846
description: HTTP1 provides HTTP/1 configuration on the listener.
821847
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
@@ -815,6 +815,32 @@ spec:
815815
required:
816816
- path
817817
type: object
818+
host:
819+
description: Host enables managing how the Host/Authority header set
820+
by clients can be normalized.
821+
properties:
822+
stripPortMode:
823+
description: |-
824+
StripPortMode determines how the port is stripped from the Host/Authority header
825+
before route matching.
826+
"Any" strips the port unconditionally, "Matching" strips it only when it matches
827+
the listener's port.
828+
If not set, no port stripping is performed (Envoy default).
829+
enum:
830+
- Any
831+
- Matching
832+
type: string
833+
stripTrailingHostDot:
834+
description: |-
835+
StripTrailingHostDot determines if the trailing dot of the host should be removed
836+
from the Host/Authority header before any processing of the request.
837+
This affects the upstream host header as well. Without this option, incoming requests
838+
with host "example.com." will not match routes with domains set to "example.com".
839+
When the host includes a port (for example "example.com.:443"), only the trailing dot
840+
from the host section is stripped, leaving the port as-is ("example.com:443").
841+
Defaults to false.
842+
type: boolean
843+
type: object
818844
http1:
819845
description: HTTP1 provides HTTP/1 configuration on the listener.
820846
properties:

internal/gatewayapi/clienttrafficpolicy.go

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

700+
// Translate Host Settings
701+
translateHostSettings(policy.Spec.Host, httpIR)
702+
700703
// Translate HTTP1 Settings
701704
if err = translateHTTP1Settings(policy.Spec.HTTP1, connection, httpIR); err != nil {
702705
err = perr.WithMessage(err, "HTTP1")
@@ -839,6 +842,21 @@ func translatePathSettings(pathSettings *egv1a1.PathSettings, httpIR *ir.HTTPLis
839842
}
840843
}
841844

845+
func translateHostSettings(hostSettings *egv1a1.HostSettings, httpIR *ir.HTTPListener) {
846+
if hostSettings == nil {
847+
return
848+
}
849+
if hostSettings.StripPortMode == nil && hostSettings.StripTrailingHostDot == nil {
850+
return
851+
}
852+
httpIR.Host = &ir.HostSettings{
853+
StripTrailingHostDot: ptr.Deref(hostSettings.StripTrailingHostDot, false),
854+
}
855+
if hostSettings.StripPortMode != nil {
856+
httpIR.Host.StripPortMode = ir.StripPortMode(*hostSettings.StripPortMode)
857+
}
858+
}
859+
842860
func buildClientTimeout(clientTimeout *egv1a1.ClientTimeout) (*ir.ClientTimeout, error) {
843861
// Return early if not set
844862
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)