diff --git a/api/config_entry_intentions.go b/api/config_entry_intentions.go index 83639f0522c9..ca3b6d3c1967 100644 --- a/api/config_entry_intentions.go +++ b/api/config_entry_intentions.go @@ -60,11 +60,13 @@ type IntentionHTTPPermission struct { } type IntentionHTTPHeaderPermission struct { - Name string - Present bool `json:",omitempty"` - Exact string `json:",omitempty"` - Prefix string `json:",omitempty"` - Suffix string `json:",omitempty"` - Regex string `json:",omitempty"` - Invert bool `json:",omitempty"` + Name string + Present bool `json:",omitempty"` + Exact string `json:",omitempty"` + Prefix string `json:",omitempty"` + Suffix string `json:",omitempty"` + Contains string `json:",omitempty"` + Regex string `json:",omitempty"` + Invert bool `json:",omitempty"` + IgnoreCase bool `json:",omitempty" alias:"ignore_case"` } diff --git a/api/config_entry_mesh.go b/api/config_entry_mesh.go index 557aeb154dea..f3346121aeef 100644 --- a/api/config_entry_mesh.go +++ b/api/config_entry_mesh.go @@ -65,12 +65,53 @@ type MeshDirectionalTLSConfig struct { type MeshHTTPConfig struct { SanitizeXForwardedClientCert bool `alias:"sanitize_x_forwarded_client_cert"` + // Incoming configures settings for incoming HTTP traffic to mesh proxies. + Incoming *MeshDirectionalHTTPConfig `json:",omitempty"` +} + +// MeshDirectionalHTTPConfig holds mesh configuration specific to HTTP +// requests for a given traffic direction. +type MeshDirectionalHTTPConfig struct { + RequestNormalization *RequestNormalizationMeshConfig `json:",omitempty" alias:"request_normalization"` } type PeeringMeshConfig struct { PeerThroughMeshGateways bool `json:",omitempty" alias:"peer_through_mesh_gateways"` } +// RequestNormalizationMeshConfig contains options pertaining to the +// normalization of HTTP requests processed by mesh proxies. +type RequestNormalizationMeshConfig struct { + // InsecureDisablePathNormalization sets the value of the \`normalize_path\` option in the Envoy listener's + // `HttpConnectionManager`. The default value is \`false\`. When set to \`true\` in Consul, \`normalize_path\` is + // set to \`false\` for the Envoy proxy. This parameter disables the normalization of request URL paths according to + // RFC 3986, conversion of \`\\\` to \`/\`, and decoding non-reserved %-encoded characters. When using L7 intentions + // with path match rules, we recommend enabling path normalization in order to avoid match rule circumvention with + // non-normalized path values. + InsecureDisablePathNormalization bool `json:",omitempty" alias:"insecure_disable_path_normalization"` + // MergeSlashes sets the value of the \`merge_slashes\` option in the Envoy listener's \`HttpConnectionManager\`. + // The default value is \`false\`. This option controls the normalization of request URL paths by merging + // consecutive \`/\` characters. This normalization is not part of RFC 3986. When using L7 intentions with path + // match rules, we recommend enabling this setting to avoid match rule circumvention through non-normalized path + // values, unless legitimate service traffic depends on allowing for repeat \`/\` characters, or upstream services + // are configured to differentiate between single and multiple slashes. + MergeSlashes bool `json:",omitempty" alias:"merge_slashes"` + // PathWithEscapedSlashesAction sets the value of the \`path_with_escaped_slashes_action\` option in the Envoy + // listener's \`HttpConnectionManager\`. The default value of this option is empty, which is equivalent to + // \`IMPLEMENTATION_SPECIFIC_DEFAULT\`. This parameter controls the action taken in response to request URL paths + // with escaped slashes in the path. When using L7 intentions with path match rules, we recommend enabling this + // setting to avoid match rule circumvention through non-normalized path values, unless legitimate service traffic + // depends on allowing for escaped \`/\` or \`\\\` characters, or upstream services are configured to differentiate + // between escaped and unescaped slashes. Refer to the Envoy documentation for more information on available + // options. + PathWithEscapedSlashesAction string `json:",omitempty" alias:"path_with_escaped_slashes_action"` + // HeadersWithUnderscoresAction sets the value of the \`headers_with_underscores_action\` option in the Envoy + // listener's \`HttpConnectionManager\` under \`common_http_protocol_options\`. The default value of this option is + // empty, which is equivalent to \`ALLOW\`. Refer to the Envoy documentation for more information on available + // options. + HeadersWithUnderscoresAction string `json:",omitempty" alias:"headers_with_underscores_action"` +} + func (e *MeshConfigEntry) GetKind() string { return MeshConfig } func (e *MeshConfigEntry) GetName() string { return MeshConfigMesh } func (e *MeshConfigEntry) GetPartition() string { return e.Partition } diff --git a/command/helpers/helpers_test.go b/command/helpers/helpers_test.go index 6479386747ba..f3835172057e 100644 --- a/command/helpers/helpers_test.go +++ b/command/helpers/helpers_test.go @@ -2305,6 +2305,10 @@ func TestParseConfigEntry(t *testing.T) { name = "hdr-suffix" suffix = "suffix" }, + { + name = "hdr-contains" + contains = "contains" + }, { name = "hdr-regex" regex = "regex" @@ -2313,7 +2317,12 @@ func TestParseConfigEntry(t *testing.T) { name = "hdr-absent" present = true invert = true - } + }, + { + name = "hdr-ignore-case" + exact = "exact" + ignore_case = true + }, ] } }, @@ -2382,6 +2391,10 @@ func TestParseConfigEntry(t *testing.T) { Name = "hdr-suffix" Suffix = "suffix" }, + { + Name = "hdr-contains" + Contains = "contains" + }, { Name = "hdr-regex" Regex = "regex" @@ -2390,6 +2403,11 @@ func TestParseConfigEntry(t *testing.T) { Name = "hdr-absent" Present = true Invert = true + }, + { + Name = "hdr-ignore-case" + Exact = "exact" + IgnoreCase = true } ] } @@ -2460,6 +2478,10 @@ func TestParseConfigEntry(t *testing.T) { "name": "hdr-suffix", "suffix": "suffix" }, + { + "name": "hdr-contains", + "contains": "contains" + }, { "name": "hdr-regex", "regex": "regex" @@ -2468,6 +2490,11 @@ func TestParseConfigEntry(t *testing.T) { "name": "hdr-absent", "present": true, "invert": true + }, + { + "name": "hdr-ignore-case", + "exact": "exact", + "ignore_case": true } ] } @@ -2542,6 +2569,10 @@ func TestParseConfigEntry(t *testing.T) { "Name": "hdr-suffix", "Suffix": "suffix" }, + { + "Name": "hdr-contains", + "Contains": "contains" + }, { "Name": "hdr-regex", "Regex": "regex" @@ -2550,6 +2581,11 @@ func TestParseConfigEntry(t *testing.T) { "Name": "hdr-absent", "Present": true, "Invert": true + }, + { + "Name": "hdr-ignore-case", + "Exact": "exact", + "IgnoreCase": true } ] } @@ -2623,6 +2659,10 @@ func TestParseConfigEntry(t *testing.T) { Name: "hdr-suffix", Suffix: "suffix", }, + { + Name: "hdr-contains", + Contains: "contains", + }, { Name: "hdr-regex", Regex: "regex", @@ -2632,6 +2672,11 @@ func TestParseConfigEntry(t *testing.T) { Present: true, Invert: true, }, + { + Name: "hdr-ignore-case", + Exact: "exact", + IgnoreCase: true, + }, }, }, }, @@ -2719,7 +2764,7 @@ func TestParseConfigEntry(t *testing.T) { }, }, { - name: "mesh", + name: "mesh: kitchen sink", snake: ` kind = "mesh" meta { @@ -2729,6 +2774,7 @@ func TestParseConfigEntry(t *testing.T) { transparent_proxy { mesh_destinations_only = true } + validate_clusters = true tls { incoming { tls_min_version = "TLSv1_1" @@ -2747,6 +2793,20 @@ func TestParseConfigEntry(t *testing.T) { ] } } + http { + sanitize_x_forwarded_client_cert = true + incoming { + request_normalization { + insecure_disable_path_normalization = true + merge_slashes = true + path_with_escaped_slashes_action = "UNESCAPE_AND_FORWARD" + headers_with_underscores_action = "DROP_HEADER" + } + } + } + peering { + peer_through_mesh_gateways = true + } `, camel: ` Kind = "mesh" @@ -2757,6 +2817,7 @@ func TestParseConfigEntry(t *testing.T) { TransparentProxy { MeshDestinationsOnly = true } + ValidateClusters = true TLS { Incoming { TLSMinVersion = "TLSv1_1" @@ -2775,6 +2836,20 @@ func TestParseConfigEntry(t *testing.T) { ] } } + HTTP { + SanitizeXForwardedClientCert = true + Incoming { + RequestNormalization { + InsecureDisablePathNormalization = true + MergeSlashes = true + PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD" + HeadersWithUnderscoresAction = "DROP_HEADER" + } + } + } + Peering { + PeerThroughMeshGateways = true + } `, snakeJSON: ` { @@ -2786,6 +2861,7 @@ func TestParseConfigEntry(t *testing.T) { "transparent_proxy": { "mesh_destinations_only": true }, + "validate_clusters": true, "tls": { "incoming": { "tls_min_version": "TLSv1_1", @@ -2803,6 +2879,20 @@ func TestParseConfigEntry(t *testing.T) { "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" ] } + }, + "http": { + "sanitize_x_forwarded_client_cert": true, + "incoming": { + "request_normalization": { + "insecure_disable_path_normalization": true, + "merge_slashes": true, + "path_with_escaped_slashes_action": "UNESCAPE_AND_FORWARD", + "headers_with_underscores_action": "DROP_HEADER" + } + } + }, + "peering": { + "peer_through_mesh_gateways": true } } `, @@ -2816,6 +2906,7 @@ func TestParseConfigEntry(t *testing.T) { "TransparentProxy": { "MeshDestinationsOnly": true }, + "ValidateClusters": true, "TLS": { "Incoming": { "TLSMinVersion": "TLSv1_1", @@ -2833,6 +2924,20 @@ func TestParseConfigEntry(t *testing.T) { "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" ] } + }, + "HTTP": { + "SanitizeXForwardedClientCert": true, + "Incoming": { + "RequestNormalization": { + "InsecureDisablePathNormalization": true, + "MergeSlashes": true, + "PathWithEscapedSlashesAction": "UNESCAPE_AND_FORWARD", + "HeadersWithUnderscoresAction": "DROP_HEADER" + } + } + }, + "Peering": { + "PeerThroughMeshGateways": true } } `, @@ -2844,6 +2949,7 @@ func TestParseConfigEntry(t *testing.T) { TransparentProxy: api.TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, + ValidateClusters: true, TLS: &api.MeshTLSConfig{ Incoming: &api.MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_1", @@ -2862,6 +2968,20 @@ func TestParseConfigEntry(t *testing.T) { }, }, }, + HTTP: &api.MeshHTTPConfig{ + SanitizeXForwardedClientCert: true, + Incoming: &api.MeshDirectionalHTTPConfig{ + RequestNormalization: &api.RequestNormalizationMeshConfig{ + InsecureDisablePathNormalization: true, + MergeSlashes: true, + PathWithEscapedSlashesAction: "UNESCAPE_AND_FORWARD", + HeadersWithUnderscoresAction: "DROP_HEADER", + }, + }, + }, + Peering: &api.PeeringMeshConfig{ + PeerThroughMeshGateways: true, + }, }, }, {