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 {
|
type IntentionHTTPHeaderPermission struct {
|
||||||
Name string
|
Name string
|
||||||
Present bool `json:",omitempty"`
|
Present bool `json:",omitempty"`
|
||||||
Exact string `json:",omitempty"`
|
Exact string `json:",omitempty"`
|
||||||
Prefix string `json:",omitempty"`
|
Prefix string `json:",omitempty"`
|
||||||
Suffix string `json:",omitempty"`
|
Suffix string `json:",omitempty"`
|
||||||
Regex string `json:",omitempty"`
|
Contains string `json:",omitempty"`
|
||||||
Invert bool `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 {
|
func cloneStringStringMap(m map[string]string) map[string]string {
|
||||||
|
@ -880,8 +882,14 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
|
||||||
if hdr.Suffix != "" {
|
if hdr.Suffix != "" {
|
||||||
hdrParts++
|
hdrParts++
|
||||||
}
|
}
|
||||||
|
if hdr.Contains != "" {
|
||||||
|
hdrParts++
|
||||||
|
}
|
||||||
if hdrParts != 1 {
|
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++
|
permParts++
|
||||||
}
|
}
|
||||||
|
|
|
@ -574,323 +574,164 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] missing required Name field`,
|
validateErr: `Sources[0].Permissions[0].HTTP.Header[0] missing required Name field`,
|
||||||
},
|
},
|
||||||
"permission header has too many parts (1)": {
|
"permission header has too many parts (1)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
// Present: true,
|
||||||
{
|
Exact: "foo",
|
||||||
Name: "foo",
|
// Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
// Prefix: "foo",
|
||||||
{
|
Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
// Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
Present: true,
|
|
||||||
Exact: "foo",
|
|
||||||
// Regex: "foo",
|
|
||||||
// Prefix: "foo",
|
|
||||||
// Suffix: "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)": {
|
"permission header has too many parts (2)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
// Present: true,
|
||||||
{
|
// Exact: "foo",
|
||||||
Name: "foo",
|
Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
Prefix: "foo",
|
||||||
{
|
// Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
// Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
Present: true,
|
|
||||||
// Exact: "foo",
|
|
||||||
Regex: "foo",
|
|
||||||
// Prefix: "foo",
|
|
||||||
// Suffix: "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)": {
|
"permission header has too many parts (3)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
// Present: true,
|
||||||
{
|
// Exact: "foo",
|
||||||
Name: "foo",
|
// Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
Prefix: "foo",
|
||||||
{
|
Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
// Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
Present: true,
|
|
||||||
// Exact: "foo",
|
|
||||||
// Regex: "foo",
|
|
||||||
Prefix: "foo",
|
|
||||||
// Suffix: "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)": {
|
"permission header has too many parts (4)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
// Present: true,
|
||||||
{
|
Exact: "foo",
|
||||||
Name: "foo",
|
// Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
Prefix: "foo",
|
||||||
{
|
Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
// Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
Present: true,
|
|
||||||
// Exact: "foo",
|
|
||||||
// Regex: "foo",
|
|
||||||
// Prefix: "foo",
|
|
||||||
Suffix: "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)": {
|
"permission header has too many parts (5)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
Present: true,
|
||||||
{
|
Exact: "foo",
|
||||||
Name: "foo",
|
Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
Prefix: "foo",
|
||||||
{
|
Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
// Present: true,
|
|
||||||
Exact: "foo",
|
|
||||||
Regex: "foo",
|
|
||||||
// Prefix: "foo",
|
|
||||||
// Suffix: "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)": {
|
"permission header has too many parts (6)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
Present: true,
|
||||||
{
|
Exact: "foo",
|
||||||
Name: "foo",
|
// Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
Prefix: "foo",
|
||||||
{
|
Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
// Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
// Present: true,
|
|
||||||
Exact: "foo",
|
|
||||||
// Regex: "foo",
|
|
||||||
Prefix: "foo",
|
|
||||||
// Suffix: "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)": {
|
"permission header has too many parts (7)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
// Present: true,
|
||||||
{
|
Exact: "foo",
|
||||||
Name: "foo",
|
// Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
Prefix: "foo",
|
||||||
{
|
Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
// Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
// Present: true,
|
|
||||||
Exact: "foo",
|
|
||||||
// Regex: "foo",
|
|
||||||
// Prefix: "foo",
|
|
||||||
Suffix: "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)": {
|
"permission header has too many parts (8)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
// Present: true,
|
||||||
{
|
// Exact: "foo",
|
||||||
Name: "foo",
|
Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
Prefix: "foo",
|
||||||
{
|
// Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
// Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
// Present: true,
|
|
||||||
// Exact: "foo",
|
|
||||||
Regex: "foo",
|
|
||||||
Prefix: "foo",
|
|
||||||
// Suffix: "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)": {
|
"permission header has too many parts (9)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
// Present: true,
|
||||||
{
|
// Exact: "foo",
|
||||||
Name: "foo",
|
Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
// Prefix: "foo",
|
||||||
{
|
Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
// Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
// Present: true,
|
|
||||||
// Exact: "foo",
|
|
||||||
Regex: "foo",
|
|
||||||
// Prefix: "foo",
|
|
||||||
Suffix: "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)": {
|
"permission header has too many parts (10)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
// Present: true,
|
||||||
{
|
// Exact: "foo",
|
||||||
Name: "foo",
|
// Regex: "foo",
|
||||||
Permissions: []*IntentionPermission{
|
Prefix: "foo",
|
||||||
{
|
Suffix: "foo",
|
||||||
Action: IntentionActionAllow,
|
// Contains: "foo",
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
// Present: true,
|
|
||||||
// Exact: "foo",
|
|
||||||
// Regex: "foo",
|
|
||||||
Prefix: "foo",
|
|
||||||
Suffix: "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)": {
|
"permission header invalid ignore case (1)": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{
|
||||||
Kind: ServiceIntentions,
|
{
|
||||||
Name: "test",
|
Name: "x-abc",
|
||||||
Sources: []*SourceIntention{
|
Present: true,
|
||||||
{
|
IgnoreCase: true,
|
||||||
Name: "foo",
|
|
||||||
Permissions: []*IntentionPermission{
|
|
||||||
{
|
|
||||||
Action: IntentionActionAllow,
|
|
||||||
HTTP: &IntentionHTTPPermission{
|
|
||||||
Header: []IntentionHTTPHeaderPermission{
|
|
||||||
{
|
|
||||||
Name: "x-abc",
|
|
||||||
Present: true,
|
|
||||||
Exact: "foo",
|
|
||||||
Regex: "foo",
|
|
||||||
Prefix: "foo",
|
|
||||||
Suffix: "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 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": {
|
"permission invalid method": {
|
||||||
entry: &ServiceIntentionsConfigEntry{
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/types"
|
"github.com/hashicorp/consul/types"
|
||||||
|
@ -72,6 +73,17 @@ type MeshDirectionalTLSConfig struct {
|
||||||
|
|
||||||
type MeshHTTPConfig struct {
|
type MeshHTTPConfig struct {
|
||||||
SanitizeXForwardedClientCert bool `alias:"sanitize_x_forwarded_client_cert"`
|
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.
|
// PeeringMeshConfig contains cluster-wide options pertaining to peering.
|
||||||
|
@ -84,6 +96,104 @@ type PeeringMeshConfig struct {
|
||||||
PeerThroughMeshGateways bool `alias:"peer_through_mesh_gateways"`
|
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 {
|
func (e *MeshConfigEntry) GetKind() string {
|
||||||
return MeshConfig
|
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()
|
return e.validateEnterpriseMeta()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,6 +307,61 @@ func (e *MeshConfigEntry) PeerThroughMeshGateways() bool {
|
||||||
return e.Peering.PeerThroughMeshGateways
|
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 {
|
func validateMeshDirectionalTLSConfig(cfg *MeshDirectionalTLSConfig) error {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -237,3 +406,36 @@ func validateTLSConfig(
|
||||||
|
|
||||||
return nil
|
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"
|
name = "hdr-suffix"
|
||||||
suffix = "suffix"
|
suffix = "suffix"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name = "hdr-contains"
|
||||||
|
contains = "contains"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name = "hdr-regex"
|
name = "hdr-regex"
|
||||||
regex = "regex"
|
regex = "regex"
|
||||||
|
@ -1918,7 +1922,12 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
name = "hdr-absent"
|
name = "hdr-absent"
|
||||||
present = true
|
present = true
|
||||||
invert = true
|
invert = true
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name = "hdr-ignore-case"
|
||||||
|
exact = "exact"
|
||||||
|
ignore_case = true
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1987,6 +1996,10 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
Name = "hdr-suffix"
|
Name = "hdr-suffix"
|
||||||
Suffix = "suffix"
|
Suffix = "suffix"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name = "hdr-contains"
|
||||||
|
Contains = "contains"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name = "hdr-regex"
|
Name = "hdr-regex"
|
||||||
Regex = "regex"
|
Regex = "regex"
|
||||||
|
@ -1995,6 +2008,11 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
Name = "hdr-absent"
|
Name = "hdr-absent"
|
||||||
Present = true
|
Present = true
|
||||||
Invert = true
|
Invert = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name = "hdr-ignore-case"
|
||||||
|
Exact = "exact"
|
||||||
|
IgnoreCase = true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2064,6 +2082,10 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
Name: "hdr-suffix",
|
Name: "hdr-suffix",
|
||||||
Suffix: "suffix",
|
Suffix: "suffix",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "hdr-contains",
|
||||||
|
Contains: "contains",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "hdr-regex",
|
Name: "hdr-regex",
|
||||||
Regex: "regex",
|
Regex: "regex",
|
||||||
|
@ -2073,6 +2095,11 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
Present: true,
|
Present: true,
|
||||||
Invert: 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: `
|
snake: `
|
||||||
kind = "mesh"
|
kind = "mesh"
|
||||||
meta {
|
meta {
|
||||||
|
@ -2145,6 +2172,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
mesh_destinations_only = true
|
mesh_destinations_only = true
|
||||||
}
|
}
|
||||||
allow_enabling_permissive_mutual_tls = true
|
allow_enabling_permissive_mutual_tls = true
|
||||||
|
validate_clusters = true
|
||||||
tls {
|
tls {
|
||||||
incoming {
|
incoming {
|
||||||
tls_min_version = "TLSv1_1"
|
tls_min_version = "TLSv1_1"
|
||||||
|
@ -2163,9 +2191,17 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http {
|
http {
|
||||||
sanitize_x_forwarded_client_cert = true
|
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 {
|
peering {
|
||||||
peer_through_mesh_gateways = true
|
peer_through_mesh_gateways = true
|
||||||
}
|
}
|
||||||
|
@ -2180,6 +2216,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
MeshDestinationsOnly = true
|
MeshDestinationsOnly = true
|
||||||
}
|
}
|
||||||
AllowEnablingPermissiveMutualTLS = true
|
AllowEnablingPermissiveMutualTLS = true
|
||||||
|
ValidateClusters = true
|
||||||
TLS {
|
TLS {
|
||||||
Incoming {
|
Incoming {
|
||||||
TLSMinVersion = "TLSv1_1"
|
TLSMinVersion = "TLSv1_1"
|
||||||
|
@ -2198,9 +2235,17 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HTTP {
|
HTTP {
|
||||||
SanitizeXForwardedClientCert = true
|
SanitizeXForwardedClientCert = true
|
||||||
}
|
Incoming {
|
||||||
|
RequestNormalization {
|
||||||
|
InsecureDisablePathNormalization = true
|
||||||
|
MergeSlashes = true
|
||||||
|
PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD"
|
||||||
|
HeadersWithUnderscoresAction = "DROP_HEADER"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Peering {
|
Peering {
|
||||||
PeerThroughMeshGateways = true
|
PeerThroughMeshGateways = true
|
||||||
}
|
}
|
||||||
|
@ -2214,6 +2259,7 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
MeshDestinationsOnly: true,
|
MeshDestinationsOnly: true,
|
||||||
},
|
},
|
||||||
AllowEnablingPermissiveMutualTLS: true,
|
AllowEnablingPermissiveMutualTLS: true,
|
||||||
|
ValidateClusters: true,
|
||||||
TLS: &MeshTLSConfig{
|
TLS: &MeshTLSConfig{
|
||||||
Incoming: &MeshDirectionalTLSConfig{
|
Incoming: &MeshDirectionalTLSConfig{
|
||||||
TLSMinVersion: types.TLSv1_1,
|
TLSMinVersion: types.TLSv1_1,
|
||||||
|
@ -2234,6 +2280,14 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
},
|
},
|
||||||
HTTP: &MeshHTTPConfig{
|
HTTP: &MeshHTTPConfig{
|
||||||
SanitizeXForwardedClientCert: true,
|
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{
|
Peering: &PeeringMeshConfig{
|
||||||
PeerThroughMeshGateways: true,
|
PeerThroughMeshGateways: true,
|
||||||
|
|
|
@ -809,6 +809,14 @@ func (o *MeshConfigEntry) DeepCopy() *MeshConfigEntry {
|
||||||
if o.HTTP != nil {
|
if o.HTTP != nil {
|
||||||
cp.HTTP = new(MeshHTTPConfig)
|
cp.HTTP = new(MeshHTTPConfig)
|
||||||
*cp.HTTP = *o.HTTP
|
*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 {
|
if o.Peering != nil {
|
||||||
cp.Peering = new(PeeringMeshConfig)
|
cp.Peering = new(PeeringMeshConfig)
|
||||||
|
|
|
@ -1396,6 +1396,7 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
|
||||||
filterOpts.httpAuthzFilters = append(filterOpts.httpAuthzFilters, addMeta)
|
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.
|
// 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
|
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) {
|
func makePermissiveFilterChain(cfgSnap *proxycfg.ConfigSnapshot, opts listenerFilterOpts) (*envoy_listener_v3.FilterChain, error) {
|
||||||
servicePort := cfgSnap.Proxy.LocalServicePort
|
servicePort := cfgSnap.Proxy.LocalServicePort
|
||||||
if servicePort <= 0 {
|
if servicePort <= 0 {
|
||||||
|
@ -2365,16 +2388,20 @@ type listenerFilterOpts struct {
|
||||||
statPrefix string
|
statPrefix string
|
||||||
|
|
||||||
// HTTP listener filter options
|
// HTTP listener filter options
|
||||||
forwardClientDetails bool
|
forwardClientDetails bool
|
||||||
forwardClientPolicy envoy_http_v3.HttpConnectionManager_ForwardClientCertDetails
|
forwardClientPolicy envoy_http_v3.HttpConnectionManager_ForwardClientCertDetails
|
||||||
httpAuthzFilters []*envoy_http_v3.HttpFilter
|
httpAuthzFilters []*envoy_http_v3.HttpFilter
|
||||||
idleTimeoutMs *int
|
idleTimeoutMs *int
|
||||||
requestTimeoutMs *int
|
requestTimeoutMs *int
|
||||||
routeName string
|
routeName string
|
||||||
routePath string
|
routePath string
|
||||||
tracing *envoy_http_v3.HttpConnectionManager_Tracing
|
tracing *envoy_http_v3.HttpConnectionManager_Tracing
|
||||||
useRDS bool
|
normalizePath bool
|
||||||
fetchTimeoutRDS *durationpb.Duration
|
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) {
|
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
|
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.useRDS {
|
||||||
if opts.cluster != "" {
|
if opts.cluster != "" {
|
||||||
return nil, fmt.Errorf("cannot specify cluster name when using RDS")
|
return nil, fmt.Errorf("cannot specify cluster name when using RDS")
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"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"
|
testinf "github.com/mitchellh/go-testing-interface"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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{
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
||||||
Exact: hdr.Exact,
|
Exact: hdr.Exact,
|
||||||
},
|
},
|
||||||
IgnoreCase: false,
|
IgnoreCase: hdr.IgnoreCase,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case hdr.Regex != "":
|
case hdr.Regex != "":
|
||||||
|
@ -1259,7 +1259,7 @@ func convertPermission(perm *structs.IntentionPermission) *envoy_rbac_v3.Permiss
|
||||||
MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{
|
MatchPattern: &envoy_matcher_v3.StringMatcher_SafeRegex{
|
||||||
SafeRegex: response.MakeEnvoyRegexMatch(hdr.Regex),
|
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{
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{
|
||||||
Prefix: hdr.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{
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Suffix{
|
||||||
Suffix: hdr.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-bar", Exact: "xyz"},
|
||||||
{Name: "x-dib", Prefix: "gaz"},
|
{Name: "x-dib", Prefix: "gaz"},
|
||||||
{Name: "x-gir", Suffix: "zim"},
|
{Name: "x-gir", Suffix: "zim"},
|
||||||
|
{Name: "x-baz", Contains: "qux"},
|
||||||
{Name: "x-zim", Regex: "gi[rR]"},
|
{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-foo", Present: true, Invert: true},
|
||||||
{Name: "z-bar", Exact: "xyz", Invert: true},
|
{Name: "z-bar", Exact: "xyz", Invert: true},
|
||||||
{Name: "z-dib", Prefix: "gaz", Invert: true},
|
{Name: "z-dib", Prefix: "gaz", Invert: true},
|
||||||
{Name: "z-gir", Suffix: "zim", Invert: true},
|
{Name: "z-gir", Suffix: "zim", Invert: true},
|
||||||
|
{Name: "z-baz", Contains: "qux", Invert: true},
|
||||||
{Name: "z-zim", Regex: "gi[rR]", Invert: true},
|
{Name: "z-zim", Regex: "gi[rR]", Invert: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -825,15 +833,25 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) {
|
||||||
Action: structs.IntentionActionDeny,
|
Action: structs.IntentionActionDeny,
|
||||||
HTTP: &structs.IntentionHTTPPermission{
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
Header: []structs.IntentionHTTPHeaderPermission{
|
Header: []structs.IntentionHTTPHeaderPermission{
|
||||||
|
// Valid vanilla match options
|
||||||
{Name: "x-foo", Present: true},
|
{Name: "x-foo", Present: true},
|
||||||
{Name: "x-bar", Exact: "xyz"},
|
{Name: "x-bar", Exact: "xyz"},
|
||||||
{Name: "x-dib", Prefix: "gaz"},
|
{Name: "x-dib", Prefix: "gaz"},
|
||||||
{Name: "x-gir", Suffix: "zim"},
|
{Name: "x-gir", Suffix: "zim"},
|
||||||
|
{Name: "x-baz", Contains: "qux"},
|
||||||
{Name: "x-zim", Regex: "gi[rR]"},
|
{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-foo", Present: true, Invert: true},
|
||||||
{Name: "z-bar", Exact: "xyz", Invert: true},
|
{Name: "z-bar", Exact: "xyz", Invert: true},
|
||||||
{Name: "z-dib", Prefix: "gaz", Invert: true},
|
{Name: "z-dib", Prefix: "gaz", Invert: true},
|
||||||
{Name: "z-gir", Suffix: "zim", Invert: true},
|
{Name: "z-gir", Suffix: "zim", Invert: true},
|
||||||
|
{Name: "z-baz", Contains: "qux", Invert: true},
|
||||||
{Name: "z-zim", Regex: "gi[rR]", Invert: true},
|
{Name: "z-zim", Regex: "gi[rR]", Invert: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1835,6 +1835,58 @@ func getCustomConfigurationGoldenTestCases(enterprise bool) []goldenTestCase {
|
||||||
}, nil)
|
}, 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": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -248,4 +249,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,6 +173,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -249,4 +250,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -272,4 +273,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -270,6 +270,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -346,4 +347,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,6 +209,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -285,4 +286,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,6 +164,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -240,4 +241,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,6 +200,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -276,4 +277,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,6 +274,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -350,4 +351,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,6 +200,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -276,4 +277,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -231,4 +232,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,6 +191,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -267,4 +268,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,6 +191,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -267,4 +268,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,6 +205,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -281,4 +282,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -231,4 +232,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -231,4 +232,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -231,4 +232,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -231,4 +232,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -231,4 +232,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -231,4 +232,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,6 +157,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -234,4 +235,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -232,4 +233,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,6 +157,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -233,4 +234,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,6 +157,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -234,4 +235,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -232,4 +233,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,6 +178,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -254,4 +255,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -231,4 +232,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,6 +203,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -302,4 +303,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,6 +176,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -252,4 +253,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,6 +181,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -257,4 +258,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"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": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -254,4 +255,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -176,4 +177,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -165,4 +166,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -233,4 +234,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -150,4 +151,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -231,4 +232,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"normalizePath": true,
|
||||||
"routeConfig": {
|
"routeConfig": {
|
||||||
"name": "public_listener",
|
"name": "public_listener",
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
|
@ -233,4 +234,4 @@
|
||||||
],
|
],
|
||||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
"versionInfo": "00000001"
|
"versionInfo": "00000001"
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"name": "x-baz",
|
||||||
|
"stringMatch": {
|
||||||
|
"contains": "qux"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"header": {
|
"header": {
|
||||||
"name": "x-zim",
|
"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": {
|
"header": {
|
||||||
"invertMatch": true,
|
"invertMatch": true,
|
||||||
|
@ -158,6 +202,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"invertMatch": true,
|
||||||
|
"name": "z-baz",
|
||||||
|
"stringMatch": {
|
||||||
|
"contains": "qux"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"header": {
|
"header": {
|
||||||
"invertMatch": true,
|
"invertMatch": true,
|
||||||
|
@ -236,4 +289,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"name": "x-baz",
|
||||||
|
"stringMatch": {
|
||||||
|
"contains": "qux"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"header": {
|
"header": {
|
||||||
"name": "x-zim",
|
"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": {
|
"header": {
|
||||||
"invertMatch": true,
|
"invertMatch": true,
|
||||||
|
@ -157,6 +201,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"invertMatch": true,
|
||||||
|
"name": "z-baz",
|
||||||
|
"stringMatch": {
|
||||||
|
"contains": "qux"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"header": {
|
"header": {
|
||||||
"invertMatch": true,
|
"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 {
|
type IntentionHTTPHeaderPermission struct {
|
||||||
Name string
|
Name string
|
||||||
Present bool `json:",omitempty"`
|
Present bool `json:",omitempty"`
|
||||||
Exact string `json:",omitempty"`
|
Exact string `json:",omitempty"`
|
||||||
Prefix string `json:",omitempty"`
|
Prefix string `json:",omitempty"`
|
||||||
Suffix string `json:",omitempty"`
|
Suffix string `json:",omitempty"`
|
||||||
Regex string `json:",omitempty"`
|
Contains string `json:",omitempty"`
|
||||||
Invert bool `json:",omitempty"`
|
Regex string `json:",omitempty"`
|
||||||
|
Invert bool `json:",omitempty"`
|
||||||
|
IgnoreCase bool `json:",omitempty" alias:"ignore_case"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IntentionJWTRequirement struct {
|
type IntentionJWTRequirement struct {
|
||||||
|
|
|
@ -69,12 +69,53 @@ type MeshDirectionalTLSConfig struct {
|
||||||
|
|
||||||
type MeshHTTPConfig struct {
|
type MeshHTTPConfig struct {
|
||||||
SanitizeXForwardedClientCert bool `alias:"sanitize_x_forwarded_client_cert"`
|
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 {
|
type PeeringMeshConfig struct {
|
||||||
PeerThroughMeshGateways bool `json:",omitempty" alias:"peer_through_mesh_gateways"`
|
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) GetKind() string { return MeshConfig }
|
||||||
func (e *MeshConfigEntry) GetName() string { return MeshConfigMesh }
|
func (e *MeshConfigEntry) GetName() string { return MeshConfigMesh }
|
||||||
func (e *MeshConfigEntry) GetPartition() string { return e.Partition }
|
func (e *MeshConfigEntry) GetPartition() string { return e.Partition }
|
||||||
|
|
|
@ -2305,6 +2305,10 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
name = "hdr-suffix"
|
name = "hdr-suffix"
|
||||||
suffix = "suffix"
|
suffix = "suffix"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name = "hdr-contains"
|
||||||
|
contains = "contains"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name = "hdr-regex"
|
name = "hdr-regex"
|
||||||
regex = "regex"
|
regex = "regex"
|
||||||
|
@ -2313,7 +2317,12 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
name = "hdr-absent"
|
name = "hdr-absent"
|
||||||
present = true
|
present = true
|
||||||
invert = true
|
invert = true
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name = "hdr-ignore-case"
|
||||||
|
exact = "exact"
|
||||||
|
ignore_case = true
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2382,6 +2391,10 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
Name = "hdr-suffix"
|
Name = "hdr-suffix"
|
||||||
Suffix = "suffix"
|
Suffix = "suffix"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name = "hdr-contains"
|
||||||
|
Contains = "contains"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name = "hdr-regex"
|
Name = "hdr-regex"
|
||||||
Regex = "regex"
|
Regex = "regex"
|
||||||
|
@ -2390,6 +2403,11 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
Name = "hdr-absent"
|
Name = "hdr-absent"
|
||||||
Present = true
|
Present = true
|
||||||
Invert = true
|
Invert = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name = "hdr-ignore-case"
|
||||||
|
Exact = "exact"
|
||||||
|
IgnoreCase = true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2460,6 +2478,10 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
"name": "hdr-suffix",
|
"name": "hdr-suffix",
|
||||||
"suffix": "suffix"
|
"suffix": "suffix"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "hdr-contains",
|
||||||
|
"contains": "contains"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "hdr-regex",
|
"name": "hdr-regex",
|
||||||
"regex": "regex"
|
"regex": "regex"
|
||||||
|
@ -2468,6 +2490,11 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
"name": "hdr-absent",
|
"name": "hdr-absent",
|
||||||
"present": true,
|
"present": true,
|
||||||
"invert": true
|
"invert": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hdr-ignore-case",
|
||||||
|
"exact": "exact",
|
||||||
|
"ignore_case": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2542,6 +2569,10 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
"Name": "hdr-suffix",
|
"Name": "hdr-suffix",
|
||||||
"Suffix": "suffix"
|
"Suffix": "suffix"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Name": "hdr-contains",
|
||||||
|
"Contains": "contains"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Name": "hdr-regex",
|
"Name": "hdr-regex",
|
||||||
"Regex": "regex"
|
"Regex": "regex"
|
||||||
|
@ -2550,6 +2581,11 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
"Name": "hdr-absent",
|
"Name": "hdr-absent",
|
||||||
"Present": true,
|
"Present": true,
|
||||||
"Invert": true
|
"Invert": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "hdr-ignore-case",
|
||||||
|
"Exact": "exact",
|
||||||
|
"IgnoreCase": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2623,6 +2659,10 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
Name: "hdr-suffix",
|
Name: "hdr-suffix",
|
||||||
Suffix: "suffix",
|
Suffix: "suffix",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "hdr-contains",
|
||||||
|
Contains: "contains",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "hdr-regex",
|
Name: "hdr-regex",
|
||||||
Regex: "regex",
|
Regex: "regex",
|
||||||
|
@ -2632,6 +2672,11 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
Present: true,
|
Present: true,
|
||||||
Invert: 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: `
|
snake: `
|
||||||
kind = "mesh"
|
kind = "mesh"
|
||||||
meta {
|
meta {
|
||||||
|
@ -2729,6 +2774,8 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
transparent_proxy {
|
transparent_proxy {
|
||||||
mesh_destinations_only = true
|
mesh_destinations_only = true
|
||||||
}
|
}
|
||||||
|
allow_enabling_permissive_mutual_tls = true
|
||||||
|
validate_clusters = true
|
||||||
tls {
|
tls {
|
||||||
incoming {
|
incoming {
|
||||||
tls_min_version = "TLSv1_1"
|
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: `
|
camel: `
|
||||||
Kind = "mesh"
|
Kind = "mesh"
|
||||||
|
@ -2757,6 +2818,8 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
TransparentProxy {
|
TransparentProxy {
|
||||||
MeshDestinationsOnly = true
|
MeshDestinationsOnly = true
|
||||||
}
|
}
|
||||||
|
AllowEnablingPermissiveMutualTLS = true
|
||||||
|
ValidateClusters = true
|
||||||
TLS {
|
TLS {
|
||||||
Incoming {
|
Incoming {
|
||||||
TLSMinVersion = "TLSv1_1"
|
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: `
|
snakeJSON: `
|
||||||
{
|
{
|
||||||
|
@ -2786,6 +2863,8 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
"transparent_proxy": {
|
"transparent_proxy": {
|
||||||
"mesh_destinations_only": true
|
"mesh_destinations_only": true
|
||||||
},
|
},
|
||||||
|
"allow_enabling_permissive_mutual_tls": true,
|
||||||
|
"validate_clusters": true,
|
||||||
"tls": {
|
"tls": {
|
||||||
"incoming": {
|
"incoming": {
|
||||||
"tls_min_version": "TLSv1_1",
|
"tls_min_version": "TLSv1_1",
|
||||||
|
@ -2803,6 +2882,20 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
"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": {
|
"TransparentProxy": {
|
||||||
"MeshDestinationsOnly": true
|
"MeshDestinationsOnly": true
|
||||||
},
|
},
|
||||||
|
"AllowEnablingPermissiveMutualTLS": true,
|
||||||
|
"ValidateClusters": true,
|
||||||
"TLS": {
|
"TLS": {
|
||||||
"Incoming": {
|
"Incoming": {
|
||||||
"TLSMinVersion": "TLSv1_1",
|
"TLSMinVersion": "TLSv1_1",
|
||||||
|
@ -2833,6 +2928,20 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
"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{
|
TransparentProxy: api.TransparentProxyMeshConfig{
|
||||||
MeshDestinationsOnly: true,
|
MeshDestinationsOnly: true,
|
||||||
},
|
},
|
||||||
|
AllowEnablingPermissiveMutualTLS: true,
|
||||||
|
ValidateClusters: true,
|
||||||
TLS: &api.MeshTLSConfig{
|
TLS: &api.MeshTLSConfig{
|
||||||
Incoming: &api.MeshDirectionalTLSConfig{
|
Incoming: &api.MeshDirectionalTLSConfig{
|
||||||
TLSMinVersion: "TLSv1_1",
|
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.Exact = s.Exact
|
||||||
t.Prefix = s.Prefix
|
t.Prefix = s.Prefix
|
||||||
t.Suffix = s.Suffix
|
t.Suffix = s.Suffix
|
||||||
|
t.Contains = s.Contains
|
||||||
t.Regex = s.Regex
|
t.Regex = s.Regex
|
||||||
t.Invert = s.Invert
|
t.Invert = s.Invert
|
||||||
|
t.IgnoreCase = s.IgnoreCase
|
||||||
}
|
}
|
||||||
func IntentionHTTPHeaderPermissionFromStructs(t *structs.IntentionHTTPHeaderPermission, s *IntentionHTTPHeaderPermission) {
|
func IntentionHTTPHeaderPermissionFromStructs(t *structs.IntentionHTTPHeaderPermission, s *IntentionHTTPHeaderPermission) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
|
@ -1165,8 +1167,10 @@ func IntentionHTTPHeaderPermissionFromStructs(t *structs.IntentionHTTPHeaderPerm
|
||||||
s.Exact = t.Exact
|
s.Exact = t.Exact
|
||||||
s.Prefix = t.Prefix
|
s.Prefix = t.Prefix
|
||||||
s.Suffix = t.Suffix
|
s.Suffix = t.Suffix
|
||||||
|
s.Contains = t.Contains
|
||||||
s.Regex = t.Regex
|
s.Regex = t.Regex
|
||||||
s.Invert = t.Invert
|
s.Invert = t.Invert
|
||||||
|
s.IgnoreCase = t.IgnoreCase
|
||||||
}
|
}
|
||||||
func IntentionHTTPPermissionToStructs(s *IntentionHTTPPermission, t *structs.IntentionHTTPPermission) {
|
func IntentionHTTPPermissionToStructs(s *IntentionHTTPPermission, t *structs.IntentionHTTPPermission) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
|
@ -1758,6 +1762,26 @@ func MeshConfigFromStructs(t *structs.MeshConfigEntry, s *MeshConfig) {
|
||||||
s.Meta = t.Meta
|
s.Meta = t.Meta
|
||||||
s.Hash = t.Hash
|
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) {
|
func MeshDirectionalTLSConfigToStructs(s *MeshDirectionalTLSConfig, t *structs.MeshDirectionalTLSConfig) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return
|
return
|
||||||
|
@ -1791,12 +1815,22 @@ func MeshHTTPConfigToStructs(s *MeshHTTPConfig, t *structs.MeshHTTPConfig) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.SanitizeXForwardedClientCert = s.SanitizeXForwardedClientCert
|
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) {
|
func MeshHTTPConfigFromStructs(t *structs.MeshHTTPConfig, s *MeshHTTPConfig) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.SanitizeXForwardedClientCert = t.SanitizeXForwardedClientCert
|
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) {
|
func MeshTLSConfigToStructs(s *MeshTLSConfig, t *structs.MeshTLSConfig) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
|
@ -1916,6 +1950,24 @@ func RemoteJWKSFromStructs(t *structs.RemoteJWKS, s *RemoteJWKS) {
|
||||||
s.JWKSCluster = &x
|
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) {
|
func ResourceReferenceToStructs(s *ResourceReference, t *structs.ResourceReference) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return
|
return
|
||||||
|
|
|
@ -291,6 +291,66 @@ func cipherSuitesFromStructs(cs []types.TLSCipherSuite) []string {
|
||||||
return cipherSuites
|
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 {
|
func enterpriseMetaToStructs(m *pbcommon.EnterpriseMeta) acl.EnterpriseMeta {
|
||||||
var entMeta acl.EnterpriseMeta
|
var entMeta acl.EnterpriseMeta
|
||||||
pbcommon.EnterpriseMetaToStructs(m, &entMeta)
|
pbcommon.EnterpriseMetaToStructs(m, &entMeta)
|
||||||
|
|
|
@ -107,6 +107,16 @@ func (msg *MeshHTTPConfig) UnmarshalBinary(b []byte) error {
|
||||||
return proto.Unmarshal(b, msg)
|
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
|
// MarshalBinary implements encoding.BinaryMarshaler
|
||||||
func (msg *PeeringMeshConfig) MarshalBinary() ([]byte, error) {
|
func (msg *PeeringMeshConfig) MarshalBinary() ([]byte, error) {
|
||||||
return proto.Marshal(msg)
|
return proto.Marshal(msg)
|
||||||
|
@ -117,6 +127,16 @@ func (msg *PeeringMeshConfig) UnmarshalBinary(b []byte) error {
|
||||||
return proto.Unmarshal(b, msg)
|
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
|
// MarshalBinary implements encoding.BinaryMarshaler
|
||||||
func (msg *ServiceResolver) MarshalBinary() ([]byte, error) {
|
func (msg *ServiceResolver) MarshalBinary() ([]byte, error) {
|
||||||
return proto.Marshal(msg)
|
return proto.Marshal(msg)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -139,6 +139,16 @@ message MeshDirectionalTLSConfig {
|
||||||
// name=Structs
|
// name=Structs
|
||||||
message MeshHTTPConfig {
|
message MeshHTTPConfig {
|
||||||
bool SanitizeXForwardedClientCert = 1;
|
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:
|
// mog annotation:
|
||||||
|
@ -150,6 +160,34 @@ message PeeringMeshConfig {
|
||||||
bool PeerThroughMeshGateways = 1;
|
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:
|
// mog annotation:
|
||||||
//
|
//
|
||||||
// target=github.com/hashicorp/consul/agent/structs.ServiceResolverConfigEntry
|
// target=github.com/hashicorp/consul/agent/structs.ServiceResolverConfigEntry
|
||||||
|
@ -521,6 +559,8 @@ message IntentionHTTPHeaderPermission {
|
||||||
string Suffix = 5;
|
string Suffix = 5;
|
||||||
string Regex = 6;
|
string Regex = 6;
|
||||||
bool Invert = 7;
|
bool Invert = 7;
|
||||||
|
string Contains = 8;
|
||||||
|
bool IgnoreCase = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mog annotation:
|
// 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"
|
action = "allow"
|
||||||
http {
|
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"
|
action = "allow"
|
||||||
http {
|
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"
|
action = "allow"
|
||||||
http {
|
http {
|
||||||
|
|
|
@ -34,49 +34,84 @@ load helpers
|
||||||
|
|
||||||
@test "test exact path" {
|
@test "test exact path" {
|
||||||
retry_default must_pass_http_request GET localhost:5000/exact
|
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" {
|
@test "test prefix path" {
|
||||||
retry_default must_pass_http_request GET localhost:5000/prefix
|
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" {
|
@test "test regex path" {
|
||||||
retry_default must_pass_http_request GET localhost:5000/regex
|
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" {
|
@test "test present header" {
|
||||||
retry_default must_pass_http_request GET localhost:5000/hdr-present anything
|
retry_default must_pass_http_request GET localhost:5000/hdr-present x-test-debug:anything
|
||||||
retry_default must_fail_http_request GET localhost:5000/hdr-present ""
|
retry_default must_fail_http_request 403 GET localhost:5000/hdr-present x-test-debug:
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "test exact header" {
|
@test "test exact header" {
|
||||||
retry_default must_pass_http_request GET localhost:5000/hdr-exact exact
|
retry_default must_pass_http_request GET localhost:5000/hdr-exact x-test-debug:exact
|
||||||
retry_default must_fail_http_request GET localhost:5000/hdr-exact exact-nope
|
retry_default must_fail_http_request 403 GET localhost:5000/hdr-exact x-test-debug:exact-nope
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "test prefix header" {
|
@test "test prefix header" {
|
||||||
retry_default must_pass_http_request GET localhost:5000/hdr-prefix prefix
|
retry_default must_pass_http_request GET localhost:5000/hdr-prefix x-test-debug:prefix
|
||||||
retry_default must_fail_http_request GET localhost:5000/hdr-prefix nope-prefix
|
retry_default must_fail_http_request 403 GET localhost:5000/hdr-prefix x-test-debug:nope-prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "test suffix header" {
|
@test "test suffix header" {
|
||||||
retry_default must_pass_http_request GET localhost:5000/hdr-suffix suffix
|
retry_default must_pass_http_request GET localhost:5000/hdr-suffix x-test-debug:suffix
|
||||||
retry_default must_fail_http_request GET localhost:5000/hdr-suffix suffix-nope
|
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" {
|
@test "test regex header" {
|
||||||
retry_default must_pass_http_request GET localhost:5000/hdr-regex regex
|
retry_default must_pass_http_request GET localhost:5000/hdr-regex x-test-debug:regex
|
||||||
retry_default must_fail_http_request GET localhost:5000/hdr-regex reggex
|
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" {
|
@test "test method match" {
|
||||||
retry_default must_pass_http_request GET localhost:5000/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_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 403 POST localhost:5000/method-match
|
||||||
retry_default must_fail_http_request HEAD 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" {
|
# @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
|
# 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
|
# that envoy will NOT reject the request.
|
||||||
# intentions.
|
|
||||||
function must_pass_http_request {
|
function must_pass_http_request {
|
||||||
local METHOD=$1
|
local METHOD=$1
|
||||||
local URL=$2
|
local URL=$2
|
||||||
local DEBUG_HEADER_VALUE="${3:-""}"
|
shift 2
|
||||||
|
|
||||||
local extra_args
|
local extra_args
|
||||||
if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then
|
|
||||||
extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}"
|
|
||||||
fi
|
|
||||||
case "$METHOD" in
|
case "$METHOD" in
|
||||||
GET) ;;
|
GET) ;;
|
||||||
|
|
||||||
|
@ -786,22 +782,25 @@ function must_pass_http_request {
|
||||||
;;
|
;;
|
||||||
esac
|
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"
|
run curl --no-keepalive -v -s -f $extra_args "$URL"
|
||||||
[ "$status" == 0 ]
|
[ "$status" == 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
# must_fail_http_request allows you to craft a specific http request to assert
|
# 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
|
# that envoy will reject the request. Must supply the expected status code before
|
||||||
# intentions.
|
# method and URL.
|
||||||
function must_fail_http_request {
|
function must_fail_http_request {
|
||||||
local METHOD=$1
|
local EXPECT_RESPONSE=$1
|
||||||
local URL=$2
|
local METHOD=$2
|
||||||
local DEBUG_HEADER_VALUE="${3:-""}"
|
local URL=$3
|
||||||
|
shift 2
|
||||||
|
|
||||||
local extra_args
|
local extra_args
|
||||||
if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then
|
|
||||||
extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}"
|
|
||||||
fi
|
|
||||||
case "$METHOD" in
|
case "$METHOD" in
|
||||||
HEAD)
|
HEAD)
|
||||||
extra_args="$extra_args -I"
|
extra_args="$extra_args -I"
|
||||||
|
@ -819,12 +818,42 @@ function must_fail_http_request {
|
||||||
;;
|
;;
|
||||||
esac
|
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
|
# Attempt to curl through upstream
|
||||||
run curl --no-keepalive -s -i $extra_args "$URL"
|
run curl --no-keepalive -s -i $extra_args "$URL"
|
||||||
|
|
||||||
echo "OUTPUT $output"
|
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 {
|
function gen_envoy_bootstrap {
|
||||||
|
|
|
@ -602,15 +602,43 @@ function run_container {
|
||||||
"run_container_$1"
|
"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 {
|
function common_run_container_service {
|
||||||
local service="$1"
|
local serviceContainer=${SERVICE_CONTAINER:-fortio}
|
||||||
local CLUSTER="$2"
|
local containerName=$(container_name_prev)
|
||||||
local httpPort="$3"
|
|
||||||
local grpcPort="$4"
|
|
||||||
|
|
||||||
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}" \
|
-e "FORTIO_NAME=${service}" \
|
||||||
$(network_snippet $CLUSTER) \
|
$(network_snippet $cluster) \
|
||||||
"${HASHICORP_DOCKER_PROXY}/fortio/fortio" \
|
"${HASHICORP_DOCKER_PROXY}/fortio/fortio" \
|
||||||
server \
|
server \
|
||||||
-http-port ":$httpPort" \
|
-http-port ":$httpPort" \
|
||||||
|
@ -618,6 +646,25 @@ function common_run_container_service {
|
||||||
-redirect-port disabled >/dev/null
|
-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 {
|
function run_container_s1 {
|
||||||
common_run_container_service s1 primary 8080 8079
|
common_run_container_service s1 primary 8080 8079
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,20 @@
|
||||||
</group.Element>
|
</group.Element>
|
||||||
{{/if}}
|
{{/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>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Component from '@ember/component';
|
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 { alias, equal, not } from '@ember/object/computed';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ export default Component.extend({
|
||||||
Exact: 'Exactly Matching',
|
Exact: 'Exactly Matching',
|
||||||
Prefix: 'Prefixed by',
|
Prefix: 'Prefixed by',
|
||||||
Suffix: 'Suffixed by',
|
Suffix: 'Suffixed by',
|
||||||
|
Contains: 'Containing',
|
||||||
Regex: 'Regular Expression',
|
Regex: 'Regular Expression',
|
||||||
Present: 'Is present',
|
Present: 'Is present',
|
||||||
};
|
};
|
||||||
|
@ -49,9 +50,14 @@ export default Component.extend({
|
||||||
headerTypeEqualsPresent: equal('headerType', 'Present'),
|
headerTypeEqualsPresent: equal('headerType', 'Present'),
|
||||||
shouldShowValueField: not('headerTypeEqualsPresent'),
|
shouldShowValueField: not('headerTypeEqualsPresent'),
|
||||||
|
|
||||||
|
shouldShowIgnoreCaseField: computed('headerType', function () {
|
||||||
|
return this.headerType !== 'Present' && this.headerType !== 'Regex';
|
||||||
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
change: function (name, changeset, e) {
|
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) {
|
switch (name) {
|
||||||
default:
|
default:
|
||||||
changeset.set(name, value);
|
changeset.set(name, value);
|
||||||
|
@ -65,6 +71,7 @@ export default Component.extend({
|
||||||
// Present is a boolean, whereas all other header types have a value
|
// Present is a boolean, whereas all other header types have a value
|
||||||
const value = changeset.HeaderType === 'Present' ? true : changeset.Value;
|
const value = changeset.HeaderType === 'Present' ? true : changeset.Value;
|
||||||
changeset.set(changeset.HeaderType, value);
|
changeset.set(changeset.HeaderType, value);
|
||||||
|
changeset.set('IgnoreCase', changeset.IgnoreCase);
|
||||||
|
|
||||||
// this will prevent the changeset from overwriting the
|
// this will prevent the changeset from overwriting the
|
||||||
// computed properties on the ED object
|
// computed properties on the ED object
|
||||||
|
|
|
@ -11,7 +11,14 @@ export default (scope = '.consul-intention-permission-header-form') => {
|
||||||
scope: scope,
|
scope: scope,
|
||||||
HeaderType: {
|
HeaderType: {
|
||||||
scope: '[data-property="headertype"]',
|
scope: '[data-property="headertype"]',
|
||||||
...powerSelect(['ExactlyMatching', 'PrefixedBy', 'SuffixedBy', 'RegEx', 'IsPresent']),
|
...powerSelect([
|
||||||
|
'ExactlyMatching',
|
||||||
|
'PrefixedBy',
|
||||||
|
'SuffixedBy',
|
||||||
|
'Containing',
|
||||||
|
'RegEx',
|
||||||
|
'IsPresent',
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
Name: {
|
Name: {
|
||||||
scope: '[data-property="name"] input',
|
scope: '[data-property="name"] input',
|
||||||
|
|
|
@ -6,21 +6,26 @@
|
||||||
import { helper } from '@ember/component/helper';
|
import { helper } from '@ember/component/helper';
|
||||||
|
|
||||||
export default helper(function routeMatch([item], hash) {
|
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'
|
(prop) => typeof item[prop] !== 'undefined'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let invertPrefix = item.Invert ? 'NOT ' : '';
|
||||||
|
let ignoreCaseSuffix = item.IgnoreCase ? ' (case-insensitive)' : '';
|
||||||
|
|
||||||
switch (prop) {
|
switch (prop) {
|
||||||
case 'Present':
|
case 'Present':
|
||||||
return `${item.Invert ? `NOT ` : ``}present`;
|
return `${invertPrefix}present`;
|
||||||
case 'Exact':
|
case 'Exact':
|
||||||
return `${item.Invert ? `NOT ` : ``}exactly matching "${item.Exact}"`;
|
return `${invertPrefix}exactly matching "${item.Exact}"${ignoreCaseSuffix}`;
|
||||||
case 'Prefix':
|
case 'Prefix':
|
||||||
return `${item.Invert ? `NOT ` : ``}prefixed by "${item.Prefix}"`;
|
return `${invertPrefix}prefixed by "${item.Prefix}"${ignoreCaseSuffix}`;
|
||||||
case 'Suffix':
|
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':
|
case 'Regex':
|
||||||
return `${item.Invert ? `NOT ` : ``}matching the regex "${item.Regex}"`;
|
return `${invertPrefix}matching the regex "${item.Regex}"`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,7 @@ export const schema = {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
HeaderType: {
|
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') Exact;
|
||||||
@attr('string') Prefix;
|
@attr('string') Prefix;
|
||||||
@attr('string') Suffix;
|
@attr('string') Suffix;
|
||||||
|
@attr('string') Contains;
|
||||||
@attr('string') Regex;
|
@attr('string') Regex;
|
||||||
// this is a boolean but we don't want it to automatically be set to false
|
// this is a boolean but we don't want it to automatically be set to false
|
||||||
@attr() Present;
|
@attr() Present;
|
||||||
|
|
||||||
@or(...schema.HeaderType.allowedValues) Value;
|
@or(...schema.HeaderType.allowedValues) Value;
|
||||||
|
@attr('boolean') IgnoreCase;
|
||||||
|
|
||||||
@computed(...schema.HeaderType.allowedValues)
|
@computed(...schema.HeaderType.allowedValues)
|
||||||
get HeaderType() {
|
get HeaderType() {
|
||||||
|
|
|
@ -78,11 +78,15 @@ ${range(headerCount).map(item => `
|
||||||
${fake.random.boolean() ? `
|
${fake.random.boolean() ? `
|
||||||
"Invert": true,
|
"Invert": true,
|
||||||
` : ``}
|
` : ``}
|
||||||
|
${fake.random.boolean() ? `
|
||||||
|
"IgnoreCase": true,
|
||||||
|
` : ``}
|
||||||
${fake.helpers.randomize([
|
${fake.helpers.randomize([
|
||||||
'"Present": true',
|
'"Present": true',
|
||||||
'"Exact": "abc"',
|
'"Exact": "abc"',
|
||||||
'"Prefix": "abc"',
|
'"Prefix": "abc"',
|
||||||
'"Suffix": "xyz"',
|
'"Suffix": "xyz"',
|
||||||
|
'"Contains": "abc"',
|
||||||
'"Regex": "[abc]"'
|
'"Regex": "[abc]"'
|
||||||
])}
|
])}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,11 +63,15 @@ ${range(headerCount).map(item => `
|
||||||
${fake.random.boolean() ? `
|
${fake.random.boolean() ? `
|
||||||
"Invert": true,
|
"Invert": true,
|
||||||
` : ``}
|
` : ``}
|
||||||
|
${fake.random.boolean() ? `
|
||||||
|
"IgnoreCase": true,
|
||||||
|
` : ``}
|
||||||
${fake.helpers.randomize([
|
${fake.helpers.randomize([
|
||||||
'"Present": true',
|
'"Present": true',
|
||||||
'"Exact": "abc"',
|
'"Exact": "abc"',
|
||||||
'"Prefix": "abc"',
|
'"Prefix": "abc"',
|
||||||
'"Suffix": "xyz"',
|
'"Suffix": "xyz"',
|
||||||
|
'"Contains": "abc"',
|
||||||
'"Regex": "[abc]"'
|
'"Regex": "[abc]"'
|
||||||
])}
|
])}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,11 +69,15 @@ ${range(headerCount).map(item => `
|
||||||
${fake.random.boolean() ? `
|
${fake.random.boolean() ? `
|
||||||
"Invert": true,
|
"Invert": true,
|
||||||
` : ``}
|
` : ``}
|
||||||
|
${fake.random.boolean() ? `
|
||||||
|
"IgnoreCase": true,
|
||||||
|
` : ``}
|
||||||
${fake.helpers.randomize([
|
${fake.helpers.randomize([
|
||||||
'"Present": true',
|
'"Present": true',
|
||||||
'"Exact": "abc"',
|
'"Exact": "abc"',
|
||||||
'"Prefix": "abc"',
|
'"Prefix": "abc"',
|
||||||
'"Suffix": "xyz"',
|
'"Suffix": "xyz"',
|
||||||
|
'"Contains": "abc"',
|
||||||
'"Regex": "[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.
|
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
|
## 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.
|
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.`,
|
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
|
- [`exact`](#spec-sources-permissions-http-header): string | no default
|
||||||
- [`prefix`](#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
|
- [`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
|
- [`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`
|
- [`invert`](#spec-sources-permissions-http-header): boolean | `false`
|
||||||
- [`description`](#spec-sources-description): string
|
- [`description`](#spec-sources-description): string
|
||||||
|
|
||||||
|
@ -156,18 +158,31 @@ Sources = [
|
||||||
{
|
{
|
||||||
Name = "<http header name>" # string
|
Name = "<http header name>" # string
|
||||||
Present = <true or false> # boolean
|
Present = <true or false> # boolean
|
||||||
|
Invert = <true or false> # boolean
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = "<http header name>" # string
|
Name = "<http header name>" # string
|
||||||
Exact = "<header-value>" # boolean
|
Exact = "<header-value>" # boolean
|
||||||
|
IgnoreCase = <true or false> # boolean
|
||||||
|
Invert = <true or false> # boolean
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = "<http header name>" # string
|
Name = "<http header name>" # string
|
||||||
Prefix = "<source header value prefix>" # string
|
Prefix = "<source header value prefix>" # string
|
||||||
|
IgnoreCase = <true or false> # boolean
|
||||||
|
Invert = <true or false> # boolean
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = "<http header name>" # string
|
Name = "<http header name>" # string
|
||||||
Suffix = "<source header value suffix>" # 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
|
Name = "<http header name>" # string
|
||||||
|
@ -227,12 +242,23 @@ spec:
|
||||||
header:
|
header:
|
||||||
- name: <http header name>
|
- name: <http header name>
|
||||||
present: true
|
present: true
|
||||||
|
invert: false
|
||||||
- name: <http header name>
|
- name: <http header name>
|
||||||
exact: false
|
exact: <header-value>
|
||||||
|
ignoreCase: false
|
||||||
|
invert: false
|
||||||
- name: <http header name>
|
- name: <http header name>
|
||||||
prefix: <source header value prefix>
|
prefix: <source header value prefix>
|
||||||
|
ignoreCase: false
|
||||||
|
invert: false
|
||||||
- name: <http header name>
|
- 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>
|
- name: <http header name>
|
||||||
regex: <regex pattern to match>
|
regex: <regex pattern to match>
|
||||||
invert: false
|
invert: false
|
||||||
|
@ -287,19 +313,32 @@ spec:
|
||||||
"Header":[
|
"Header":[
|
||||||
{
|
{
|
||||||
"Name":"<http header name>",
|
"Name":"<http header name>",
|
||||||
"Present":true
|
"Present":true,
|
||||||
|
"Invert":false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Name":"<http header name>",
|
"Name":"<http header name>",
|
||||||
"Exact":false
|
"Exact":"<header-value>",
|
||||||
|
"IgnoreCase":false,,
|
||||||
|
"Invert":false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Name":"<http header name>",
|
"Name":"<http header name>",
|
||||||
"Prefix":"<source header value prefix>"
|
"Prefix":"<source header value prefix>",
|
||||||
|
"IgnoreCase":false,
|
||||||
|
"Invert":false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Name":"<http header name>",
|
"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>",
|
"Name":"<http header name>",
|
||||||
|
@ -923,16 +962,22 @@ Specifies a set of criteria for matching HTTP request headers. The request heade
|
||||||
- Default: None
|
- Default: None
|
||||||
- Data type: List of maps
|
- 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 |
|
| Parameter | Description | Data type | Required |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `name` | Specifies the name of the header to match. | string | 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 |
|
| `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`, or `regex` are configured in the same `header` configuration. | string | 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`, 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`, 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 |
|
||||||
| `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 |
|
| `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 |
|
| `invert` | Inverts the matching logic configured in the `header`. Default is `false`. | boolean | optional |
|
||||||
|
|
||||||
### `spec.sources[].type`
|
### `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.
|
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
|
### 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
|
all service-to-service communication to be explicitly
|
||||||
allowed via an allow [intention](/consul/docs/connect/intentions).
|
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
|
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
|
default policy when determining whether to allow or deny communications without
|
||||||
an explicit intention.
|
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
|
### ACLs Enabled with Default Deny
|
||||||
|
|
||||||
Consul must be configured to use ACLs with a default deny policy. This forces
|
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
|
may edit intentions. This renders the security of the created intentions
|
||||||
effectively useless.
|
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 Enabled
|
||||||
|
|
||||||
TCP and UDP encryption must be enabled to prevent plaintext communication
|
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
|
[authentication methods](/consul/docs/security/acl/auth-methods) can be used to enable trusted external parties to authorize
|
||||||
ACL token creation.
|
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
|
- **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.
|
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
|
- **🏷 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.
|
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
|
- **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
|
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,
|
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
|
- **Customize Default Limits** - Consul has a number of builtin features with default connection limits that should be
|
||||||
tuned to fit your environment.
|
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).
|
[API gateway](/consul/docs/connect/config-entries/api-gateway).
|
||||||
On Kubernetes, the modern API gateway is associated with the `connectInject.apiGateway` stanza.
|
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
|
## 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 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.
|
[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
|
## Consul 1.16.x
|
||||||
|
|
||||||
### Known issues
|
### Known issues
|
||||||
|
@ -241,6 +254,10 @@ In Consul v1.15 and higher:
|
||||||
|
|
||||||
</CodeBlockConfig>
|
</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
|
## Consul 1.14.x
|
||||||
|
|
||||||
### Service Mesh Compatibility
|
### Service Mesh Compatibility
|
||||||
|
|
Loading…
Reference in New Issue