mirror of https://github.com/hashicorp/consul
Backport of [NET-1151 NET-11228] security: Add request normalization and header match options to prevent L7 intentions bypass into release/1.20.x (#21839)
backport of commit 9e7757da16
Co-authored-by: Michael Zalimeni <michael.zalimeni@hashicorp.com>
pull/21849/head
parent
2300ed5c89
commit
424f5a808a
|
@ -0,0 +1,9 @@
|
|||
```release-note:security
|
||||
mesh: Add `http.incoming.requestNormalization` to Mesh configuration entry to support inbound service traffic request normalization. This resolves [CVE-2024-10005](https://nvd.nist.gov/vuln/detail/CVE-2024-10005) and [CVE-2024-10006](https://nvd.nist.gov/vuln/detail/CVE-2024-10006).
|
||||
```
|
||||
```release-note:security
|
||||
mesh: Add `contains` and `ignoreCase` to L7 Intentions HTTP header matching criteria to support configuration resilient to variable casing and multiple values. This resolves [CVE-2024-10006](https://nvd.nist.gov/vuln/detail/CVE-2024-10006).
|
||||
```
|
||||
```release-note:breaking-change
|
||||
mesh: Enable Envoy `HttpConnectionManager.normalize_path` by default on inbound traffic to mesh proxies. This resolves [CVE-2024-10005](https://nvd.nist.gov/vuln/detail/CVE-2024-10005).
|
||||
```
|
|
@ -426,13 +426,15 @@ func (p *IntentionHTTPPermission) Clone() *IntentionHTTPPermission {
|
|||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
func cloneStringStringMap(m map[string]string) map[string]string {
|
||||
|
@ -880,8 +882,14 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
|
|||
if hdr.Suffix != "" {
|
||||
hdrParts++
|
||||
}
|
||||
if hdr.Contains != "" {
|
||||
hdrParts++
|
||||
}
|
||||
if hdrParts != 1 {
|
||||
return fmt.Errorf(errorPrefix+".Header[%d] should only contain one of Present, Exact, Prefix, Suffix, or Regex", i, j, k)
|
||||
return fmt.Errorf(errorPrefix+".Header[%d] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex", i, j, k)
|
||||
}
|
||||
if hdr.IgnoreCase && (hdr.Present || hdr.Regex != "") {
|
||||
return fmt.Errorf(errorPrefix+".Header[%d] should set one of Exact, Prefix, Suffix, or Contains when using IgnoreCase", i, j, k)
|
||||
}
|
||||
permParts++
|
||||
}
|
||||
|
|
|
@ -574,323 +574,164 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
|||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] missing required Name field`,
|
||||
},
|
||||
"permission header has too many parts (1)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
Present: true,
|
||||
Exact: "foo",
|
||||
// Regex: "foo",
|
||||
// Prefix: "foo",
|
||||
// Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
Exact: "foo",
|
||||
// Regex: "foo",
|
||||
// Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
// Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (2)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
Present: true,
|
||||
// Exact: "foo",
|
||||
Regex: "foo",
|
||||
// Prefix: "foo",
|
||||
// Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
// Exact: "foo",
|
||||
Regex: "foo",
|
||||
Prefix: "foo",
|
||||
// Suffix: "foo",
|
||||
// Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (3)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
Present: true,
|
||||
// Exact: "foo",
|
||||
// Regex: "foo",
|
||||
Prefix: "foo",
|
||||
// Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
// Exact: "foo",
|
||||
// Regex: "foo",
|
||||
Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
// Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (4)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
Present: true,
|
||||
// Exact: "foo",
|
||||
// Regex: "foo",
|
||||
// Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
Exact: "foo",
|
||||
// Regex: "foo",
|
||||
Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
// Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (5)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
Exact: "foo",
|
||||
Regex: "foo",
|
||||
// Prefix: "foo",
|
||||
// Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
Present: true,
|
||||
Exact: "foo",
|
||||
Regex: "foo",
|
||||
Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (6)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
Exact: "foo",
|
||||
// Regex: "foo",
|
||||
Prefix: "foo",
|
||||
// Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
Present: true,
|
||||
Exact: "foo",
|
||||
// Regex: "foo",
|
||||
Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
// Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (7)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
Exact: "foo",
|
||||
// Regex: "foo",
|
||||
// Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
Exact: "foo",
|
||||
// Regex: "foo",
|
||||
Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
// Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (8)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
// Exact: "foo",
|
||||
Regex: "foo",
|
||||
Prefix: "foo",
|
||||
// Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
// Exact: "foo",
|
||||
Regex: "foo",
|
||||
Prefix: "foo",
|
||||
// Suffix: "foo",
|
||||
// Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (9)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
// Exact: "foo",
|
||||
Regex: "foo",
|
||||
// Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
// Exact: "foo",
|
||||
Regex: "foo",
|
||||
// Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
// Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (10)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
// Exact: "foo",
|
||||
// Regex: "foo",
|
||||
Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
// Present: true,
|
||||
// Exact: "foo",
|
||||
// Regex: "foo",
|
||||
Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
// Contains: "foo",
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`,
|
||||
},
|
||||
"permission header has too many parts (11)": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: []IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
Present: true,
|
||||
Exact: "foo",
|
||||
Regex: "foo",
|
||||
Prefix: "foo",
|
||||
Suffix: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"permission header invalid ignore case (1)": {
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
Present: true,
|
||||
IgnoreCase: true,
|
||||
},
|
||||
},
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`,
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should set one of Exact, Prefix, Suffix, or Contains when using IgnoreCase`,
|
||||
},
|
||||
"permission header invalid ignore case (2)": {
|
||||
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||
{
|
||||
Name: "x-abc",
|
||||
Regex: "qux",
|
||||
IgnoreCase: true,
|
||||
},
|
||||
}),
|
||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should set one of Exact, Prefix, Suffix, or Contains when using IgnoreCase`,
|
||||
},
|
||||
"permission invalid method": {
|
||||
entry: &ServiceIntentionsConfigEntry{
|
||||
|
@ -1677,3 +1518,25 @@ func TestMigrateIntentions(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// httpHeaderPermissionEntry is a helper to generate a ServiceIntentionsConfigEntry for testing
|
||||
// IntentionHTTPHeaderPermission values.
|
||||
func httpHeaderPermissionEntry(header []IntentionHTTPHeaderPermission) *ServiceIntentionsConfigEntry {
|
||||
return &ServiceIntentionsConfigEntry{
|
||||
Kind: ServiceIntentions,
|
||||
Name: "test",
|
||||
Sources: []*SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Permissions: []*IntentionPermission{
|
||||
{
|
||||
Action: IntentionActionAllow,
|
||||
HTTP: &IntentionHTTPPermission{
|
||||
Header: header,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package structs
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/acl"
|
||||
"github.com/hashicorp/consul/types"
|
||||
|
@ -72,6 +73,17 @@ 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"`
|
||||
// There is not currently an outgoing MeshDirectionalHTTPConfig, as
|
||||
// the only required config for either direction at present is inbound
|
||||
// request normalization.
|
||||
}
|
||||
|
||||
// MeshDirectionalHTTPConfig holds mesh configuration specific to HTTP
|
||||
// requests for a given traffic direction.
|
||||
type MeshDirectionalHTTPConfig struct {
|
||||
RequestNormalization *RequestNormalizationMeshConfig `json:",omitempty" alias:"request_normalization"`
|
||||
}
|
||||
|
||||
// PeeringMeshConfig contains cluster-wide options pertaining to peering.
|
||||
|
@ -84,6 +96,104 @@ type PeeringMeshConfig struct {
|
|||
PeerThroughMeshGateways bool `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 `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 `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 PathWithEscapedSlashesAction `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 HeadersWithUnderscoresAction `alias:"headers_with_underscores_action"`
|
||||
}
|
||||
|
||||
// PathWithEscapedSlashesAction is an enum that defines the action to take when
|
||||
// a request path contains escaped slashes. It mirrors exactly the set of options
|
||||
// in Envoy's UriPathNormalizationOptions.PathWithEscapedSlashesAction enum.
|
||||
type PathWithEscapedSlashesAction string
|
||||
|
||||
// See github.com/envoyproxy/go-control-plane envoy_http_v3.HttpConnectionManager_PathWithEscapedSlashesAction.
|
||||
const (
|
||||
PathWithEscapedSlashesActionDefault PathWithEscapedSlashesAction = "IMPLEMENTATION_SPECIFIC_DEFAULT"
|
||||
PathWithEscapedSlashesActionKeep PathWithEscapedSlashesAction = "KEEP_UNCHANGED"
|
||||
PathWithEscapedSlashesActionReject PathWithEscapedSlashesAction = "REJECT_REQUEST"
|
||||
PathWithEscapedSlashesActionUnescapeAndRedirect PathWithEscapedSlashesAction = "UNESCAPE_AND_REDIRECT"
|
||||
PathWithEscapedSlashesActionUnescapeAndForward PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD"
|
||||
)
|
||||
|
||||
// PathWithEscapedSlashesActionStrings returns an ordered slice of all PathWithEscapedSlashesAction values as strings.
|
||||
func PathWithEscapedSlashesActionStrings() []string {
|
||||
return []string{
|
||||
string(PathWithEscapedSlashesActionDefault),
|
||||
string(PathWithEscapedSlashesActionKeep),
|
||||
string(PathWithEscapedSlashesActionReject),
|
||||
string(PathWithEscapedSlashesActionUnescapeAndRedirect),
|
||||
string(PathWithEscapedSlashesActionUnescapeAndForward),
|
||||
}
|
||||
}
|
||||
|
||||
// pathWithEscapedSlashesActions contains the canonical set of PathWithEscapedSlashesActionValues values.
|
||||
var pathWithEscapedSlashesActions = (func() map[PathWithEscapedSlashesAction]struct{} {
|
||||
m := make(map[PathWithEscapedSlashesAction]struct{})
|
||||
for _, v := range PathWithEscapedSlashesActionStrings() {
|
||||
m[PathWithEscapedSlashesAction(v)] = struct{}{}
|
||||
}
|
||||
return m
|
||||
})()
|
||||
|
||||
// HeadersWithUnderscoresAction is an enum that defines the action to take when
|
||||
// a request contains headers with underscores. It mirrors exactly the set of
|
||||
// options in Envoy's HttpProtocolOptions.HeadersWithUnderscoresAction enum.
|
||||
type HeadersWithUnderscoresAction string
|
||||
|
||||
// See github.com/envoyproxy/go-control-plane envoy_core_v3.HttpProtocolOptions_HeadersWithUnderscoresAction.
|
||||
const (
|
||||
HeadersWithUnderscoresActionAllow HeadersWithUnderscoresAction = "ALLOW"
|
||||
HeadersWithUnderscoresActionRejectRequest HeadersWithUnderscoresAction = "REJECT_REQUEST"
|
||||
HeadersWithUnderscoresActionDropHeader HeadersWithUnderscoresAction = "DROP_HEADER"
|
||||
)
|
||||
|
||||
// HeadersWithUnderscoresActionStrings returns an ordered slice of all HeadersWithUnderscoresAction values as strings
|
||||
// for use in returning validation errors.
|
||||
func HeadersWithUnderscoresActionStrings() []string {
|
||||
return []string{
|
||||
string(HeadersWithUnderscoresActionAllow),
|
||||
string(HeadersWithUnderscoresActionRejectRequest),
|
||||
string(HeadersWithUnderscoresActionDropHeader),
|
||||
}
|
||||
}
|
||||
|
||||
// headersWithUnderscoresActions contains the canonical set of HeadersWithUnderscoresAction values.
|
||||
var headersWithUnderscoresActions = (func() map[HeadersWithUnderscoresAction]struct{} {
|
||||
m := make(map[HeadersWithUnderscoresAction]struct{})
|
||||
for _, v := range HeadersWithUnderscoresActionStrings() {
|
||||
m[HeadersWithUnderscoresAction(v)] = struct{}{}
|
||||
}
|
||||
return m
|
||||
})()
|
||||
|
||||
func (e *MeshConfigEntry) GetKind() string {
|
||||
return MeshConfig
|
||||
}
|
||||
|
@ -141,6 +251,10 @@ func (e *MeshConfigEntry) Validate() error {
|
|||
}
|
||||
}
|
||||
|
||||
if err := validateRequestNormalizationMeshConfig(e.GetHTTPIncomingRequestNormalization()); err != nil {
|
||||
return fmt.Errorf("error in HTTP incoming request normalization configuration: %v", err)
|
||||
}
|
||||
|
||||
return e.validateEnterpriseMeta()
|
||||
}
|
||||
|
||||
|
@ -193,6 +307,61 @@ func (e *MeshConfigEntry) PeerThroughMeshGateways() bool {
|
|||
return e.Peering.PeerThroughMeshGateways
|
||||
}
|
||||
|
||||
func (e *MeshConfigEntry) GetHTTP() *MeshHTTPConfig {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.HTTP
|
||||
}
|
||||
|
||||
func (e *MeshHTTPConfig) GetIncoming() *MeshDirectionalHTTPConfig {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Incoming
|
||||
}
|
||||
|
||||
func (e *MeshDirectionalHTTPConfig) GetRequestNormalization() *RequestNormalizationMeshConfig {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.RequestNormalization
|
||||
}
|
||||
|
||||
// GetHTTPIncomingRequestNormalization is a convenience accessor for mesh.http.incoming.request_normalization
|
||||
// since no other fields currently exist under mesh.http.incoming.
|
||||
func (e *MeshConfigEntry) GetHTTPIncomingRequestNormalization() *RequestNormalizationMeshConfig {
|
||||
return e.GetHTTP().GetIncoming().GetRequestNormalization()
|
||||
}
|
||||
|
||||
func (r *RequestNormalizationMeshConfig) GetInsecureDisablePathNormalization() bool {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
return r.InsecureDisablePathNormalization
|
||||
}
|
||||
|
||||
func (r *RequestNormalizationMeshConfig) GetMergeSlashes() bool {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
return r.MergeSlashes
|
||||
}
|
||||
|
||||
func (r *RequestNormalizationMeshConfig) GetPathWithEscapedSlashesAction() PathWithEscapedSlashesAction {
|
||||
if r == nil || r.PathWithEscapedSlashesAction == "" {
|
||||
return PathWithEscapedSlashesActionDefault
|
||||
}
|
||||
return r.PathWithEscapedSlashesAction
|
||||
}
|
||||
|
||||
func (r *RequestNormalizationMeshConfig) GetHeadersWithUnderscoresAction() HeadersWithUnderscoresAction {
|
||||
if r == nil || r.HeadersWithUnderscoresAction == "" {
|
||||
return HeadersWithUnderscoresActionAllow
|
||||
}
|
||||
return r.HeadersWithUnderscoresAction
|
||||
}
|
||||
|
||||
func validateMeshDirectionalTLSConfig(cfg *MeshDirectionalTLSConfig) error {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
|
@ -237,3 +406,36 @@ func validateTLSConfig(
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRequestNormalizationMeshConfig(cfg *RequestNormalizationMeshConfig) error {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
if err := validatePathWithEscapedSlashesAction(cfg.PathWithEscapedSlashesAction); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateHeadersWithUnderscoresAction(cfg.HeadersWithUnderscoresAction); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePathWithEscapedSlashesAction(v PathWithEscapedSlashesAction) error {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
if _, ok := pathWithEscapedSlashesActions[v]; !ok {
|
||||
return fmt.Errorf("no matching PathWithEscapedSlashesAction value found for %s, please specify one of [%s]", string(v), strings.Join(PathWithEscapedSlashesActionStrings(), ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHeadersWithUnderscoresAction(v HeadersWithUnderscoresAction) error {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
if _, ok := headersWithUnderscoresActions[v]; !ok {
|
||||
return fmt.Errorf("no matching HeadersWithUnderscoresAction value found for %s, please specify one of [%s]", string(v), strings.Join(HeadersWithUnderscoresActionStrings(), ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -47,3 +47,164 @@ func TestMeshConfigEntry_PeerThroughMeshGateways(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeshConfigEntry_GetHTTPIncomingRequestNormalization(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
input *MeshConfigEntry
|
||||
want *RequestNormalizationMeshConfig
|
||||
}{
|
||||
// Ensure nil is gracefully handled at each level of config path.
|
||||
"nil entry": {
|
||||
input: nil,
|
||||
want: nil,
|
||||
},
|
||||
"nil http config": {
|
||||
input: &MeshConfigEntry{
|
||||
HTTP: nil,
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"nil http incoming config": {
|
||||
input: &MeshConfigEntry{
|
||||
HTTP: &MeshHTTPConfig{
|
||||
Incoming: nil,
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"nil http incoming request normalization config": {
|
||||
input: &MeshConfigEntry{
|
||||
HTTP: &MeshHTTPConfig{
|
||||
Incoming: &MeshDirectionalHTTPConfig{
|
||||
RequestNormalization: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, tc.input.GetHTTPIncomingRequestNormalization())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeshConfigEntry_RequestNormalizationMeshConfig(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
input *RequestNormalizationMeshConfig
|
||||
getFn func(*RequestNormalizationMeshConfig) any
|
||||
want any
|
||||
}{
|
||||
// Ensure defaults are returned when config is not set.
|
||||
"nil entry gets false GetInsecureDisablePathNormalization": {
|
||||
input: nil,
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetInsecureDisablePathNormalization()
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
"nil entry gets false GetMergeSlashes": {
|
||||
input: nil,
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetMergeSlashes()
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
"nil entry gets default GetPathWithEscapedSlashesAction": {
|
||||
input: nil,
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetPathWithEscapedSlashesAction()
|
||||
},
|
||||
want: PathWithEscapedSlashesAction("IMPLEMENTATION_SPECIFIC_DEFAULT"),
|
||||
},
|
||||
"nil entry gets default GetHeadersWithUnderscoresAction": {
|
||||
input: nil,
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetHeadersWithUnderscoresAction()
|
||||
},
|
||||
want: HeadersWithUnderscoresAction("ALLOW"),
|
||||
},
|
||||
"empty entry gets default GetPathWithEscapedSlashesAction": {
|
||||
input: &RequestNormalizationMeshConfig{},
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetPathWithEscapedSlashesAction()
|
||||
},
|
||||
want: PathWithEscapedSlashesAction("IMPLEMENTATION_SPECIFIC_DEFAULT"),
|
||||
},
|
||||
"empty entry gets default GetHeadersWithUnderscoresAction": {
|
||||
input: &RequestNormalizationMeshConfig{},
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetHeadersWithUnderscoresAction()
|
||||
},
|
||||
want: HeadersWithUnderscoresAction("ALLOW"),
|
||||
},
|
||||
// Ensure values are returned when set.
|
||||
"non-default entry gets expected InsecureDisablePathNormalization": {
|
||||
input: &RequestNormalizationMeshConfig{InsecureDisablePathNormalization: true},
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetInsecureDisablePathNormalization()
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"non-default entry gets expected MergeSlashes": {
|
||||
input: &RequestNormalizationMeshConfig{MergeSlashes: true},
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetMergeSlashes()
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"non-default entry gets expected PathWithEscapedSlashesAction": {
|
||||
input: &RequestNormalizationMeshConfig{PathWithEscapedSlashesAction: "UNESCAPE_AND_FORWARD"},
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetPathWithEscapedSlashesAction()
|
||||
},
|
||||
want: PathWithEscapedSlashesAction("UNESCAPE_AND_FORWARD"),
|
||||
},
|
||||
"non-default entry gets expected HeadersWithUnderscoresAction": {
|
||||
input: &RequestNormalizationMeshConfig{HeadersWithUnderscoresAction: "REJECT_REQUEST"},
|
||||
getFn: func(c *RequestNormalizationMeshConfig) any {
|
||||
return c.GetHeadersWithUnderscoresAction()
|
||||
},
|
||||
want: HeadersWithUnderscoresAction("REJECT_REQUEST"),
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, tc.getFn(tc.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeshConfigEntry_validateRequestNormalizationMeshConfig(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
input *RequestNormalizationMeshConfig
|
||||
wantErr string
|
||||
}{
|
||||
"nil entry is valid": {
|
||||
input: nil,
|
||||
wantErr: "",
|
||||
},
|
||||
"invalid PathWithEscapedSlashesAction is rejected": {
|
||||
input: &RequestNormalizationMeshConfig{
|
||||
PathWithEscapedSlashesAction: PathWithEscapedSlashesAction("INVALID"),
|
||||
},
|
||||
wantErr: "no matching PathWithEscapedSlashesAction value found for INVALID, please specify one of [IMPLEMENTATION_SPECIFIC_DEFAULT, KEEP_UNCHANGED, REJECT_REQUEST, UNESCAPE_AND_REDIRECT, UNESCAPE_AND_FORWARD]",
|
||||
},
|
||||
"invalid HeadersWithUnderscoresAction is rejected": {
|
||||
input: &RequestNormalizationMeshConfig{
|
||||
HeadersWithUnderscoresAction: HeadersWithUnderscoresAction("INVALID"),
|
||||
},
|
||||
wantErr: "no matching HeadersWithUnderscoresAction value found for INVALID, please specify one of [ALLOW, REJECT_REQUEST, DROP_HEADER]",
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.wantErr == "" {
|
||||
assert.NoError(t, validateRequestNormalizationMeshConfig(tc.input))
|
||||
} else {
|
||||
assert.EqualError(t, validateRequestNormalizationMeshConfig(tc.input), tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1910,6 +1910,10 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
name = "hdr-suffix"
|
||||
suffix = "suffix"
|
||||
},
|
||||
{
|
||||
name = "hdr-contains"
|
||||
contains = "contains"
|
||||
},
|
||||
{
|
||||
name = "hdr-regex"
|
||||
regex = "regex"
|
||||
|
@ -1918,7 +1922,12 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
name = "hdr-absent"
|
||||
present = true
|
||||
invert = true
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "hdr-ignore-case"
|
||||
exact = "exact"
|
||||
ignore_case = true
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -1987,6 +1996,10 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
Name = "hdr-suffix"
|
||||
Suffix = "suffix"
|
||||
},
|
||||
{
|
||||
Name = "hdr-contains"
|
||||
Contains = "contains"
|
||||
},
|
||||
{
|
||||
Name = "hdr-regex"
|
||||
Regex = "regex"
|
||||
|
@ -1995,6 +2008,11 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
Name = "hdr-absent"
|
||||
Present = true
|
||||
Invert = true
|
||||
},
|
||||
{
|
||||
Name = "hdr-ignore-case"
|
||||
Exact = "exact"
|
||||
IgnoreCase = true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -2064,6 +2082,10 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
Name: "hdr-suffix",
|
||||
Suffix: "suffix",
|
||||
},
|
||||
{
|
||||
Name: "hdr-contains",
|
||||
Contains: "contains",
|
||||
},
|
||||
{
|
||||
Name: "hdr-regex",
|
||||
Regex: "regex",
|
||||
|
@ -2073,6 +2095,11 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
Present: true,
|
||||
Invert: true,
|
||||
},
|
||||
{
|
||||
Name: "hdr-ignore-case",
|
||||
Exact: "exact",
|
||||
IgnoreCase: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2134,7 +2161,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "mesh",
|
||||
name: "mesh: kitchen sink",
|
||||
snake: `
|
||||
kind = "mesh"
|
||||
meta {
|
||||
|
@ -2145,6 +2172,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
mesh_destinations_only = true
|
||||
}
|
||||
allow_enabling_permissive_mutual_tls = true
|
||||
validate_clusters = true
|
||||
tls {
|
||||
incoming {
|
||||
tls_min_version = "TLSv1_1"
|
||||
|
@ -2163,9 +2191,17 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
]
|
||||
}
|
||||
}
|
||||
http {
|
||||
sanitize_x_forwarded_client_cert = true
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -2180,6 +2216,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
MeshDestinationsOnly = true
|
||||
}
|
||||
AllowEnablingPermissiveMutualTLS = true
|
||||
ValidateClusters = true
|
||||
TLS {
|
||||
Incoming {
|
||||
TLSMinVersion = "TLSv1_1"
|
||||
|
@ -2198,9 +2235,17 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
]
|
||||
}
|
||||
}
|
||||
HTTP {
|
||||
SanitizeXForwardedClientCert = true
|
||||
}
|
||||
HTTP {
|
||||
SanitizeXForwardedClientCert = true
|
||||
Incoming {
|
||||
RequestNormalization {
|
||||
InsecureDisablePathNormalization = true
|
||||
MergeSlashes = true
|
||||
PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD"
|
||||
HeadersWithUnderscoresAction = "DROP_HEADER"
|
||||
}
|
||||
}
|
||||
}
|
||||
Peering {
|
||||
PeerThroughMeshGateways = true
|
||||
}
|
||||
|
@ -2214,6 +2259,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
MeshDestinationsOnly: true,
|
||||
},
|
||||
AllowEnablingPermissiveMutualTLS: true,
|
||||
ValidateClusters: true,
|
||||
TLS: &MeshTLSConfig{
|
||||
Incoming: &MeshDirectionalTLSConfig{
|
||||
TLSMinVersion: types.TLSv1_1,
|
||||
|
@ -2234,6 +2280,14 @@ func TestDecodeConfigEntry(t *testing.T) {
|
|||
},
|
||||
HTTP: &MeshHTTPConfig{
|
||||
SanitizeXForwardedClientCert: true,
|
||||
Incoming: &MeshDirectionalHTTPConfig{
|
||||
RequestNormalization: &RequestNormalizationMeshConfig{
|
||||
InsecureDisablePathNormalization: true, // note: this is the opposite of the recommended default
|
||||
MergeSlashes: true,
|
||||
PathWithEscapedSlashesAction: "UNESCAPE_AND_FORWARD",
|
||||
HeadersWithUnderscoresAction: "DROP_HEADER",
|
||||
},
|
||||
},
|
||||
},
|
||||
Peering: &PeeringMeshConfig{
|
||||
PeerThroughMeshGateways: true,
|
||||
|
|
|
@ -809,6 +809,14 @@ func (o *MeshConfigEntry) DeepCopy() *MeshConfigEntry {
|
|||
if o.HTTP != nil {
|
||||
cp.HTTP = new(MeshHTTPConfig)
|
||||
*cp.HTTP = *o.HTTP
|
||||
if o.HTTP.Incoming != nil {
|
||||
cp.HTTP.Incoming = new(MeshDirectionalHTTPConfig)
|
||||
*cp.HTTP.Incoming = *o.HTTP.Incoming
|
||||
if o.HTTP.Incoming.RequestNormalization != nil {
|
||||
cp.HTTP.Incoming.RequestNormalization = new(RequestNormalizationMeshConfig)
|
||||
*cp.HTTP.Incoming.RequestNormalization = *o.HTTP.Incoming.RequestNormalization
|
||||
}
|
||||
}
|
||||
}
|
||||
if o.Peering != nil {
|
||||
cp.Peering = new(PeeringMeshConfig)
|
||||
|
|
|
@ -1396,6 +1396,7 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
|
|||
filterOpts.httpAuthzFilters = append(filterOpts.httpAuthzFilters, addMeta)
|
||||
|
||||
}
|
||||
setNormalizationOptions(cfgSnap.MeshConfig().GetHTTPIncomingRequestNormalization(), &filterOpts)
|
||||
}
|
||||
|
||||
// If an inbound connect limit is set, inject a connection limit filter on each chain.
|
||||
|
@ -1464,6 +1465,28 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
|
|||
return l, err
|
||||
}
|
||||
|
||||
// setNormalizationOptions sets the normalization options for the listener filter.
|
||||
// This is only used for inbound listeners today (see MeshHTTPConfig).
|
||||
func setNormalizationOptions(rn *structs.RequestNormalizationMeshConfig, opts *listenerFilterOpts) {
|
||||
// Note that these options are _always_ set, not just when rn is non-nil. This enables us to set
|
||||
// Consul defaults (e.g. InsecureDisablePathNormalization = false) that override Envoy defaults
|
||||
// (e.g. normalize_path = false). We override defaults here rather than in xDS code s.t. Consul
|
||||
// defaults are only applied where Consul configuration dictates it should be.
|
||||
|
||||
opts.normalizePath = !rn.GetInsecureDisablePathNormalization() // invert to enable path normalization by default
|
||||
opts.mergeSlashes = rn.GetMergeSlashes()
|
||||
if rn.GetPathWithEscapedSlashesAction() != "" {
|
||||
v := string(rn.GetPathWithEscapedSlashesAction())
|
||||
a := envoy_http_v3.HttpConnectionManager_PathWithEscapedSlashesAction_value[v]
|
||||
opts.pathWithEscapedSlashesAction = envoy_http_v3.HttpConnectionManager_PathWithEscapedSlashesAction(a)
|
||||
}
|
||||
if rn.GetHeadersWithUnderscoresAction() != "" {
|
||||
v := string(rn.GetHeadersWithUnderscoresAction())
|
||||
a := envoy_core_v3.HttpProtocolOptions_HeadersWithUnderscoresAction_value[v]
|
||||
opts.headersWithUnderscoresAction = envoy_core_v3.HttpProtocolOptions_HeadersWithUnderscoresAction(a)
|
||||
}
|
||||
}
|
||||
|
||||
func makePermissiveFilterChain(cfgSnap *proxycfg.ConfigSnapshot, opts listenerFilterOpts) (*envoy_listener_v3.FilterChain, error) {
|
||||
servicePort := cfgSnap.Proxy.LocalServicePort
|
||||
if servicePort <= 0 {
|
||||
|
@ -2365,16 +2388,20 @@ type listenerFilterOpts struct {
|
|||
statPrefix string
|
||||
|
||||
// HTTP listener filter options
|
||||
forwardClientDetails bool
|
||||
forwardClientPolicy envoy_http_v3.HttpConnectionManager_ForwardClientCertDetails
|
||||
httpAuthzFilters []*envoy_http_v3.HttpFilter
|
||||
idleTimeoutMs *int
|
||||
requestTimeoutMs *int
|
||||
routeName string
|
||||
routePath string
|
||||
tracing *envoy_http_v3.HttpConnectionManager_Tracing
|
||||
useRDS bool
|
||||
fetchTimeoutRDS *durationpb.Duration
|
||||
forwardClientDetails bool
|
||||
forwardClientPolicy envoy_http_v3.HttpConnectionManager_ForwardClientCertDetails
|
||||
httpAuthzFilters []*envoy_http_v3.HttpFilter
|
||||
idleTimeoutMs *int
|
||||
requestTimeoutMs *int
|
||||
routeName string
|
||||
routePath string
|
||||
tracing *envoy_http_v3.HttpConnectionManager_Tracing
|
||||
normalizePath bool
|
||||
mergeSlashes bool
|
||||
pathWithEscapedSlashesAction envoy_http_v3.HttpConnectionManager_PathWithEscapedSlashesAction
|
||||
headersWithUnderscoresAction envoy_core_v3.HttpProtocolOptions_HeadersWithUnderscoresAction
|
||||
useRDS bool
|
||||
fetchTimeoutRDS *durationpb.Duration
|
||||
}
|
||||
|
||||
func makeListenerFilter(opts listenerFilterOpts) (*envoy_listener_v3.Filter, error) {
|
||||
|
@ -2490,6 +2517,19 @@ func makeHTTPFilter(opts listenerFilterOpts) (*envoy_listener_v3.Filter, error)
|
|||
cfg.Tracing = opts.tracing
|
||||
}
|
||||
|
||||
// Request normalization
|
||||
if opts.normalizePath {
|
||||
cfg.NormalizePath = &wrapperspb.BoolValue{Value: true}
|
||||
}
|
||||
cfg.MergeSlashes = opts.mergeSlashes
|
||||
cfg.PathWithEscapedSlashesAction = opts.pathWithEscapedSlashesAction
|
||||
if opts.headersWithUnderscoresAction != 0 { // check for non-default to avoid needless instantiation of options
|
||||
if cfg.CommonHttpProtocolOptions == nil {
|
||||
cfg.CommonHttpProtocolOptions = &envoy_core_v3.HttpProtocolOptions{}
|
||||
}
|
||||
cfg.CommonHttpProtocolOptions.HeadersWithUnderscoresAction = opts.headersWithUnderscoresAction
|
||||
}
|
||||
|
||||
if opts.useRDS {
|
||||
if opts.cluster != "" {
|
||||
return nil, fmt.Errorf("cannot specify cluster name when using RDS")
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"testing"
|
||||
"text/template"
|
||||
|
||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||
testinf "github.com/mitchellh/go-testing-interface"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -369,3 +371,71 @@ func TestGetAlpnProtocols(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_setNormalizationOptions(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
rn *structs.RequestNormalizationMeshConfig
|
||||
opts *listenerFilterOpts
|
||||
want *listenerFilterOpts
|
||||
}{
|
||||
"nil entry": {
|
||||
rn: nil,
|
||||
opts: &listenerFilterOpts{},
|
||||
want: &listenerFilterOpts{
|
||||
normalizePath: true,
|
||||
},
|
||||
},
|
||||
"empty entry": {
|
||||
rn: &structs.RequestNormalizationMeshConfig{},
|
||||
opts: &listenerFilterOpts{},
|
||||
want: &listenerFilterOpts{
|
||||
normalizePath: true,
|
||||
},
|
||||
},
|
||||
"empty is equivalent to defaults": {
|
||||
rn: &structs.RequestNormalizationMeshConfig{},
|
||||
opts: &listenerFilterOpts{},
|
||||
want: &listenerFilterOpts{
|
||||
normalizePath: true,
|
||||
mergeSlashes: false,
|
||||
pathWithEscapedSlashesAction: envoy_http_v3.HttpConnectionManager_IMPLEMENTATION_SPECIFIC_DEFAULT,
|
||||
headersWithUnderscoresAction: envoy_core_v3.HttpProtocolOptions_ALLOW,
|
||||
},
|
||||
},
|
||||
"some options": {
|
||||
rn: &structs.RequestNormalizationMeshConfig{
|
||||
InsecureDisablePathNormalization: false,
|
||||
MergeSlashes: true,
|
||||
PathWithEscapedSlashesAction: "",
|
||||
HeadersWithUnderscoresAction: "DROP_HEADER",
|
||||
},
|
||||
opts: &listenerFilterOpts{},
|
||||
want: &listenerFilterOpts{
|
||||
normalizePath: true,
|
||||
mergeSlashes: true,
|
||||
headersWithUnderscoresAction: envoy_core_v3.HttpProtocolOptions_DROP_HEADER,
|
||||
},
|
||||
},
|
||||
"all options": {
|
||||
rn: &structs.RequestNormalizationMeshConfig{
|
||||
InsecureDisablePathNormalization: true, // note: this is the opposite of the recommended default
|
||||
MergeSlashes: true,
|
||||
PathWithEscapedSlashesAction: "REJECT_REQUEST",
|
||||
HeadersWithUnderscoresAction: "DROP_HEADER",
|
||||
},
|
||||
opts: &listenerFilterOpts{},
|
||||
want: &listenerFilterOpts{
|
||||
normalizePath: false,
|
||||
mergeSlashes: true,
|
||||
pathWithEscapedSlashesAction: envoy_http_v3.HttpConnectionManager_REJECT_REQUEST,
|
||||
headersWithUnderscoresAction: envoy_core_v3.HttpProtocolOptions_DROP_HEADER,
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
setNormalizationOptions(tc.rn, tc.opts)
|
||||
assert.Equal(t, tc.want, tc.opts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1250,7 +1250,7 @@ func convertPermission(perm *structs.IntentionPermission) *envoy_rbac_v3.Permiss
|
|||
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
||||
Exact: hdr.Exact,
|
||||
},
|
||||
IgnoreCase: false,
|
||||
IgnoreCase: hdr.IgnoreCase,
|
||||
},
|
||||
}
|
||||
case hdr.Regex != "":
|
||||
|
@ -1259,7 +1259,7 @@ func convertPermission(perm *structs.IntentionPermission) *envoy_rbac_v3.Permiss
|
|||
MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{
|
||||
SafeRegex: response.MakeEnvoyRegexMatch(hdr.Regex),
|
||||
},
|
||||
IgnoreCase: false,
|
||||
// IgnoreCase is not supported for SafeRegex matching per Envoy docs.
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1269,7 +1269,7 @@ func convertPermission(perm *structs.IntentionPermission) *envoy_rbac_v3.Permiss
|
|||
MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{
|
||||
Prefix: hdr.Prefix,
|
||||
},
|
||||
IgnoreCase: false,
|
||||
IgnoreCase: hdr.IgnoreCase,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1279,7 +1279,17 @@ func convertPermission(perm *structs.IntentionPermission) *envoy_rbac_v3.Permiss
|
|||
MatchPattern: &envoy_matcher_v3.StringMatcher_Suffix{
|
||||
Suffix: hdr.Suffix,
|
||||
},
|
||||
IgnoreCase: false,
|
||||
IgnoreCase: hdr.IgnoreCase,
|
||||
},
|
||||
}
|
||||
|
||||
case hdr.Contains != "":
|
||||
eh.HeaderMatchSpecifier = &envoy_route_v3.HeaderMatcher_StringMatch{
|
||||
StringMatch: &envoy_matcher_v3.StringMatcher{
|
||||
MatchPattern: &envoy_matcher_v3.StringMatcher_Contains{
|
||||
Contains: hdr.Contains,
|
||||
},
|
||||
IgnoreCase: hdr.IgnoreCase,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -786,11 +786,19 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) {
|
|||
{Name: "x-bar", Exact: "xyz"},
|
||||
{Name: "x-dib", Prefix: "gaz"},
|
||||
{Name: "x-gir", Suffix: "zim"},
|
||||
{Name: "x-baz", Contains: "qux"},
|
||||
{Name: "x-zim", Regex: "gi[rR]"},
|
||||
// Present does not support IgnoreCase
|
||||
{Name: "y-bar", Exact: "xyz", IgnoreCase: true},
|
||||
{Name: "y-dib", Prefix: "gaz", IgnoreCase: true},
|
||||
{Name: "y-gir", Suffix: "zim", IgnoreCase: true},
|
||||
{Name: "y-baz", Contains: "qux", IgnoreCase: true},
|
||||
// Regex does not support IgnoreCase
|
||||
{Name: "z-foo", Present: true, Invert: true},
|
||||
{Name: "z-bar", Exact: "xyz", Invert: true},
|
||||
{Name: "z-dib", Prefix: "gaz", Invert: true},
|
||||
{Name: "z-gir", Suffix: "zim", Invert: true},
|
||||
{Name: "z-baz", Contains: "qux", Invert: true},
|
||||
{Name: "z-zim", Regex: "gi[rR]", Invert: true},
|
||||
},
|
||||
},
|
||||
|
@ -825,15 +833,25 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) {
|
|||
Action: structs.IntentionActionDeny,
|
||||
HTTP: &structs.IntentionHTTPPermission{
|
||||
Header: []structs.IntentionHTTPHeaderPermission{
|
||||
// Valid vanilla match options
|
||||
{Name: "x-foo", Present: true},
|
||||
{Name: "x-bar", Exact: "xyz"},
|
||||
{Name: "x-dib", Prefix: "gaz"},
|
||||
{Name: "x-gir", Suffix: "zim"},
|
||||
{Name: "x-baz", Contains: "qux"},
|
||||
{Name: "x-zim", Regex: "gi[rR]"},
|
||||
// Valid ignore case match options
|
||||
// (Present and Regex do not support IgnoreCase)
|
||||
{Name: "y-bar", Exact: "xyz", IgnoreCase: true},
|
||||
{Name: "y-dib", Prefix: "gaz", IgnoreCase: true},
|
||||
{Name: "y-gir", Suffix: "zim", IgnoreCase: true},
|
||||
{Name: "y-baz", Contains: "qux", IgnoreCase: true},
|
||||
// Valid invert match options
|
||||
{Name: "z-foo", Present: true, Invert: true},
|
||||
{Name: "z-bar", Exact: "xyz", Invert: true},
|
||||
{Name: "z-dib", Prefix: "gaz", Invert: true},
|
||||
{Name: "z-gir", Suffix: "zim", Invert: true},
|
||||
{Name: "z-baz", Contains: "qux", Invert: true},
|
||||
{Name: "z-zim", Regex: "gi[rR]", Invert: true},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1835,6 +1835,58 @@ func getCustomConfigurationGoldenTestCases(enterprise bool) []goldenTestCase {
|
|||
}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
// Same as below case, but keeps the recommended default value of InsecureDisablePathNormalization
|
||||
// to show that the inverse value is reflected in xDS `normalize_path` config.
|
||||
name: "connect-proxy-with-mesh-config-request-normalization-all-envoy-options-enabled",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
cfgSnap := proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
|
||||
// Ensure public inbound listener has HTTP filter so normalization applies.
|
||||
ns.Proxy.Config["protocol"] = "http"
|
||||
// Ensure outbound HTTP listener has HTTP filter so we can observe normalization is not applied.
|
||||
ns.Proxy.Upstreams[0].Config["protocol"] = "http"
|
||||
}, nil)
|
||||
cfgSnap.ConnectProxy.MeshConfig = &structs.MeshConfigEntry{
|
||||
HTTP: &structs.MeshHTTPConfig{
|
||||
Incoming: &structs.MeshDirectionalHTTPConfig{
|
||||
RequestNormalization: &structs.RequestNormalizationMeshConfig{
|
||||
InsecureDisablePathNormalization: false,
|
||||
MergeSlashes: true,
|
||||
PathWithEscapedSlashesAction: "UNESCAPE_AND_FORWARD",
|
||||
HeadersWithUnderscoresAction: "REJECT_REQUEST",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return cfgSnap
|
||||
},
|
||||
},
|
||||
{
|
||||
// Same as above case, but inverts the recommended default value of InsecureDisablePathNormalization
|
||||
// to show that the value is respected when explicitly set (does not set `normalize_path`).
|
||||
name: "connect-proxy-with-mesh-config-request-normalization-all-consul-options",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
cfgSnap := proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
|
||||
// Ensure public inbound listener has HTTP filter so normalization applies.
|
||||
ns.Proxy.Config["protocol"] = "http"
|
||||
// Ensure outbound HTTP listener has HTTP filter so we can observe normalization is not applied.
|
||||
ns.Proxy.Upstreams[0].Config["protocol"] = "http"
|
||||
}, nil)
|
||||
cfgSnap.ConnectProxy.MeshConfig = &structs.MeshConfigEntry{
|
||||
HTTP: &structs.MeshHTTPConfig{
|
||||
Incoming: &structs.MeshDirectionalHTTPConfig{
|
||||
RequestNormalization: &structs.RequestNormalizationMeshConfig{
|
||||
InsecureDisablePathNormalization: true, // note: this is the opposite of the recommended default
|
||||
MergeSlashes: true,
|
||||
PathWithEscapedSlashesAction: "UNESCAPE_AND_FORWARD",
|
||||
HeadersWithUnderscoresAction: "REJECT_REQUEST",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return cfgSnap
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -172,6 +172,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -248,4 +249,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,6 +173,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -249,4 +250,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,6 +196,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -272,4 +273,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -270,6 +270,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -346,4 +347,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -209,6 +209,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -285,4 +286,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,6 +164,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -240,4 +241,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,6 +200,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -276,4 +277,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,6 +274,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -350,4 +351,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,6 +200,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -276,4 +277,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -231,4 +232,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,6 +191,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -267,4 +268,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,6 +191,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -267,4 +268,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,6 +205,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -281,4 +282,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -231,4 +232,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -231,4 +232,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -231,4 +232,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -231,4 +232,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -231,4 +232,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -231,4 +232,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,6 +157,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -234,4 +235,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -232,4 +233,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,6 +157,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -233,4 +234,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,6 +157,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -234,4 +235,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,6 +156,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -232,4 +233,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -178,6 +178,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -254,4 +255,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -231,4 +232,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,6 +203,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -302,4 +303,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,6 +176,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -252,4 +253,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,6 +181,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -257,4 +258,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"circuitBreakers": {},
|
||||
"commonLbConfig": {
|
||||
"healthyPanicThreshold": {}
|
||||
},
|
||||
"connectTimeout": "5s",
|
||||
"edsClusterConfig": {
|
||||
"edsConfig": {
|
||||
"ads": {},
|
||||
"resourceApiVersion": "V3"
|
||||
}
|
||||
},
|
||||
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"outlierDetection": {},
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tlsParams": {},
|
||||
"validationContext": {
|
||||
"matchTypedSubjectAltNames": [
|
||||
{
|
||||
"matcher": {
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db"
|
||||
},
|
||||
"sanType": "URI"
|
||||
}
|
||||
],
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
},
|
||||
"type": "EDS"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"circuitBreakers": {},
|
||||
"connectTimeout": "5s",
|
||||
"edsClusterConfig": {
|
||||
"edsConfig": {
|
||||
"ads": {},
|
||||
"resourceApiVersion": "V3"
|
||||
}
|
||||
},
|
||||
"name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"outlierDetection": {},
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tlsParams": {},
|
||||
"validationContext": {
|
||||
"matchTypedSubjectAltNames": [
|
||||
{
|
||||
"matcher": {
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target"
|
||||
},
|
||||
"sanType": "URI"
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target"
|
||||
},
|
||||
"sanType": "URI"
|
||||
}
|
||||
],
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
},
|
||||
"type": "EDS"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"connectTimeout": "5s",
|
||||
"loadAssignment": {
|
||||
"clusterName": "local_app",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.0.0.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "local_app",
|
||||
"type": "STATIC"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"circuitBreakers": {},
|
||||
"commonLbConfig": {
|
||||
"healthyPanicThreshold": {}
|
||||
},
|
||||
"connectTimeout": "5s",
|
||||
"edsClusterConfig": {
|
||||
"edsConfig": {
|
||||
"ads": {},
|
||||
"resourceApiVersion": "V3"
|
||||
}
|
||||
},
|
||||
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"outlierDetection": {},
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tlsParams": {},
|
||||
"validationContext": {
|
||||
"matchTypedSubjectAltNames": [
|
||||
{
|
||||
"matcher": {
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db"
|
||||
},
|
||||
"sanType": "URI"
|
||||
}
|
||||
],
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
},
|
||||
"type": "EDS"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"circuitBreakers": {},
|
||||
"connectTimeout": "5s",
|
||||
"edsClusterConfig": {
|
||||
"edsConfig": {
|
||||
"ads": {},
|
||||
"resourceApiVersion": "V3"
|
||||
}
|
||||
},
|
||||
"name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"outlierDetection": {},
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tlsParams": {},
|
||||
"validationContext": {
|
||||
"matchTypedSubjectAltNames": [
|
||||
{
|
||||
"matcher": {
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target"
|
||||
},
|
||||
"sanType": "URI"
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target"
|
||||
},
|
||||
"sanType": "URI"
|
||||
}
|
||||
],
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
},
|
||||
"type": "EDS"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"connectTimeout": "5s",
|
||||
"loadAssignment": {
|
||||
"clusterName": "local_app",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.0.0.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "local_app",
|
||||
"type": "STATIC"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
},
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.2",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
},
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.20.1.2",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
},
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.2",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
},
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.20.1.2",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.0.0.1",
|
||||
"portValue": 9191
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.http_connection_manager",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"routeConfig": {
|
||||
"name": "db",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"name": "db.default.default.dc1",
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"statPrefix": "upstream.db.default.default.dc1",
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
},
|
||||
"upgradeConfigs": [
|
||||
{
|
||||
"upgradeType": "websocket"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": "db:127.0.0.1:9191",
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.10.10.10",
|
||||
"portValue": 8181
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"statPrefix": "upstream.prepared_query_geo-cache"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "0.0.0.0",
|
||||
"portValue": 9999
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.http_connection_manager",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
"commonHttpProtocolOptions": {
|
||||
"headersWithUnderscoresAction": "REJECT_REQUEST"
|
||||
},
|
||||
"forwardClientCertDetails": "APPEND_FORWARD",
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.rbac",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
|
||||
"rules": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.header_to_metadata",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.header_to_metadata.v3.Config",
|
||||
"requestRules": [
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "trust-domain",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "partition",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "namespace",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "datacenter",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\4"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "service",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\5"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"mergeSlashes": true,
|
||||
"pathWithEscapedSlashesAction": "UNESCAPE_AND_FORWARD",
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"name": "public_listener",
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "local_app"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"setCurrentClientCertDetails": {
|
||||
"cert": true,
|
||||
"chain": true,
|
||||
"dns": true,
|
||||
"subject": true,
|
||||
"uri": true
|
||||
},
|
||||
"statPrefix": "public_listener",
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
},
|
||||
"upgradeConfigs": [
|
||||
{
|
||||
"upgradeType": "websocket"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"alpnProtocols": [
|
||||
"http/1.1"
|
||||
],
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tlsParams": {},
|
||||
"validationContext": {
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requireClientCertificate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"name": "public_listener:0.0.0.0:9999",
|
||||
"trafficDirection": "INBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.0.0.1",
|
||||
"portValue": 9191
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.http_connection_manager",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"routeConfig": {
|
||||
"name": "db",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"name": "db.default.default.dc1",
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"statPrefix": "upstream.db.default.default.dc1",
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
},
|
||||
"upgradeConfigs": [
|
||||
{
|
||||
"upgradeType": "websocket"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": "db:127.0.0.1:9191",
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.10.10.10",
|
||||
"portValue": 8181
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"statPrefix": "upstream.prepared_query_geo-cache"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "0.0.0.0",
|
||||
"portValue": 9999
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.http_connection_manager",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
"commonHttpProtocolOptions": {
|
||||
"headersWithUnderscoresAction": "REJECT_REQUEST"
|
||||
},
|
||||
"forwardClientCertDetails": "APPEND_FORWARD",
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.rbac",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
|
||||
"rules": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.header_to_metadata",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.header_to_metadata.v3.Config",
|
||||
"requestRules": [
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "trust-domain",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "partition",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "namespace",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "datacenter",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\4"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"key": "service",
|
||||
"metadataNamespace": "consul",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\5"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"mergeSlashes": true,
|
||||
"normalizePath": true,
|
||||
"pathWithEscapedSlashesAction": "UNESCAPE_AND_FORWARD",
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"name": "public_listener",
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "local_app"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"setCurrentClientCertDetails": {
|
||||
"cert": true,
|
||||
"chain": true,
|
||||
"dns": true,
|
||||
"subject": true,
|
||||
"uri": true
|
||||
},
|
||||
"statPrefix": "public_listener",
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
},
|
||||
"upgradeConfigs": [
|
||||
{
|
||||
"upgradeType": "websocket"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"alpnProtocols": [
|
||||
"http/1.1"
|
||||
],
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tlsParams": {},
|
||||
"validationContext": {
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requireClientCertificate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"name": "public_listener:0.0.0.0:9999",
|
||||
"trafficDirection": "INBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -254,4 +255,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -176,4 +177,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -165,4 +166,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -233,4 +234,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -150,4 +151,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -231,4 +232,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,6 +156,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"normalizePath": true,
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
|
@ -233,4 +234,4 @@
|
|||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"versionInfo": "00000001"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "x-baz",
|
||||
"stringMatch": {
|
||||
"contains": "qux"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "x-zim",
|
||||
|
@ -124,6 +132,42 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "y-bar",
|
||||
"stringMatch": {
|
||||
"exact": "xyz",
|
||||
"ignoreCase": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "y-dib",
|
||||
"stringMatch": {
|
||||
"ignoreCase": true,
|
||||
"prefix": "gaz"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "y-gir",
|
||||
"stringMatch": {
|
||||
"ignoreCase": true,
|
||||
"suffix": "zim"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "y-baz",
|
||||
"stringMatch": {
|
||||
"contains": "qux",
|
||||
"ignoreCase": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"invertMatch": true,
|
||||
|
@ -158,6 +202,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"invertMatch": true,
|
||||
"name": "z-baz",
|
||||
"stringMatch": {
|
||||
"contains": "qux"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"invertMatch": true,
|
||||
|
@ -236,4 +289,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,6 +113,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "x-baz",
|
||||
"stringMatch": {
|
||||
"contains": "qux"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "x-zim",
|
||||
|
@ -123,6 +131,42 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "y-bar",
|
||||
"stringMatch": {
|
||||
"exact": "xyz",
|
||||
"ignoreCase": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "y-dib",
|
||||
"stringMatch": {
|
||||
"ignoreCase": true,
|
||||
"prefix": "gaz"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "y-gir",
|
||||
"stringMatch": {
|
||||
"ignoreCase": true,
|
||||
"suffix": "zim"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"name": "y-baz",
|
||||
"stringMatch": {
|
||||
"contains": "qux",
|
||||
"ignoreCase": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"invertMatch": true,
|
||||
|
@ -157,6 +201,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"invertMatch": true,
|
||||
"name": "z-baz",
|
||||
"stringMatch": {
|
||||
"contains": "qux"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"invertMatch": true,
|
||||
|
@ -235,4 +288,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"nonce": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
|
||||
"versionInfo": "00000001"
|
||||
}
|
|
@ -63,13 +63,15 @@ 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"`
|
||||
}
|
||||
|
||||
type IntentionJWTRequirement struct {
|
||||
|
|
|
@ -69,12 +69,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 }
|
||||
|
|
|
@ -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,8 @@ func TestParseConfigEntry(t *testing.T) {
|
|||
transparent_proxy {
|
||||
mesh_destinations_only = true
|
||||
}
|
||||
allow_enabling_permissive_mutual_tls = true
|
||||
validate_clusters = true
|
||||
tls {
|
||||
incoming {
|
||||
tls_min_version = "TLSv1_1"
|
||||
|
@ -2747,6 +2794,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 +2818,8 @@ func TestParseConfigEntry(t *testing.T) {
|
|||
TransparentProxy {
|
||||
MeshDestinationsOnly = true
|
||||
}
|
||||
AllowEnablingPermissiveMutualTLS = true
|
||||
ValidateClusters = true
|
||||
TLS {
|
||||
Incoming {
|
||||
TLSMinVersion = "TLSv1_1"
|
||||
|
@ -2775,6 +2838,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 +2863,8 @@ func TestParseConfigEntry(t *testing.T) {
|
|||
"transparent_proxy": {
|
||||
"mesh_destinations_only": true
|
||||
},
|
||||
"allow_enabling_permissive_mutual_tls": true,
|
||||
"validate_clusters": true,
|
||||
"tls": {
|
||||
"incoming": {
|
||||
"tls_min_version": "TLSv1_1",
|
||||
|
@ -2803,6 +2882,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 +2909,8 @@ func TestParseConfigEntry(t *testing.T) {
|
|||
"TransparentProxy": {
|
||||
"MeshDestinationsOnly": true
|
||||
},
|
||||
"AllowEnablingPermissiveMutualTLS": true,
|
||||
"ValidateClusters": true,
|
||||
"TLS": {
|
||||
"Incoming": {
|
||||
"TLSMinVersion": "TLSv1_1",
|
||||
|
@ -2833,6 +2928,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 +2953,8 @@ func TestParseConfigEntry(t *testing.T) {
|
|||
TransparentProxy: api.TransparentProxyMeshConfig{
|
||||
MeshDestinationsOnly: true,
|
||||
},
|
||||
AllowEnablingPermissiveMutualTLS: true,
|
||||
ValidateClusters: true,
|
||||
TLS: &api.MeshTLSConfig{
|
||||
Incoming: &api.MeshDirectionalTLSConfig{
|
||||
TLSMinVersion: "TLSv1_1",
|
||||
|
@ -2862,6 +2973,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,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1153,8 +1153,10 @@ func IntentionHTTPHeaderPermissionToStructs(s *IntentionHTTPHeaderPermission, t
|
|||
t.Exact = s.Exact
|
||||
t.Prefix = s.Prefix
|
||||
t.Suffix = s.Suffix
|
||||
t.Contains = s.Contains
|
||||
t.Regex = s.Regex
|
||||
t.Invert = s.Invert
|
||||
t.IgnoreCase = s.IgnoreCase
|
||||
}
|
||||
func IntentionHTTPHeaderPermissionFromStructs(t *structs.IntentionHTTPHeaderPermission, s *IntentionHTTPHeaderPermission) {
|
||||
if s == nil {
|
||||
|
@ -1165,8 +1167,10 @@ func IntentionHTTPHeaderPermissionFromStructs(t *structs.IntentionHTTPHeaderPerm
|
|||
s.Exact = t.Exact
|
||||
s.Prefix = t.Prefix
|
||||
s.Suffix = t.Suffix
|
||||
s.Contains = t.Contains
|
||||
s.Regex = t.Regex
|
||||
s.Invert = t.Invert
|
||||
s.IgnoreCase = t.IgnoreCase
|
||||
}
|
||||
func IntentionHTTPPermissionToStructs(s *IntentionHTTPPermission, t *structs.IntentionHTTPPermission) {
|
||||
if s == nil {
|
||||
|
@ -1758,6 +1762,26 @@ func MeshConfigFromStructs(t *structs.MeshConfigEntry, s *MeshConfig) {
|
|||
s.Meta = t.Meta
|
||||
s.Hash = t.Hash
|
||||
}
|
||||
func MeshDirectionalHTTPConfigToStructs(s *MeshDirectionalHTTPConfig, t *structs.MeshDirectionalHTTPConfig) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
if s.RequestNormalization != nil {
|
||||
var x structs.RequestNormalizationMeshConfig
|
||||
RequestNormalizationMeshConfigToStructs(s.RequestNormalization, &x)
|
||||
t.RequestNormalization = &x
|
||||
}
|
||||
}
|
||||
func MeshDirectionalHTTPConfigFromStructs(t *structs.MeshDirectionalHTTPConfig, s *MeshDirectionalHTTPConfig) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
if t.RequestNormalization != nil {
|
||||
var x RequestNormalizationMeshConfig
|
||||
RequestNormalizationMeshConfigFromStructs(t.RequestNormalization, &x)
|
||||
s.RequestNormalization = &x
|
||||
}
|
||||
}
|
||||
func MeshDirectionalTLSConfigToStructs(s *MeshDirectionalTLSConfig, t *structs.MeshDirectionalTLSConfig) {
|
||||
if s == nil {
|
||||
return
|
||||
|
@ -1791,12 +1815,22 @@ func MeshHTTPConfigToStructs(s *MeshHTTPConfig, t *structs.MeshHTTPConfig) {
|
|||
return
|
||||
}
|
||||
t.SanitizeXForwardedClientCert = s.SanitizeXForwardedClientCert
|
||||
if s.Incoming != nil {
|
||||
var x structs.MeshDirectionalHTTPConfig
|
||||
MeshDirectionalHTTPConfigToStructs(s.Incoming, &x)
|
||||
t.Incoming = &x
|
||||
}
|
||||
}
|
||||
func MeshHTTPConfigFromStructs(t *structs.MeshHTTPConfig, s *MeshHTTPConfig) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.SanitizeXForwardedClientCert = t.SanitizeXForwardedClientCert
|
||||
if t.Incoming != nil {
|
||||
var x MeshDirectionalHTTPConfig
|
||||
MeshDirectionalHTTPConfigFromStructs(t.Incoming, &x)
|
||||
s.Incoming = &x
|
||||
}
|
||||
}
|
||||
func MeshTLSConfigToStructs(s *MeshTLSConfig, t *structs.MeshTLSConfig) {
|
||||
if s == nil {
|
||||
|
@ -1916,6 +1950,24 @@ func RemoteJWKSFromStructs(t *structs.RemoteJWKS, s *RemoteJWKS) {
|
|||
s.JWKSCluster = &x
|
||||
}
|
||||
}
|
||||
func RequestNormalizationMeshConfigToStructs(s *RequestNormalizationMeshConfig, t *structs.RequestNormalizationMeshConfig) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
t.InsecureDisablePathNormalization = s.InsecureDisablePathNormalization
|
||||
t.MergeSlashes = s.MergeSlashes
|
||||
t.PathWithEscapedSlashesAction = pathWithEscapedSlashesActionToStructs(s.PathWithEscapedSlashesAction)
|
||||
t.HeadersWithUnderscoresAction = headersWithUnderscoresActionToStructs(s.HeadersWithUnderscoresAction)
|
||||
}
|
||||
func RequestNormalizationMeshConfigFromStructs(t *structs.RequestNormalizationMeshConfig, s *RequestNormalizationMeshConfig) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.InsecureDisablePathNormalization = t.InsecureDisablePathNormalization
|
||||
s.MergeSlashes = t.MergeSlashes
|
||||
s.PathWithEscapedSlashesAction = pathWithEscapedSlashesActionFromStructs(t.PathWithEscapedSlashesAction)
|
||||
s.HeadersWithUnderscoresAction = headersWithUnderscoresActionFromStructs(t.HeadersWithUnderscoresAction)
|
||||
}
|
||||
func ResourceReferenceToStructs(s *ResourceReference, t *structs.ResourceReference) {
|
||||
if s == nil {
|
||||
return
|
||||
|
|
|
@ -291,6 +291,66 @@ func cipherSuitesFromStructs(cs []types.TLSCipherSuite) []string {
|
|||
return cipherSuites
|
||||
}
|
||||
|
||||
func pathWithEscapedSlashesActionFromStructs(a structs.PathWithEscapedSlashesAction) PathWithEscapedSlashesAction {
|
||||
switch a {
|
||||
case structs.PathWithEscapedSlashesActionDefault:
|
||||
return PathWithEscapedSlashesAction_PathWithEscapedSlashesActionDefault
|
||||
case structs.PathWithEscapedSlashesActionKeep:
|
||||
return PathWithEscapedSlashesAction_PathWithEscapedSlashesActionKeep
|
||||
case structs.PathWithEscapedSlashesActionReject:
|
||||
return PathWithEscapedSlashesAction_PathWithEscapedSlashesActionReject
|
||||
case structs.PathWithEscapedSlashesActionUnescapeAndRedirect:
|
||||
return PathWithEscapedSlashesAction_PathWithEscapedSlashesActionUnescapeAndRedirect
|
||||
case structs.PathWithEscapedSlashesActionUnescapeAndForward:
|
||||
return PathWithEscapedSlashesAction_PathWithEscapedSlashesActionUnescapeAndForward
|
||||
default:
|
||||
return PathWithEscapedSlashesAction_PathWithEscapedSlashesActionDefault
|
||||
}
|
||||
}
|
||||
|
||||
func pathWithEscapedSlashesActionToStructs(a PathWithEscapedSlashesAction) structs.PathWithEscapedSlashesAction {
|
||||
switch a {
|
||||
case PathWithEscapedSlashesAction_PathWithEscapedSlashesActionDefault:
|
||||
return structs.PathWithEscapedSlashesActionDefault
|
||||
case PathWithEscapedSlashesAction_PathWithEscapedSlashesActionKeep:
|
||||
return structs.PathWithEscapedSlashesActionKeep
|
||||
case PathWithEscapedSlashesAction_PathWithEscapedSlashesActionReject:
|
||||
return structs.PathWithEscapedSlashesActionReject
|
||||
case PathWithEscapedSlashesAction_PathWithEscapedSlashesActionUnescapeAndRedirect:
|
||||
return structs.PathWithEscapedSlashesActionUnescapeAndRedirect
|
||||
case PathWithEscapedSlashesAction_PathWithEscapedSlashesActionUnescapeAndForward:
|
||||
return structs.PathWithEscapedSlashesActionUnescapeAndForward
|
||||
default:
|
||||
return structs.PathWithEscapedSlashesActionDefault
|
||||
}
|
||||
}
|
||||
|
||||
func headersWithUnderscoresActionFromStructs(a structs.HeadersWithUnderscoresAction) HeadersWithUnderscoresAction {
|
||||
switch a {
|
||||
case structs.HeadersWithUnderscoresActionAllow:
|
||||
return HeadersWithUnderscoresAction_HeadersWithUnderscoresActionAllow
|
||||
case structs.HeadersWithUnderscoresActionRejectRequest:
|
||||
return HeadersWithUnderscoresAction_HeadersWithUnderscoresActionRejectRequest
|
||||
case structs.HeadersWithUnderscoresActionDropHeader:
|
||||
return HeadersWithUnderscoresAction_HeadersWithUnderscoresActionDropHeader
|
||||
default:
|
||||
return HeadersWithUnderscoresAction_HeadersWithUnderscoresActionAllow
|
||||
}
|
||||
}
|
||||
|
||||
func headersWithUnderscoresActionToStructs(a HeadersWithUnderscoresAction) structs.HeadersWithUnderscoresAction {
|
||||
switch a {
|
||||
case HeadersWithUnderscoresAction_HeadersWithUnderscoresActionAllow:
|
||||
return structs.HeadersWithUnderscoresActionAllow
|
||||
case HeadersWithUnderscoresAction_HeadersWithUnderscoresActionRejectRequest:
|
||||
return structs.HeadersWithUnderscoresActionRejectRequest
|
||||
case HeadersWithUnderscoresAction_HeadersWithUnderscoresActionDropHeader:
|
||||
return structs.HeadersWithUnderscoresActionDropHeader
|
||||
default:
|
||||
return structs.HeadersWithUnderscoresActionAllow
|
||||
}
|
||||
}
|
||||
|
||||
func enterpriseMetaToStructs(m *pbcommon.EnterpriseMeta) acl.EnterpriseMeta {
|
||||
var entMeta acl.EnterpriseMeta
|
||||
pbcommon.EnterpriseMetaToStructs(m, &entMeta)
|
||||
|
|
|
@ -107,6 +107,16 @@ func (msg *MeshHTTPConfig) UnmarshalBinary(b []byte) error {
|
|||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *MeshDirectionalHTTPConfig) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *MeshDirectionalHTTPConfig) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *PeeringMeshConfig) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
|
@ -117,6 +127,16 @@ func (msg *PeeringMeshConfig) UnmarshalBinary(b []byte) error {
|
|||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *RequestNormalizationMeshConfig) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *RequestNormalizationMeshConfig) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *ServiceResolver) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -139,6 +139,16 @@ message MeshDirectionalTLSConfig {
|
|||
// name=Structs
|
||||
message MeshHTTPConfig {
|
||||
bool SanitizeXForwardedClientCert = 1;
|
||||
MeshDirectionalHTTPConfig Incoming = 2;
|
||||
}
|
||||
|
||||
// mog annotation:
|
||||
//
|
||||
// target=github.com/hashicorp/consul/agent/structs.MeshDirectionalHTTPConfig
|
||||
// output=config_entry.gen.go
|
||||
// name=Structs
|
||||
message MeshDirectionalHTTPConfig {
|
||||
RequestNormalizationMeshConfig RequestNormalization = 1;
|
||||
}
|
||||
|
||||
// mog annotation:
|
||||
|
@ -150,6 +160,34 @@ message PeeringMeshConfig {
|
|||
bool PeerThroughMeshGateways = 1;
|
||||
}
|
||||
|
||||
// mog annotation:
|
||||
//
|
||||
// target=github.com/hashicorp/consul/agent/structs.RequestNormalizationMeshConfig
|
||||
// output=config_entry.gen.go
|
||||
// name=Structs
|
||||
message RequestNormalizationMeshConfig {
|
||||
bool InsecureDisablePathNormalization = 1;
|
||||
bool MergeSlashes = 2;
|
||||
// mog: func-to=pathWithEscapedSlashesActionToStructs func-from=pathWithEscapedSlashesActionFromStructs
|
||||
PathWithEscapedSlashesAction PathWithEscapedSlashesAction = 3;
|
||||
// mog: func-to=headersWithUnderscoresActionToStructs func-from=headersWithUnderscoresActionFromStructs
|
||||
HeadersWithUnderscoresAction HeadersWithUnderscoresAction = 4;
|
||||
}
|
||||
|
||||
enum PathWithEscapedSlashesAction {
|
||||
PathWithEscapedSlashesActionDefault = 0;
|
||||
PathWithEscapedSlashesActionKeep = 1;
|
||||
PathWithEscapedSlashesActionReject = 2;
|
||||
PathWithEscapedSlashesActionUnescapeAndRedirect = 3;
|
||||
PathWithEscapedSlashesActionUnescapeAndForward = 4;
|
||||
}
|
||||
|
||||
enum HeadersWithUnderscoresAction {
|
||||
HeadersWithUnderscoresActionAllow = 0;
|
||||
HeadersWithUnderscoresActionRejectRequest = 1;
|
||||
HeadersWithUnderscoresActionDropHeader = 2;
|
||||
}
|
||||
|
||||
// mog annotation:
|
||||
//
|
||||
// target=github.com/hashicorp/consul/agent/structs.ServiceResolverConfigEntry
|
||||
|
@ -521,6 +559,8 @@ message IntentionHTTPHeaderPermission {
|
|||
string Suffix = 5;
|
||||
string Regex = 6;
|
||||
bool Invert = 7;
|
||||
string Contains = 8;
|
||||
bool IgnoreCase = 9;
|
||||
}
|
||||
|
||||
// mog annotation:
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
// Use default-allow policy so that we can test specific deny rules
|
||||
default_intention_policy = "allow"
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
|
||||
snapshot_envoy_admin localhost:19000 s1 primary || true
|
||||
snapshot_envoy_admin localhost:19001 s2 || true
|
|
@ -0,0 +1,101 @@
|
|||
#!/bin/bash
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
upsert_config_entry primary '
|
||||
kind = "service-defaults"
|
||||
name = "s2"
|
||||
protocol = "http"
|
||||
'
|
||||
|
||||
upsert_config_entry primary '
|
||||
kind = "mesh"
|
||||
http {
|
||||
incoming {
|
||||
request_normalization {
|
||||
insecure_disable_path_normalization = true
|
||||
merge_slashes = false // explicitly set to the default for clarity
|
||||
path_with_escaped_slashes_action = "" // explicitly set to the default for clarity
|
||||
headers_with_underscores_action = "" // explicitly set to the default for clarity
|
||||
}
|
||||
}
|
||||
}
|
||||
'
|
||||
|
||||
upsert_config_entry primary '
|
||||
kind = "service-intentions"
|
||||
name = "s2"
|
||||
sources {
|
||||
name = "s1"
|
||||
permissions = [
|
||||
// paths
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
path_exact = "/value/supersecret"
|
||||
}
|
||||
},
|
||||
// headers
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "x-check"
|
||||
contains = "bad"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "x-check"
|
||||
exact = "exactbad"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "x-check"
|
||||
prefix = "prebad-"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "x-check"
|
||||
suffix = "-sufbad"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
// redundant with above case, but included for real-world example
|
||||
// and to cover values containing ".".
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "Host"
|
||||
suffix = "bad.com"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
'
|
||||
|
||||
register_services primary
|
||||
|
||||
gen_envoy_bootstrap s1 19000
|
||||
gen_envoy_bootstrap s2 19001
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
# Allow for non-normalized path testing by using alternative container.
|
||||
export SERVICE_CONTAINER="echo"
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load helpers
|
||||
|
||||
@test "s1 proxy admin is up on :19000" {
|
||||
retry_default curl -f -s localhost:19000/stats -o /dev/null
|
||||
}
|
||||
|
||||
@test "s2 proxy admin is up on :19001" {
|
||||
retry_default curl -f -s localhost:19001/stats -o /dev/null
|
||||
}
|
||||
|
||||
@test "s1 proxy listener should be up and have right cert" {
|
||||
assert_proxy_presents_cert_uri localhost:21000 s1
|
||||
}
|
||||
|
||||
@test "s2 proxy listener should be up and have right cert" {
|
||||
assert_proxy_presents_cert_uri localhost:21001 s2
|
||||
}
|
||||
|
||||
@test "s2 proxies should be healthy" {
|
||||
assert_service_has_healthy_instances s2 1
|
||||
}
|
||||
|
||||
@test "s1 upstream should have healthy endpoints for s2" {
|
||||
assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary HEALTHY 1
|
||||
}
|
||||
|
||||
@test "s2 should have http rbac rules loaded from xDS" {
|
||||
retry_default assert_envoy_http_rbac_policy_count localhost:19001 1
|
||||
}
|
||||
|
||||
# The following tests exercise the same cases in "case-l7-intentions-request-normalization"
|
||||
# but with all normalization disabled, including default path normalization. Note that
|
||||
# disabling normalization is not recommended in production environments unless specifically
|
||||
# required.
|
||||
|
||||
@test "test allowed path" {
|
||||
retry_default must_pass_http_request GET localhost:5000/foo
|
||||
retry_default must_pass_http_request GET localhost:5000/value/foo
|
||||
retry_default must_pass_http_request GET localhost:5000/foo/supersecret
|
||||
}
|
||||
|
||||
@test "test disallowed path" {
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value/supersecret'
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value/supersecret#foo'
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value/supersecret?'
|
||||
}
|
||||
|
||||
@test "test ignored disallowed path with repeat slashes" {
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value//supersecret'
|
||||
get_echo_request_path | grep -Fx '/value//supersecret'
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value///supersecret'
|
||||
get_echo_request_path | grep -Fx '/value///supersecret'
|
||||
}
|
||||
|
||||
@test "test ignored disallowed path with escaped characters" {
|
||||
# escaped '/' (HTTP reserved)
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value%2Fsupersecret'
|
||||
get_echo_request_path | grep -Fx '/value%2Fsupersecret'
|
||||
# escaped 'v' (not HTTP reserved)
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value/%73upersecret'
|
||||
get_echo_request_path | grep -Fx '/value/%73upersecret'
|
||||
}
|
||||
|
||||
@test "test ignored disallowed path with backward slashes" {
|
||||
# URLs must be quoted due to backslashes, otherwise shell erases them
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value\supersecret'
|
||||
get_echo_request_path | grep -Fx '/value\supersecret'
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value\\supersecret'
|
||||
get_echo_request_path | grep -Fx '/value\\supersecret'
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value\/supersecret'
|
||||
get_echo_request_path | grep -Fx '/value\/supersecret'
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value/\/supersecret'
|
||||
get_echo_request_path | grep -Fx '/value/\/supersecret'
|
||||
}
|
||||
|
||||
@test "test ignored underscore in header key" {
|
||||
retry_default must_pass_http_request GET localhost:5000/foo x_poison:anything
|
||||
get_echo_request_header_value "x_poison" | grep -Fx 'anything'
|
||||
retry_default must_pass_http_request GET localhost:5000/foo x_check:bad
|
||||
get_echo_request_header_value "x_check" | grep -Fx 'bad'
|
||||
retry_default must_pass_http_request GET localhost:5000/foo x_check:good-sufbad
|
||||
get_echo_request_header_value "x_check" | grep -Fx 'good-sufbad'
|
||||
retry_default must_pass_http_request GET localhost:5000/foo x_check:prebad-good
|
||||
get_echo_request_header_value "x_check" | grep -Fx 'prebad-good'
|
||||
}
|
||||
|
||||
# Header contains and ignoreCase are not expected to change behavior with normalization
|
||||
# disabled, so those cases from "case-l7-intentions-request-normalization" are omitted here.
|
||||
|
||||
|
||||
# @test "s1 upstream should NOT be able to connect to s2" {
|
||||
# run retry_default must_fail_tcp_connection localhost:5000
|
||||
|
||||
# echo "OUTPUT $output"
|
||||
|
||||
# [ "$status" == "0" ]
|
||||
# }
|
|
@ -0,0 +1,5 @@
|
|||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
// Use default-allow policy so that we can test specific deny rules
|
||||
default_intention_policy = "allow"
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
|
||||
snapshot_envoy_admin localhost:19000 s1 primary || true
|
||||
snapshot_envoy_admin localhost:19001 s2 || true
|
|
@ -0,0 +1,101 @@
|
|||
#!/bin/bash
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
upsert_config_entry primary '
|
||||
kind = "service-defaults"
|
||||
name = "s2"
|
||||
protocol = "http"
|
||||
'
|
||||
|
||||
upsert_config_entry primary '
|
||||
kind = "mesh"
|
||||
http {
|
||||
incoming {
|
||||
request_normalization {
|
||||
insecure_disable_path_normalization = false // explicitly set to the default for clarity
|
||||
merge_slashes = true
|
||||
path_with_escaped_slashes_action = "UNESCAPE_AND_FORWARD"
|
||||
headers_with_underscores_action = "REJECT_REQUEST"
|
||||
}
|
||||
}
|
||||
}
|
||||
'
|
||||
|
||||
upsert_config_entry primary '
|
||||
kind = "service-intentions"
|
||||
name = "s2"
|
||||
sources {
|
||||
name = "s1"
|
||||
permissions = [
|
||||
// paths
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
path_exact = "/value/supersecret"
|
||||
}
|
||||
},
|
||||
// headers
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "x-check"
|
||||
contains = "bad"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "x-check"
|
||||
exact = "exactbad"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "x-check"
|
||||
prefix = "prebad-"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "x-check"
|
||||
suffix = "-sufbad"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
// redundant with above case, but included for real-world example
|
||||
// and to cover values containing ".".
|
||||
{
|
||||
action = "deny"
|
||||
http {
|
||||
header = [{
|
||||
name = "Host"
|
||||
suffix = "bad.com"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
'
|
||||
|
||||
register_services primary
|
||||
|
||||
gen_envoy_bootstrap s1 19000
|
||||
gen_envoy_bootstrap s2 19001
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
# Allow for non-normalized path testing by using alternative container.
|
||||
export SERVICE_CONTAINER="echo"
|
|
@ -0,0 +1,129 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load helpers
|
||||
|
||||
@test "s1 proxy admin is up on :19000" {
|
||||
retry_default curl -f -s localhost:19000/stats -o /dev/null
|
||||
}
|
||||
|
||||
@test "s2 proxy admin is up on :19001" {
|
||||
retry_default curl -f -s localhost:19001/stats -o /dev/null
|
||||
}
|
||||
|
||||
@test "s1 proxy listener should be up and have right cert" {
|
||||
assert_proxy_presents_cert_uri localhost:21000 s1
|
||||
}
|
||||
|
||||
@test "s2 proxy listener should be up and have right cert" {
|
||||
assert_proxy_presents_cert_uri localhost:21001 s2
|
||||
}
|
||||
|
||||
@test "s2 proxies should be healthy" {
|
||||
assert_service_has_healthy_instances s2 1
|
||||
}
|
||||
|
||||
@test "s1 upstream should have healthy endpoints for s2" {
|
||||
assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary HEALTHY 1
|
||||
}
|
||||
|
||||
@test "s2 should have http rbac rules loaded from xDS" {
|
||||
retry_default assert_envoy_http_rbac_policy_count localhost:19001 1
|
||||
}
|
||||
|
||||
# The following tests assert one of two things: that the request was
|
||||
# rejected by L7 intentions as expected due to normalization, or that the
|
||||
# request was allowed, and the request received by the upstream matched the
|
||||
# expected normalized form.
|
||||
|
||||
@test "test allowed path" {
|
||||
retry_default must_pass_http_request GET localhost:5000/foo
|
||||
retry_default must_pass_http_request GET localhost:5000/value/foo
|
||||
retry_default must_pass_http_request GET localhost:5000/foo/supersecret
|
||||
}
|
||||
|
||||
@test "test disallowed path" {
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value/supersecret'
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value/supersecret#foo'
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value/supersecret?'
|
||||
}
|
||||
|
||||
@test "test disallowed path with repeat slashes" {
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value//supersecret'
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value///supersecret'
|
||||
}
|
||||
|
||||
@test "test path with repeat slashes normalized" {
|
||||
# After each request, verify that the request path observed by fortio matches the expected normalized path.
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value//foo'
|
||||
get_echo_request_path | grep -Fx '/value/foo'
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value///foo'
|
||||
get_echo_request_path | grep -Fx '/value/foo'
|
||||
}
|
||||
|
||||
@test "test disallowed path with escaped characters" {
|
||||
# escaped '/' (HTTP reserved)
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value%2Fsupersecret'
|
||||
# escaped 'v' (not HTTP reserved)
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value/%73upersecret'
|
||||
}
|
||||
|
||||
@test "test path with escaped characters normalized" {
|
||||
# escaped '/' (HTTP reserved)
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value%2Ffoo'
|
||||
get_echo_request_path | grep -Fx '/value/foo'
|
||||
# escaped 'v' (not HTTP reserved)
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value/%66oo'
|
||||
get_echo_request_path | grep -Fx '/value/foo'
|
||||
}
|
||||
|
||||
@test "test disallowed path with backward slashes" {
|
||||
# URLs must be quoted due to backslashes, otherwise shell erases them
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value\supersecret'
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value\\supersecret'
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value\/supersecret'
|
||||
retry_default must_fail_http_request 403 GET 'localhost:5000/value/\/supersecret'
|
||||
}
|
||||
|
||||
@test "test path with backward slashes normalized" {
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value\foo'
|
||||
get_echo_request_path | grep -Fx '/value/foo'
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value\\foo'
|
||||
get_echo_request_path | grep -Fx '/value/foo'
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value\/foo'
|
||||
get_echo_request_path | grep -Fx '/value/foo'
|
||||
retry_default must_pass_http_request GET 'localhost:5000/value/\/foo'
|
||||
get_echo_request_path | grep -Fx '/value/foo'
|
||||
}
|
||||
|
||||
@test "test disallowed underscore in header key" {
|
||||
# Envoy responds with 400 when configured to reject underscore headers.
|
||||
retry_default must_fail_http_request 400 GET localhost:5000/foo x_poison:anything
|
||||
retry_default must_fail_http_request 400 GET localhost:5000/foo x_check:bad
|
||||
retry_default must_fail_http_request 400 GET localhost:5000/foo x_check:good-sufbad
|
||||
retry_default must_fail_http_request 400 GET localhost:5000/foo x_check:prebad-good
|
||||
}
|
||||
|
||||
@test "test disallowed contains header" {
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/foo x-check:thiscontainsbadinit
|
||||
}
|
||||
|
||||
@test "test disallowed ignore case header" {
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/foo x-check:exactBaD
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/foo x-check:good-SuFBaD
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/foo x-check:PrEBaD-good
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/foo x-check:thiscontainsBaDinit
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/foo Host:foo.BaD.com
|
||||
}
|
||||
|
||||
@test "test case-insensitive disallowed header" {
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/foo Host:foo.BAD.com
|
||||
}
|
||||
|
||||
|
||||
# @test "s1 upstream should NOT be able to connect to s2" {
|
||||
# run retry_default must_fail_tcp_connection localhost:5000
|
||||
|
||||
# echo "OUTPUT $output"
|
||||
|
||||
# [ "$status" == "0" ]
|
||||
# }
|
|
@ -51,6 +51,17 @@ sources {
|
|||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "allow"
|
||||
http {
|
||||
path_exact = "/hdr-exact-ignore-case"
|
||||
header = [{
|
||||
name = "x-test-debug"
|
||||
exact = "foo.bar.com"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "allow"
|
||||
http {
|
||||
|
@ -61,6 +72,17 @@ sources {
|
|||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "allow"
|
||||
http {
|
||||
path_exact = "/hdr-prefix-ignore-case"
|
||||
header = [{
|
||||
name = "x-test-debug"
|
||||
prefix = "foo.bar"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "allow"
|
||||
http {
|
||||
|
@ -71,6 +93,38 @@ sources {
|
|||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "allow"
|
||||
http {
|
||||
path_exact = "/hdr-suffix-ignore-case"
|
||||
header = [{
|
||||
name = "x-test-debug"
|
||||
suffix = "bar.com"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "allow"
|
||||
http {
|
||||
path_exact = "/hdr-contains"
|
||||
header = [{
|
||||
name = "x-test-debug"
|
||||
contains = "contains"
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "allow"
|
||||
http {
|
||||
path_exact = "/hdr-contains-ignore-case"
|
||||
header = [{
|
||||
name = "x-test-debug"
|
||||
contains = "contains"
|
||||
ignore_case = true
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
action = "allow"
|
||||
http {
|
||||
|
|
|
@ -34,49 +34,84 @@ load helpers
|
|||
|
||||
@test "test exact path" {
|
||||
retry_default must_pass_http_request GET localhost:5000/exact
|
||||
retry_default must_fail_http_request GET localhost:5000/exact-nope
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/exact-nope
|
||||
}
|
||||
|
||||
@test "test prefix path" {
|
||||
retry_default must_pass_http_request GET localhost:5000/prefix
|
||||
retry_default must_fail_http_request GET localhost:5000/nope-prefix
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/nope-prefix
|
||||
}
|
||||
|
||||
@test "test regex path" {
|
||||
retry_default must_pass_http_request GET localhost:5000/regex
|
||||
retry_default must_fail_http_request GET localhost:5000/reggex
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/reggex
|
||||
}
|
||||
|
||||
@test "test present header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-present anything
|
||||
retry_default must_fail_http_request GET localhost:5000/hdr-present ""
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-present x-test-debug:anything
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-present x-test-debug:
|
||||
}
|
||||
|
||||
@test "test exact header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-exact exact
|
||||
retry_default must_fail_http_request GET localhost:5000/hdr-exact exact-nope
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-exact x-test-debug:exact
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-exact x-test-debug:exact-nope
|
||||
}
|
||||
|
||||
@test "test prefix header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-prefix prefix
|
||||
retry_default must_fail_http_request GET localhost:5000/hdr-prefix nope-prefix
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-prefix x-test-debug:prefix
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-prefix x-test-debug:nope-prefix
|
||||
}
|
||||
|
||||
@test "test suffix header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-suffix suffix
|
||||
retry_default must_fail_http_request GET localhost:5000/hdr-suffix suffix-nope
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-suffix x-test-debug:suffix
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-suffix x-test-debug:suffix-nope
|
||||
}
|
||||
|
||||
@test "test contains header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-contains x-test-debug:contains
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-contains x-test-debug:ccontainss
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-contains x-test-debug:still-contains-value
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-contains x-test-debug:conntains
|
||||
}
|
||||
|
||||
@test "test regex header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-regex regex
|
||||
retry_default must_fail_http_request GET localhost:5000/hdr-regex reggex
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-regex x-test-debug:regex
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-regex x-test-debug:reggex
|
||||
}
|
||||
|
||||
@test "test exact ignore case header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-exact-ignore-case x-test-debug:foo.bar.com
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-exact-ignore-case x-test-debug:foo.BAR.com
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-exact-ignore-case x-test-debug:fOo.bAr.coM
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-exact-ignore-case x-test-debug:fOo.bAr.coM.nope
|
||||
}
|
||||
|
||||
@test "test prefix ignore case header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-prefix-ignore-case x-test-debug:foo.bar.com
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-prefix-ignore-case x-test-debug:foo.BAR.com
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-prefix-ignore-case x-test-debug:fOo.bAr.coM
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-prefix-ignore-case x-test-debug:nope.fOo.bAr.coM
|
||||
}
|
||||
|
||||
@test "test suffix ignore case header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-suffix-ignore-case x-test-debug:foo.bar.com
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-suffix-ignore-case x-test-debug:foo.BAR.com
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-suffix-ignore-case x-test-debug:fOo.bAr.coM
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-suffix-ignore-case x-test-debug:fOo.bAr.coM.nope
|
||||
}
|
||||
|
||||
@test "test contains ignore case header" {
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-contains-ignore-case x-test-debug:cOntAins
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-contains-ignore-case x-test-debug:CconTainsS
|
||||
retry_default must_pass_http_request GET localhost:5000/hdr-contains-ignore-case x-test-debug:still-cOntAins-value
|
||||
retry_default must_fail_http_request 403 GET localhost:5000/hdr-contains-ignore-case x-test-debug:cOnntAins
|
||||
}
|
||||
|
||||
@test "test method match" {
|
||||
retry_default must_pass_http_request GET localhost:5000/method-match
|
||||
retry_default must_pass_http_request PUT localhost:5000/method-match
|
||||
retry_default must_fail_http_request POST localhost:5000/method-match
|
||||
retry_default must_fail_http_request HEAD localhost:5000/method-match
|
||||
retry_default must_fail_http_request 403 POST localhost:5000/method-match
|
||||
retry_default must_fail_http_request 403 HEAD localhost:5000/method-match
|
||||
}
|
||||
|
||||
# @test "s1 upstream should NOT be able to connect to s2" {
|
||||
|
|
|
@ -761,17 +761,13 @@ function must_fail_http_connection {
|
|||
}
|
||||
|
||||
# must_pass_http_request allows you to craft a specific http request to assert
|
||||
# that envoy will NOT reject the request. Primarily of use for testing L7
|
||||
# intentions.
|
||||
# that envoy will NOT reject the request.
|
||||
function must_pass_http_request {
|
||||
local METHOD=$1
|
||||
local URL=$2
|
||||
local DEBUG_HEADER_VALUE="${3:-""}"
|
||||
shift 2
|
||||
|
||||
local extra_args
|
||||
if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then
|
||||
extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}"
|
||||
fi
|
||||
case "$METHOD" in
|
||||
GET) ;;
|
||||
|
||||
|
@ -786,22 +782,25 @@ function must_pass_http_request {
|
|||
;;
|
||||
esac
|
||||
|
||||
# Treat any remaining args as header KVs
|
||||
for HEADER_ARG in "$@"; do
|
||||
extra_args="$extra_args -H ${HEADER_ARG}"
|
||||
done
|
||||
|
||||
run curl --no-keepalive -v -s -f $extra_args "$URL"
|
||||
[ "$status" == 0 ]
|
||||
}
|
||||
|
||||
# must_fail_http_request allows you to craft a specific http request to assert
|
||||
# that envoy will reject the request. Primarily of use for testing L7
|
||||
# intentions.
|
||||
# that envoy will reject the request. Must supply the expected status code before
|
||||
# method and URL.
|
||||
function must_fail_http_request {
|
||||
local METHOD=$1
|
||||
local URL=$2
|
||||
local DEBUG_HEADER_VALUE="${3:-""}"
|
||||
local EXPECT_RESPONSE=$1
|
||||
local METHOD=$2
|
||||
local URL=$3
|
||||
shift 2
|
||||
|
||||
local extra_args
|
||||
if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then
|
||||
extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}"
|
||||
fi
|
||||
case "$METHOD" in
|
||||
HEAD)
|
||||
extra_args="$extra_args -I"
|
||||
|
@ -819,12 +818,42 @@ function must_fail_http_request {
|
|||
;;
|
||||
esac
|
||||
|
||||
# Treat any remaining args as header KVs
|
||||
for HEADER_ARG in "$@"; do
|
||||
extra_args="$extra_args -H ${HEADER_ARG}"
|
||||
done
|
||||
|
||||
# Attempt to curl through upstream
|
||||
run curl --no-keepalive -s -i $extra_args "$URL"
|
||||
|
||||
echo "OUTPUT $output"
|
||||
|
||||
echo "$output" | grep "403 Forbidden"
|
||||
# Output of curl should include status code immediately after 'HTTP/1.1'
|
||||
echo "$output" | grep "HTTP/1.1 $EXPECT_RESPONSE"
|
||||
}
|
||||
|
||||
# Gets the JSON response containing request parameters from the echo service response.
|
||||
# See https://github.com/mendhak/docker-http-https-echo?tab=readme-ov-file#screenshots
|
||||
# for example response body.
|
||||
# Requires SERVICE_CONTAINER=echo.
|
||||
function get_echo_output {
|
||||
# Take the JSON response from $output, starting with first line containing only '{'
|
||||
# and ending with the next line containing only '}'.
|
||||
# The first sed converts a trailing '}* <some text...>' (curl -v output) to just '}'.
|
||||
local json=$(echo "$output" | sed 's/}\*.*/}/' | sed -n -e '/^{$/,/^}$/{ p; }')
|
||||
echo $json | jq -r '.' || echo "Output did not contain valid JSON: $output" >&3
|
||||
}
|
||||
|
||||
# Gets the value of the raw request path from the echo service response.
|
||||
# Requires SERVICE_CONTAINER=echo.
|
||||
function get_echo_request_path {
|
||||
get_echo_output | jq -r '.path'
|
||||
}
|
||||
|
||||
# Gets the value of a given request header from the echo service response.
|
||||
# Requires SERVICE_CONTAINER=echo.
|
||||
function get_echo_request_header_value {
|
||||
get_echo_output | jq -r ".headers.$1"
|
||||
}
|
||||
|
||||
function gen_envoy_bootstrap {
|
||||
|
|
|
@ -602,15 +602,43 @@ function run_container {
|
|||
"run_container_$1"
|
||||
}
|
||||
|
||||
# Run the common service container. By default, uses fortio/fortio.
|
||||
#
|
||||
# To use mendhak/http-https-echo, set SERVICE_CONTAINER=echo in vars.sh.
|
||||
#
|
||||
# To provide a custom docker run command for test containers, override
|
||||
# common_run_container_service in vars.sh (which will be sourced prior to
|
||||
# invocation). Use $(container_name_prev) in the custom function to get
|
||||
# the correct effective container name. See common_run_container-fortio
|
||||
# for the expected args list.
|
||||
function common_run_container_service {
|
||||
local service="$1"
|
||||
local CLUSTER="$2"
|
||||
local httpPort="$3"
|
||||
local grpcPort="$4"
|
||||
local serviceContainer=${SERVICE_CONTAINER:-fortio}
|
||||
local containerName=$(container_name_prev)
|
||||
|
||||
docker run --sysctl net.ipv6.conf.all.disable_ipv6=1 -d --name $(container_name_prev) \
|
||||
case "$serviceContainer" in
|
||||
fortio)
|
||||
common_run_container-fortio "$containerName" "$@"
|
||||
;;
|
||||
echo)
|
||||
common_run_container-echo "$containerName" "$@"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown common run container: $runContainer"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function common_run_container-fortio {
|
||||
local containerName="$1"
|
||||
local service="$2"
|
||||
local cluster="$3"
|
||||
local httpPort="$4"
|
||||
local grpcPort="$5"
|
||||
|
||||
docker run --sysctl net.ipv6.conf.all.disable_ipv6=1 -d --name $containerName \
|
||||
-e "FORTIO_NAME=${service}" \
|
||||
$(network_snippet $CLUSTER) \
|
||||
$(network_snippet $cluster) \
|
||||
"${HASHICORP_DOCKER_PROXY}/fortio/fortio" \
|
||||
server \
|
||||
-http-port ":$httpPort" \
|
||||
|
@ -618,6 +646,25 @@ function common_run_container_service {
|
|||
-redirect-port disabled >/dev/null
|
||||
}
|
||||
|
||||
# Alternative to Fortio, which has limited ability to echo back arbitrary
|
||||
# requests (only one pre-determined debug path), and uses Go's net/http, which
|
||||
# force-normalizes paths. Useful for verifying HTTP request parameters sent by
|
||||
# Envoy to the upstream.
|
||||
function common_run_container-echo {
|
||||
local containerName="$1"
|
||||
local cluster="$3"
|
||||
local httpPort="$4"
|
||||
|
||||
# HTTPS_PORT=0 will randomly assign a port number. It must be set, otherwise
|
||||
# multiple containers on same network will fail due to using the same default port.
|
||||
docker run --sysctl net.ipv6.conf.all.disable_ipv6=1 -d --name $containerName \
|
||||
-e "HTTP_PORT=${httpPort}" \
|
||||
-e "HTTPS_PORT=0" \
|
||||
$(network_snippet $cluster) \
|
||||
${HASHICORP_DOCKER_PROXY}/mendhak/http-https-echo:34 >/dev/null
|
||||
}
|
||||
|
||||
|
||||
function run_container_s1 {
|
||||
common_run_container_service s1 primary 8080 8079
|
||||
}
|
||||
|
|
|
@ -69,7 +69,20 @@
|
|||
</group.Element>
|
||||
{{/if}}
|
||||
|
||||
{{#if shouldShowIgnoreCaseField}}
|
||||
<group.Element
|
||||
@name="IgnoreCase"
|
||||
@error={{changeset-get changeset 'error.IgnoreCase'}}
|
||||
as |el|>
|
||||
<el.Label>Ignore Case</el.Label>
|
||||
<el.Checkbox
|
||||
checked={{if IgnoreCase 'checked'}}
|
||||
onchange={{action 'change' 'IgnoreCase' changeset}}
|
||||
/>
|
||||
</group.Element>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</FormGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
import { get, set, computed } from '@ember/object';
|
||||
import { set, computed } from '@ember/object';
|
||||
import { alias, equal, not } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
|
@ -37,6 +37,7 @@ export default Component.extend({
|
|||
Exact: 'Exactly Matching',
|
||||
Prefix: 'Prefixed by',
|
||||
Suffix: 'Suffixed by',
|
||||
Contains: 'Containing',
|
||||
Regex: 'Regular Expression',
|
||||
Present: 'Is present',
|
||||
};
|
||||
|
@ -49,9 +50,14 @@ export default Component.extend({
|
|||
headerTypeEqualsPresent: equal('headerType', 'Present'),
|
||||
shouldShowValueField: not('headerTypeEqualsPresent'),
|
||||
|
||||
shouldShowIgnoreCaseField: computed('headerType', function () {
|
||||
return this.headerType !== 'Present' && this.headerType !== 'Regex';
|
||||
}),
|
||||
|
||||
actions: {
|
||||
change: function (name, changeset, e) {
|
||||
const value = typeof get(e, 'target.value') !== 'undefined' ? e.target.value : e;
|
||||
const valueIndicator = e.target?.type === 'checkbox' ? e.target?.checked : e.target?.value;
|
||||
const value = typeof valueIndicator !== 'undefined' ? valueIndicator : e;
|
||||
switch (name) {
|
||||
default:
|
||||
changeset.set(name, value);
|
||||
|
@ -65,6 +71,7 @@ export default Component.extend({
|
|||
// Present is a boolean, whereas all other header types have a value
|
||||
const value = changeset.HeaderType === 'Present' ? true : changeset.Value;
|
||||
changeset.set(changeset.HeaderType, value);
|
||||
changeset.set('IgnoreCase', changeset.IgnoreCase);
|
||||
|
||||
// this will prevent the changeset from overwriting the
|
||||
// computed properties on the ED object
|
||||
|
|
|
@ -11,7 +11,14 @@ export default (scope = '.consul-intention-permission-header-form') => {
|
|||
scope: scope,
|
||||
HeaderType: {
|
||||
scope: '[data-property="headertype"]',
|
||||
...powerSelect(['ExactlyMatching', 'PrefixedBy', 'SuffixedBy', 'RegEx', 'IsPresent']),
|
||||
...powerSelect([
|
||||
'ExactlyMatching',
|
||||
'PrefixedBy',
|
||||
'SuffixedBy',
|
||||
'Containing',
|
||||
'RegEx',
|
||||
'IsPresent',
|
||||
]),
|
||||
},
|
||||
Name: {
|
||||
scope: '[data-property="name"] input',
|
||||
|
|
|
@ -6,21 +6,26 @@
|
|||
import { helper } from '@ember/component/helper';
|
||||
|
||||
export default helper(function routeMatch([item], hash) {
|
||||
const prop = ['Present', 'Exact', 'Prefix', 'Suffix', 'Regex'].find(
|
||||
const prop = ['Present', 'Exact', 'Prefix', 'Suffix', 'Contains', 'Regex'].find(
|
||||
(prop) => typeof item[prop] !== 'undefined'
|
||||
);
|
||||
|
||||
let invertPrefix = item.Invert ? 'NOT ' : '';
|
||||
let ignoreCaseSuffix = item.IgnoreCase ? ' (case-insensitive)' : '';
|
||||
|
||||
switch (prop) {
|
||||
case 'Present':
|
||||
return `${item.Invert ? `NOT ` : ``}present`;
|
||||
return `${invertPrefix}present`;
|
||||
case 'Exact':
|
||||
return `${item.Invert ? `NOT ` : ``}exactly matching "${item.Exact}"`;
|
||||
return `${invertPrefix}exactly matching "${item.Exact}"${ignoreCaseSuffix}`;
|
||||
case 'Prefix':
|
||||
return `${item.Invert ? `NOT ` : ``}prefixed by "${item.Prefix}"`;
|
||||
return `${invertPrefix}prefixed by "${item.Prefix}"${ignoreCaseSuffix}`;
|
||||
case 'Suffix':
|
||||
return `${item.Invert ? `NOT ` : ``}suffixed by "${item.Suffix}"`;
|
||||
return `${invertPrefix}suffixed by "${item.Suffix}"${ignoreCaseSuffix}`;
|
||||
case 'Contains':
|
||||
return `${invertPrefix}containing "${item.Contains}"${ignoreCaseSuffix}`;
|
||||
case 'Regex':
|
||||
return `${item.Invert ? `NOT ` : ``}matching the regex "${item.Regex}"`;
|
||||
return `${invertPrefix}matching the regex "${item.Regex}"`;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ export const schema = {
|
|||
required: true,
|
||||
},
|
||||
HeaderType: {
|
||||
allowedValues: ['Exact', 'Prefix', 'Suffix', 'Regex', 'Present'],
|
||||
allowedValues: ['Exact', 'Prefix', 'Suffix', 'Contains', 'Regex', 'Present'],
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -23,11 +23,13 @@ export default class IntentionPermission extends Fragment {
|
|||
@attr('string') Exact;
|
||||
@attr('string') Prefix;
|
||||
@attr('string') Suffix;
|
||||
@attr('string') Contains;
|
||||
@attr('string') Regex;
|
||||
// this is a boolean but we don't want it to automatically be set to false
|
||||
@attr() Present;
|
||||
|
||||
@or(...schema.HeaderType.allowedValues) Value;
|
||||
@attr('boolean') IgnoreCase;
|
||||
|
||||
@computed(...schema.HeaderType.allowedValues)
|
||||
get HeaderType() {
|
||||
|
|
|
@ -78,11 +78,15 @@ ${range(headerCount).map(item => `
|
|||
${fake.random.boolean() ? `
|
||||
"Invert": true,
|
||||
` : ``}
|
||||
${fake.random.boolean() ? `
|
||||
"IgnoreCase": true,
|
||||
` : ``}
|
||||
${fake.helpers.randomize([
|
||||
'"Present": true',
|
||||
'"Exact": "abc"',
|
||||
'"Prefix": "abc"',
|
||||
'"Suffix": "xyz"',
|
||||
'"Contains": "abc"',
|
||||
'"Regex": "[abc]"'
|
||||
])}
|
||||
}
|
||||
|
|
|
@ -63,11 +63,15 @@ ${range(headerCount).map(item => `
|
|||
${fake.random.boolean() ? `
|
||||
"Invert": true,
|
||||
` : ``}
|
||||
${fake.random.boolean() ? `
|
||||
"IgnoreCase": true,
|
||||
` : ``}
|
||||
${fake.helpers.randomize([
|
||||
'"Present": true',
|
||||
'"Exact": "abc"',
|
||||
'"Prefix": "abc"',
|
||||
'"Suffix": "xyz"',
|
||||
'"Contains": "abc"',
|
||||
'"Regex": "[abc]"'
|
||||
])}
|
||||
}
|
||||
|
|
|
@ -69,11 +69,15 @@ ${range(headerCount).map(item => `
|
|||
${fake.random.boolean() ? `
|
||||
"Invert": true,
|
||||
` : ``}
|
||||
${fake.random.boolean() ? `
|
||||
"IgnoreCase": true,
|
||||
` : ``}
|
||||
${fake.helpers.randomize([
|
||||
'"Present": true',
|
||||
'"Exact": "abc"',
|
||||
'"Prefix": "abc"',
|
||||
'"Suffix": "xyz"',
|
||||
'"Contains": "abc"',
|
||||
'"Regex": "[abc]"'
|
||||
])}
|
||||
}
|
||||
|
|
|
@ -264,6 +264,58 @@ spec:
|
|||
|
||||
Note that the Kubernetes example does not include a `partition` field. Configuration entries are applied on Kubernetes using [custom resource definitions (CRD)](/consul/docs/k8s/crds), which can only be scoped to their own partition.
|
||||
|
||||
### Request Normalization
|
||||
|
||||
Enable options under `HTTP.Incoming.RequestNormalization` to apply normalization to all inbound traffic to mesh proxies.
|
||||
|
||||
<CodeTabs tabs={[ "HCL", "Kubernetes YAML", "JSON" ]}>
|
||||
|
||||
```hcl
|
||||
Kind = "mesh"
|
||||
HTTP {
|
||||
Incoming {
|
||||
RequestNormalization {
|
||||
InsecureDisablePathNormalization = false // default false, shown for completeness
|
||||
MergeSlashes = true
|
||||
PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD"
|
||||
HeadersWithUnderscoresAction = "REJECT_REQUEST"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```yaml
|
||||
apiVersion: consul.hashicorp.com/v1alpha1
|
||||
kind: Mesh
|
||||
metadata:
|
||||
name: mesh
|
||||
spec:
|
||||
http:
|
||||
incoming:
|
||||
requestNormalization:
|
||||
insecureDisablePathNormalization: false # default false, shown for completeness
|
||||
mergeSlashes: true
|
||||
pathWithEscapedSlashesAction: UNESCAPE_AND_FORWARD
|
||||
headersWithUnderscoresAction: REJECT_REQUEST
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"Kind": "mesh",
|
||||
"HTTP": {
|
||||
"Incoming": {
|
||||
"RequestNormalization": {
|
||||
"InsecureDisablePathNormalization": false,
|
||||
"MergeSlashes": true,
|
||||
"PathWithEscapedSlashesAction": "UNESCAPE_AND_FORWARD",
|
||||
"HeadersWithUnderscoresAction": "REJECT_REQUEST"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTabs>
|
||||
|
||||
## Available Fields
|
||||
|
||||
|
@ -452,6 +504,57 @@ Note that the Kubernetes example does not include a `partition` field. Configura
|
|||
for all Envoy proxies. As a result, Consul will not include the \`x-forwarded-client-cert\` header in the next hop.
|
||||
If set to \`false\` (default), the XFCC header is propagated to upstream applications.`,
|
||||
},
|
||||
{
|
||||
name: 'Incoming',
|
||||
type: 'DirectionalHTTPConfig: <optional>',
|
||||
description: `HTTP configuration for inbound traffic to mesh proxies.`,
|
||||
children: [
|
||||
{
|
||||
name: 'RequestNormalization',
|
||||
type: 'RequestNormalizationConfig: <optional>',
|
||||
description: `Request normalization configuration for inbound traffic to mesh proxies.`,
|
||||
children: [
|
||||
{
|
||||
name: 'InsecureDisablePathNormalization',
|
||||
type: 'bool: false',
|
||||
description: `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.`,
|
||||
},
|
||||
{
|
||||
name: 'MergeSlashes',
|
||||
type: 'bool: false',
|
||||
description: `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.`,
|
||||
},
|
||||
{
|
||||
name: 'PathWithEscapedSlashesAction',
|
||||
type: 'string: ""',
|
||||
description: `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.`,
|
||||
},
|
||||
{
|
||||
name: 'HeadersWithUnderscoresAction',
|
||||
type: 'string: ""',
|
||||
description: `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.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -96,7 +96,9 @@ The following outline shows how to format the service intentions configuration e
|
|||
- [`exact`](#spec-sources-permissions-http-header): string | no default
|
||||
- [`prefix`](#spec-sources-permissions-http-header): string | no default
|
||||
- [`suffix`](#spec-sources-permissions-http-header): string | no default
|
||||
- [`contains`](#spec-sources-permissions-http-header): string | no default
|
||||
- [`regex`](#spec-sources-permissions-http-header): string | no default
|
||||
- [`ignoreCase`](#spec-sources-permissions-http-header): boolean | `false`
|
||||
- [`invert`](#spec-sources-permissions-http-header): boolean | `false`
|
||||
- [`description`](#spec-sources-description): string
|
||||
|
||||
|
@ -156,18 +158,31 @@ Sources = [
|
|||
{
|
||||
Name = "<http header name>" # string
|
||||
Present = <true or false> # boolean
|
||||
Invert = <true or false> # boolean
|
||||
},
|
||||
{
|
||||
Name = "<http header name>" # string
|
||||
Exact = "<header-value>" # boolean
|
||||
IgnoreCase = <true or false> # boolean
|
||||
Invert = <true or false> # boolean
|
||||
},
|
||||
{
|
||||
Name = "<http header name>" # string
|
||||
Prefix = "<source header value prefix>" # string
|
||||
IgnoreCase = <true or false> # boolean
|
||||
Invert = <true or false> # boolean
|
||||
},
|
||||
{
|
||||
Name = "<http header name>" # string
|
||||
Suffix = "<source header value suffix>" # string
|
||||
IgnoreCase = <true or false> # boolean
|
||||
Invert = <true or false> # boolean
|
||||
},
|
||||
{
|
||||
Name = "<http header name>" # string
|
||||
Contains = "<value to search for>" # string
|
||||
IgnoreCase = <true or false> # boolean
|
||||
Invert = <true or false> # boolean
|
||||
},
|
||||
{
|
||||
Name = "<http header name>" # string
|
||||
|
@ -227,12 +242,23 @@ spec:
|
|||
header:
|
||||
- name: <http header name>
|
||||
present: true
|
||||
invert: false
|
||||
- name: <http header name>
|
||||
exact: false
|
||||
exact: <header-value>
|
||||
ignoreCase: false
|
||||
invert: false
|
||||
- name: <http header name>
|
||||
prefix: <source header value prefix>
|
||||
ignoreCase: false
|
||||
invert: false
|
||||
- name: <http header name>
|
||||
suffix: <source header value suffix>
|
||||
ignoreCase: false
|
||||
invert: false
|
||||
- name: <http header name>
|
||||
contains: <value to search for>
|
||||
ignoreCase: false
|
||||
invert: false
|
||||
- name: <http header name>
|
||||
regex: <regex pattern to match>
|
||||
invert: false
|
||||
|
@ -287,19 +313,32 @@ spec:
|
|||
"Header":[
|
||||
{
|
||||
"Name":"<http header name>",
|
||||
"Present":true
|
||||
"Present":true,
|
||||
"Invert":false
|
||||
},
|
||||
{
|
||||
"Name":"<http header name>",
|
||||
"Exact":false
|
||||
"Exact":"<header-value>",
|
||||
"IgnoreCase":false,,
|
||||
"Invert":false
|
||||
},
|
||||
{
|
||||
"Name":"<http header name>",
|
||||
"Prefix":"<source header value prefix>"
|
||||
"Prefix":"<source header value prefix>",
|
||||
"IgnoreCase":false,
|
||||
"Invert":false
|
||||
},
|
||||
{
|
||||
"Name":"<http header name>",
|
||||
"Suffix":"<source header value suffix>"
|
||||
"Suffix":"<source header value suffix>",
|
||||
"IgnoreCase":false,
|
||||
"Invert":false
|
||||
},
|
||||
{
|
||||
"Name":"<http header name>",
|
||||
"Contains":"<value to search for>",
|
||||
"IgnoreCase":false,
|
||||
"Invert":false
|
||||
},
|
||||
{
|
||||
"Name":"<http header name>",
|
||||
|
@ -923,16 +962,22 @@ Specifies a set of criteria for matching HTTP request headers. The request heade
|
|||
- Default: None
|
||||
- Data type: List of maps
|
||||
|
||||
Each member of the `header` list is a map that contains a `name` field and at least one match criterion. The following table describes the parameters that each member of the `header` list may contain:
|
||||
Each member of the `header` list is a map that contains a `name` field and at least one match criterion.
|
||||
|
||||
~> **Warning**: If it is possible for a header to contain multiple values, we recommend using `contains` or `regex` rather than `exact`, `prefix`, or `suffix`. Envoy internally concatenates multiple header values into a single CSV value prior to applying match rules, which may result in match rules that depend on the beginning or end of a string vulnerable to circumvention. A more robust alternative is using `contains` or, if a stricter value match is required, configuring a regex pattern that is tolerant of comma-separated values.
|
||||
|
||||
The following table describes the parameters that each member of the `header` list may contain:
|
||||
|
||||
| Parameter | Description | Data type | Required |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | Specifies the name of the header to match. | string | required |
|
||||
| `present` | Enables a match if the header configured in the `name` field appears in the request. Consul matches on any value as long as the header key appears in the request. Do not specify `present` if `exact`, `prefix`, `suffix`, or `regex` are configured in the same `header` configuration. | boolean | optional |
|
||||
| `Exact` | Specifies a value for the header key set in the `Name` field. If the request header value matches the `exact` value, Consul applies the permission. Do not specify `exact` if `present`, `prefix`, `suffix`, or `regex` are configured in the same `header` configuration. | string | optional |
|
||||
| `prefix` | Specifies a prefix value for the header key set in the `name` field. If the request header value starts with the `prefix` value, Consul applies the permission. Do not specify `prefix` if `present`, `exact`, `suffix`, or `regex` are configured in the same `header` configuration. | string | optional |
|
||||
| `suffix` | Specifies a suffix value for the header key set in the `name` field. If the request header value ends with the `suffix` value, Consul applies the permission. Do not specify `suffix` if `present`, `exact`, `prefix`, or `regex` are configured in the same `header` configuration. | string | optional |
|
||||
| `regex` | Specifies a regular expression pattern as the value for the header key set in the `name` field. If the request header value matches the regex, Consul applies the permission. Do not specify `regex` if `present`, `exact`, `prefix`, or `suffix` are configured in the same `header` configuration. The regex syntax is proxy-specific. If using Envoy, refer to the [re2 documentation](https://github.com/google/re2/wiki/Syntax) for details. | string | optional |
|
||||
| `present` | Enables a match if the header configured in the `name` field appears in the request. Consul matches on any value as long as the header key appears in the request. Do not specify `present` if `exact`, `prefix`, `suffix`, `contains`, or `regex` are configured in the same `header` configuration. | boolean | optional |
|
||||
| `Exact` | Specifies a value for the header key set in the `Name` field. If the request header value matches the `exact` value, Consul applies the permission. Do not specify `exact` if `present`, `prefix`, `suffix`, `contains`, or `regex` are configured in the same `header` configuration. | string | optional |
|
||||
| `prefix` | Specifies a prefix value for the header key set in the `name` field. If the request header value starts with the `prefix` value, Consul applies the permission. Do not specify `prefix` if `present`, `exact`, `suffix`, `contains`, or `regex` are configured in the same `header` configuration. | string | optional |
|
||||
| `suffix` | Specifies a suffix value for the header key set in the `name` field. If the request header value ends with the `suffix` value, Consul applies the permission. Do not specify `suffix` if `present`, `exact`, `prefix`, `contains`, or `regex` are configured in the same `header` configuration. | string | optional |
|
||||
| `contains` | Specifies a contains value for the header key set in the `name` field. If the request header value includes the `contains` value, Consul applies the permission. Do not specify `contains` if `present`, `exact`, `prefix`, `suffix`, or `regex` are configured in the same `header` configuration. | string | optional |
|
||||
| `regex` | Specifies a regular expression pattern as the value for the header key set in the `name` field. If the request header value matches the regex, Consul applies the permission. Do not specify `regex` if `present`, `exact`, `prefix`, `suffix`, or `contains` are configured in the same `header` configuration. The regex syntax is proxy-specific. If using Envoy, refer to the [re2 documentation](https://github.com/google/re2/wiki/Syntax) for details. | string | optional |
|
||||
| `ignoreCase` | Ignores the case of the provided header value when matching with exact, prefix, suffix, or contains. Default is `false`. | boolean | optional |
|
||||
| `invert` | Inverts the matching logic configured in the `header`. Default is `false`. | boolean | optional |
|
||||
|
||||
### `spec.sources[].type`
|
||||
|
|
|
@ -36,7 +36,11 @@ application](/consul/docs/connect/native) enforces intentions on inbound connect
|
|||
|
||||
L4 intentions mediate the ability to establish new connections. Modifying an intention does not have an effect on existing connections. As a result, changing a connection from `allow` to `deny` does not sever the connection.
|
||||
|
||||
L7 intentions mediate the ability to issue new requests. When an intention is modified, requests received after the modification use the latest intention rules to enforce access. Changing a connection from `allow` to `deny` does not sever the connection, but doing so blocks new requests from being processed.
|
||||
L7 intentions mediate the ability to issue new requests. When an intention is modified, requests received after the modification use the latest intention rules to enforce access. Changing a connection from `allow` to `deny` does not sever the connection, but doing so blocks new requests from being processed.
|
||||
|
||||
When using L7 intentions, we recommend that you review and update the [Mesh request normalization configuration](/consul/docs/connect/security#request-normalization-and-configured) to avoid unintended match rule circumvention. More details are available in the [Mesh configuration entry reference](/consul/docs/connect/config-entries/mesh#request-normalization).
|
||||
|
||||
When you use L7 intentions with header matching and it is possible for a header to contain multiple values, we recommend using `contains` or `regex` instead of `exact`, `prefix`, or `suffix`. For more information, refer to the [service intentions configuration entry reference](/consul/docs/connect/config-entries/service-intentions#spec-sources-permissions-http-header).
|
||||
|
||||
### Caching
|
||||
|
||||
|
|
|
@ -32,10 +32,38 @@ Consul should be configured with a default deny intention policy. This forces
|
|||
all service-to-service communication to be explicitly
|
||||
allowed via an allow [intention](/consul/docs/connect/intentions).
|
||||
|
||||
One advantage of using a default deny policy in combination with specific "allow" rules
|
||||
is that a failure of intentions due to misconfiguration always results in
|
||||
_denied_ traffic, rather than unwanted _allowed_ traffic.
|
||||
|
||||
In the absence of `default_intention_policy` Consul will fall back to the ACL
|
||||
default policy when determining whether to allow or deny communications without
|
||||
an explicit intention.
|
||||
|
||||
### Request Normalization Configured for L7 Intentions
|
||||
|
||||
Atypical traffic patterns may interfere with the enforcement of L7 intentions. For
|
||||
example, if a service makes request to a non-normalized URI path and Consul is not
|
||||
configured to force path normalization, it becomes possible to circumvent path match rules. While a
|
||||
default deny policy can limit the impact of this issue, we still recommend
|
||||
that you review your current request normalization configuration. Normalization is critical to avoid unwanted
|
||||
traffic, especially when using unrecommended security options such as a default allow intentions policy.
|
||||
|
||||
Consul adopts a default normalization mode that adheres to [RFC 3986](
|
||||
https://tools.ietf.org/html/rfc3986#section-6), but additional options to enable stricter
|
||||
normalization are available in the cluster-wide [Mesh configuration entry](
|
||||
/consul/docs/connect/config-entries/mesh). We recommend reviewing these options and
|
||||
enabling the strictest set that does not interfere with application traffic.
|
||||
|
||||
We also recommend that you review L7 intention header match rules for potential
|
||||
issues with multiple header values. Refer to the [service intentions
|
||||
configuration entry reference](/consul/docs/connect/config-entries/service-intentions#spec-sources-permissions-http-header)
|
||||
for more information.
|
||||
|
||||
You do not need to enable request normalization if you are not using L7 intentions.
|
||||
However, normalization may also benefit the use of other service mesh features that
|
||||
rely on L7 attribute matching, such as [service routers](/consul/docs/connect/manage-traffic#routing).
|
||||
|
||||
### ACLs Enabled with Default Deny
|
||||
|
||||
Consul must be configured to use ACLs with a default deny policy. This forces
|
||||
|
@ -51,6 +79,10 @@ this. **If ACLs are not enabled**, deny intentions will still be enforced, but a
|
|||
may edit intentions. This renders the security of the created intentions
|
||||
effectively useless.
|
||||
|
||||
The advantage of a default deny policy in combination with specific "allow" rules
|
||||
is that at worst, a failure of intentions due to misconfiguration will result in
|
||||
_denied_ traffic, rather than unwanted _allowed_ traffic.
|
||||
|
||||
### TCP and UDP Encryption Enabled
|
||||
|
||||
TCP and UDP encryption must be enabled to prevent plaintext communication
|
||||
|
|
|
@ -26,6 +26,10 @@ environment, but the general mechanisms for a secure Consul deployment revolve a
|
|||
[authentication methods](/consul/docs/security/acl/auth-methods) can be used to enable trusted external parties to authorize
|
||||
ACL token creation.
|
||||
|
||||
- **Intentions** - If in use, configure service intentions to use a default-deny policy. If L7 intentions are
|
||||
in use, enable [Mesh request normalization](/consul/docs/connect/config-entries/mesh#request-normalization)
|
||||
and review your [header match rules](/consul/docs/connect/config-entries/service-intentions#spec-sources-permissions-http-header) to prevent malformed requests from bypassing intentions.
|
||||
|
||||
- **Namespaces** <EnterpriseAlert inline /> - Read and write operations can be scoped to a logical namespace to restrict
|
||||
access to Consul components within a multi-tenant environment.
|
||||
|
||||
|
@ -178,6 +182,13 @@ environment and adapt these configurations accordingly.
|
|||
- **🏷 Namespace** <EnterpriseAlert inline /> - a named, logical scoping of Consul Enterprise resources, typically to
|
||||
enable multi-tenant environments. Consul CE clusters always operate within the "default" namespace.
|
||||
|
||||
- **Intentions** - Service intentions control traffic communication between services at the network layer (L4) and
|
||||
application layer (L7). If in use, we strongly recommend configuring intentions to use a default-deny policy.
|
||||
When L7 intentions are in use, review your configuration for [Mesh request normalization](/consul/docs/connect/config-entries/mesh#request-normalization)
|
||||
and use the strictest set of options suitable to your environment. At minimum, we
|
||||
recommend keeping path normalization enabled, because this default setting prevents requests that do not conform to [RFC 3986](
|
||||
https://tools.ietf.org/html/rfc3986#section-6) from bypassing path match rules.
|
||||
|
||||
- **Gossip Encryption** - A shared, base64-encoded 32-byte symmetric key is required to [encrypt Serf gossip
|
||||
communication](/consul/tutorials/security/gossip-encryption-secure?utm_source=consul.io&utm_medium=docs) within a cluster using
|
||||
AES GCM. The key size determines which AES encryption types to use; 16, 24, or 32 bytes to select AES-128, AES-192,
|
||||
|
@ -252,6 +263,10 @@ environment and adapt these configurations accordingly.
|
|||
}
|
||||
```
|
||||
|
||||
- **Customize Mesh HTTP Request Normalization** - If L7 intentions are in use, we recommend configuring request normalization to
|
||||
avoid match rule circumvention. Other normalization options, such as dropping or rejecting headers with underscores,
|
||||
may also be appropriate depending on your requirements. Review the options in the [Mesh configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization) to determine the appropriate settings for your use case.
|
||||
|
||||
- **Customize Default Limits** - Consul has a number of builtin features with default connection limits that should be
|
||||
tuned to fit your environment.
|
||||
|
||||
|
|
|
@ -43,6 +43,15 @@ The Kubernetes-only legacy API gateway is superseded by the modern, multi-runtim
|
|||
[API gateway](/consul/docs/connect/config-entries/api-gateway).
|
||||
On Kubernetes, the modern API gateway is associated with the `connectInject.apiGateway` stanza.
|
||||
|
||||
### Mesh traffic request path normalization enabled by default
|
||||
|
||||
As of Consul v1.19.3, inbound traffic to mesh proxies will have Envoy request [path normalization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path) applied by default. This should not interfere with the majority of service traffic, but can be disabled if needed by setting `http.incoming.request_normalization.insecure_disable_path_normalization` to `true` in the [global `mesh` configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization). This setting is generally safe to change if not using L7 intentions with path matching.
|
||||
|
||||
## Consul 1.18.x
|
||||
|
||||
### Mesh traffic request path normalization enabled by default
|
||||
|
||||
As of Consul v1.18.5, inbound traffic to mesh proxies will have Envoy request [path normalization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path) applied by default. This should not interfere with the majority of service traffic, but can be disabled if needed by setting `http.incoming.request_normalization.insecure_disable_path_normalization` to `true` in the [global `mesh` configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization). This setting is generally safe to change if not using L7 intentions with path matching.
|
||||
|
||||
## Consul 1.17.x
|
||||
|
||||
|
@ -65,6 +74,10 @@ service-defaults are configured in each partition and namespace before upgrading
|
|||
#### ACL tokens with templated policies
|
||||
[ACL templated policies](/consul/docs/security/acl#templated-policies) were added to 1.17.0 to simplify obtaining the right permissions for ACL tokens. When performing a [rolling upgrade](/consul/tutorials/datacenter-operations/upgrade-federated-environment#server-rolling-upgrade) and a version of Consul prior to 1.17.x is presented with a token created Consul v1.17.x or newer that contains templated policies, the templated policies field is not recognized. As a result, the token might not have the expected permissions on the older version of Consul.
|
||||
|
||||
### Mesh traffic request path normalization enabled by default
|
||||
|
||||
As of Consul v1.17.8, inbound traffic to mesh proxies will have Envoy request [path normalization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path) applied by default. This should not interfere with the majority of service traffic, but can be disabled if needed by setting `http.incoming.request_normalization.insecure_disable_path_normalization` to `true` in the [global `mesh` configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization). This setting is generally safe to change if not using L7 intentions with path matching.
|
||||
|
||||
## Consul 1.16.x
|
||||
|
||||
### Known issues
|
||||
|
@ -241,6 +254,10 @@ In Consul v1.15 and higher:
|
|||
|
||||
</CodeBlockConfig>
|
||||
|
||||
### Mesh traffic request path normalization enabled by default
|
||||
|
||||
As of Consul v1.15.15, inbound traffic to mesh proxies will have Envoy request [path normalization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path) applied by default. This should not interfere with the majority of service traffic, but can be disabled if needed by setting `http.incoming.request_normalization.insecure_disable_path_normalization` to `true` in the [global `mesh` configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization). This setting is generally safe to change if not using L7 intentions with path matching.
|
||||
|
||||
## Consul 1.14.x
|
||||
|
||||
### Service Mesh Compatibility
|
||||
|
|
Loading…
Reference in New Issue