@@ -87,7 +87,18 @@ type MeshTLSConfig struct {
87
87
}
88
88
89
89
type MeshHTTPConfig struct {
90
- SanitizeXForwardedClientCert bool `json:"sanitizeXForwardedClientCert"`
90
+ SanitizeXForwardedClientCert bool `json:"sanitizeXForwardedClientCert,omitempty"`
91
+ // Incoming configures settings for incoming HTTP traffic to mesh proxies.
92
+ Incoming * MeshDirectionalHTTPConfig `json:"incoming,omitempty"`
93
+ // There is not currently an outgoing MeshDirectionalHTTPConfig, as
94
+ // the only required config for either direction at present is inbound
95
+ // request normalization.
96
+ }
97
+
98
+ // MeshDirectionalHTTPConfig holds mesh configuration specific to HTTP
99
+ // requests for a given traffic direction.
100
+ type MeshDirectionalHTTPConfig struct {
101
+ RequestNormalization * RequestNormalizationMeshConfig `json:"requestNormalization,omitempty"`
91
102
}
92
103
93
104
type PeeringMeshConfig struct {
@@ -117,6 +128,61 @@ type MeshDirectionalTLSConfig struct {
117
128
CipherSuites []string `json:"cipherSuites,omitempty"`
118
129
}
119
130
131
+ // RequestNormalizationMeshConfig contains options pertaining to the
132
+ // normalization of HTTP requests processed by mesh proxies.
133
+ type RequestNormalizationMeshConfig struct {
134
+ // InsecureDisablePathNormalization sets the value of the \`normalize_path\` option in the Envoy listener's
135
+ // `HttpConnectionManager`. The default value is \`false\`. When set to \`true\` in Consul, \`normalize_path\` is
136
+ // set to \`false\` for the Envoy proxy. This parameter disables the normalization of request URL paths according to
137
+ // RFC 3986, conversion of \`\\\` to \`/\`, and decoding non-reserved %-encoded characters. When using L7 intentions
138
+ // with path match rules, we recommend enabling path normalization in order to avoid match rule circumvention with
139
+ // non-normalized path values.
140
+ InsecureDisablePathNormalization bool `json:"insecureDisablePathNormalization,omitempty"`
141
+ // MergeSlashes sets the value of the \`merge_slashes\` option in the Envoy listener's \`HttpConnectionManager\`.
142
+ // The default value is \`false\`. This option controls the normalization of request URL paths by merging
143
+ // consecutive \`/\` characters. This normalization is not part of RFC 3986. When using L7 intentions with path
144
+ // match rules, we recommend enabling this setting to avoid match rule circumvention through non-normalized path
145
+ // values, unless legitimate service traffic depends on allowing for repeat \`/\` characters, or upstream services
146
+ // are configured to differentiate between single and multiple slashes.
147
+ MergeSlashes bool `json:"mergeSlashes,omitempty"`
148
+ // PathWithEscapedSlashesAction sets the value of the \`path_with_escaped_slashes_action\` option in the Envoy
149
+ // listener's \`HttpConnectionManager\`. The default value of this option is empty, which is equivalent to
150
+ // \`IMPLEMENTATION_SPECIFIC_DEFAULT\`. This parameter controls the action taken in response to request URL paths
151
+ // with escaped slashes in the path. When using L7 intentions with path match rules, we recommend enabling this
152
+ // setting to avoid match rule circumvention through non-normalized path values, unless legitimate service traffic
153
+ // depends on allowing for escaped \`/\` or \`\\\` characters, or upstream services are configured to differentiate
154
+ // between escaped and unescaped slashes. Refer to the Envoy documentation for more information on available
155
+ // options.
156
+ PathWithEscapedSlashesAction string `json:"pathWithEscapedSlashesAction,omitempty"`
157
+ // HeadersWithUnderscoresAction sets the value of the \`headers_with_underscores_action\` option in the Envoy
158
+ // listener's \`HttpConnectionManager\` under \`common_http_protocol_options\`. The default value of this option is
159
+ // empty, which is equivalent to \`ALLOW\`. Refer to the Envoy documentation for more information on available
160
+ // options.
161
+ HeadersWithUnderscoresAction string `json:"headersWithUnderscoresAction,omitempty"`
162
+ }
163
+
164
+ // PathWithEscapedSlashesAction is an enum that defines the action to take when
165
+ // a request path contains escaped slashes. It mirrors exactly the set of options
166
+ // in Envoy's UriPathNormalizationOptions.PathWithEscapedSlashesAction enum.
167
+ // See github.com/envoyproxy/go-control-plane envoy_http_v3.HttpConnectionManager_PathWithEscapedSlashesAction.
168
+ const (
169
+ PathWithEscapedSlashesActionDefault = "IMPLEMENTATION_SPECIFIC_DEFAULT"
170
+ PathWithEscapedSlashesActionKeep = "KEEP_UNCHANGED"
171
+ PathWithEscapedSlashesActionReject = "REJECT_REQUEST"
172
+ PathWithEscapedSlashesActionUnescapeAndRedirect = "UNESCAPE_AND_REDIRECT"
173
+ PathWithEscapedSlashesActionUnescapeAndForward = "UNESCAPE_AND_FORWARD"
174
+ )
175
+
176
+ // HeadersWithUnderscoresAction is an enum that defines the action to take when
177
+ // a request contains headers with underscores. It mirrors exactly the set of
178
+ // options in Envoy's HttpProtocolOptions.HeadersWithUnderscoresAction enum.
179
+ // See github.com/envoyproxy/go-control-plane envoy_core_v3.HttpProtocolOptions_HeadersWithUnderscoresAction.
180
+ const (
181
+ HeadersWithUnderscoresActionAllow = "ALLOW"
182
+ HeadersWithUnderscoresActionRejectRequest = "REJECT_REQUEST"
183
+ HeadersWithUnderscoresActionDropHeader = "DROP_HEADER"
184
+ )
185
+
120
186
func (in * TransparentProxyMeshConfig ) toConsul () capi.TransparentProxyMeshConfig {
121
187
return capi.TransparentProxyMeshConfig {MeshDestinationsOnly : in .MeshDestinationsOnly }
122
188
}
@@ -227,6 +293,11 @@ func (in *Mesh) Validate(consulMeta common.ConsulMeta) error {
227
293
228
294
errs = append (errs , in .Spec .TLS .validate (path .Child ("tls" ))... )
229
295
errs = append (errs , in .Spec .Peering .validate (path .Child ("peering" ), consulMeta .PartitionsEnabled , consulMeta .Partition )... )
296
+ if in .Spec .HTTP != nil &&
297
+ in .Spec .HTTP .Incoming != nil &&
298
+ in .Spec .HTTP .Incoming .RequestNormalization != nil {
299
+ errs = append (errs , in .Spec .HTTP .Incoming .RequestNormalization .validate (path .Child ("http" , "incoming" , "requestNormalization" ))... )
300
+ }
230
301
231
302
if len (errs ) > 0 {
232
303
return apierrors .NewInvalid (
@@ -252,6 +323,28 @@ func (in *MeshHTTPConfig) toConsul() *capi.MeshHTTPConfig {
252
323
}
253
324
return & capi.MeshHTTPConfig {
254
325
SanitizeXForwardedClientCert : in .SanitizeXForwardedClientCert ,
326
+ Incoming : in .Incoming .toConsul (),
327
+ }
328
+ }
329
+
330
+ func (in * MeshDirectionalHTTPConfig ) toConsul () * capi.MeshDirectionalHTTPConfig {
331
+ if in == nil {
332
+ return nil
333
+ }
334
+ return & capi.MeshDirectionalHTTPConfig {
335
+ RequestNormalization : in .RequestNormalization .toConsul (),
336
+ }
337
+ }
338
+
339
+ func (in * RequestNormalizationMeshConfig ) toConsul () * capi.RequestNormalizationMeshConfig {
340
+ if in == nil {
341
+ return nil
342
+ }
343
+ return & capi.RequestNormalizationMeshConfig {
344
+ InsecureDisablePathNormalization : in .InsecureDisablePathNormalization ,
345
+ MergeSlashes : in .MergeSlashes ,
346
+ PathWithEscapedSlashesAction : in .PathWithEscapedSlashesAction ,
347
+ HeadersWithUnderscoresAction : in .HeadersWithUnderscoresAction ,
255
348
}
256
349
}
257
350
@@ -316,6 +409,36 @@ func (in *PeeringMeshConfig) validate(path *field.Path, partitionsEnabled bool,
316
409
return errs
317
410
}
318
411
412
+ func (in * RequestNormalizationMeshConfig ) validate (path * field.Path ) field.ErrorList {
413
+ if in == nil {
414
+ return nil
415
+ }
416
+
417
+ var errs field.ErrorList
418
+ pathWithEscapedSlashesActions := []string {
419
+ PathWithEscapedSlashesActionDefault ,
420
+ PathWithEscapedSlashesActionKeep ,
421
+ PathWithEscapedSlashesActionReject ,
422
+ PathWithEscapedSlashesActionUnescapeAndRedirect ,
423
+ PathWithEscapedSlashesActionUnescapeAndForward ,
424
+ "" ,
425
+ }
426
+ headersWithUnderscoresActions := []string {
427
+ HeadersWithUnderscoresActionAllow ,
428
+ HeadersWithUnderscoresActionRejectRequest ,
429
+ HeadersWithUnderscoresActionDropHeader ,
430
+ "" ,
431
+ }
432
+
433
+ if ! sliceContains (pathWithEscapedSlashesActions , in .PathWithEscapedSlashesAction ) {
434
+ errs = append (errs , field .Invalid (path .Child ("pathWithEscapedSlashesAction" ), in .PathWithEscapedSlashesAction , notInSliceMessage (pathWithEscapedSlashesActions )))
435
+ }
436
+ if ! sliceContains (headersWithUnderscoresActions , in .HeadersWithUnderscoresAction ) {
437
+ errs = append (errs , field .Invalid (path .Child ("headersWithUnderscoresAction" ), in .HeadersWithUnderscoresAction , notInSliceMessage (headersWithUnderscoresActions )))
438
+ }
439
+ return errs
440
+ }
441
+
319
442
// DefaultNamespaceFields has no behaviour here as meshes have no namespace specific fields.
320
443
func (in * Mesh ) DefaultNamespaceFields (_ common.ConsulMeta ) {
321
444
}
0 commit comments