mirror of https://github.com/status-im/consul.git
Disable remote proxy patching except AWS Lambda (#17415)
To avoid unintended tampering with remote downstreams via service config, refactor BasicEnvoyExtender and RuntimeConfig to disallow typical Envoy extensions from being applied to non-local proxies. Continue to allow this behavior for AWS Lambda and the read-only Validate builtin extensions. Addresses CVE-2023-2816.
This commit is contained in:
parent
e2a81aa8bd
commit
b8d2640429
|
@ -0,0 +1,7 @@
|
|||
```release-note:security
|
||||
extensions: Disable remote downstream proxy patching by Envoy Extensions other than AWS Lambda. Previously, an operator with service:write ACL permissions for an upstream service could modify Envoy proxy config for downstream services without equivalent permissions for those services. This issue only impacts the Lua extension. [[CVE-2023-2816](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-2816)]
|
||||
```
|
||||
|
||||
```release-note:breaking-change
|
||||
extensions: The Lua extension now targets local proxy listeners for the configured service's upstreams, rather than remote downstream listeners for the configured service, when ListenerType is set to outbound in extension configuration. See [CVE-2023-2816](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-2816) changelog entry for more details.
|
||||
```
|
|
@ -42,7 +42,7 @@ func Constructor(ext api.EnvoyExtension) (extensioncommon.EnvoyExtender, error)
|
|||
if err := a.fromArguments(ext.Arguments); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &extensioncommon.BasicEnvoyExtender{
|
||||
return &extensioncommon.UpstreamEnvoyExtender{
|
||||
Extension: &a,
|
||||
}, nil
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ func (a *awsLambda) validate() error {
|
|||
// CanApply returns true if the kind of the provided ExtensionConfiguration matches
|
||||
// the kind of the lambda configuration
|
||||
func (a *awsLambda) CanApply(config *extensioncommon.RuntimeConfig) bool {
|
||||
return config.Kind == config.OutgoingProxyKind()
|
||||
return config.Kind == config.UpstreamOutgoingProxyKind()
|
||||
}
|
||||
|
||||
// PatchRoute modifies the routing configuration for a service of kind TerminatingGateway. If the kind is
|
||||
|
@ -75,6 +75,11 @@ func (a *awsLambda) PatchRoute(r *extensioncommon.RuntimeConfig, route *envoy_ro
|
|||
return route, false, nil
|
||||
}
|
||||
|
||||
// Only patch outbound routes.
|
||||
if extensioncommon.IsRouteToLocalAppCluster(route) {
|
||||
return route, false, nil
|
||||
}
|
||||
|
||||
for _, virtualHost := range route.VirtualHosts {
|
||||
for _, route := range virtualHost.Routes {
|
||||
action, ok := route.Action.(*envoy_route_v3.Route_Route)
|
||||
|
@ -95,6 +100,11 @@ func (a *awsLambda) PatchRoute(r *extensioncommon.RuntimeConfig, route *envoy_ro
|
|||
|
||||
// PatchCluster patches the provided envoy cluster with data required to support an AWS lambda function
|
||||
func (a *awsLambda) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) {
|
||||
// Only patch outbound clusters.
|
||||
if extensioncommon.IsLocalAppCluster(c) {
|
||||
return c, false, nil
|
||||
}
|
||||
|
||||
transportSocket, err := extensioncommon.MakeUpstreamTLSTransportSocket(&envoy_tls_v3.UpstreamTlsContext{
|
||||
Sni: "*.amazonaws.com",
|
||||
})
|
||||
|
@ -156,7 +166,12 @@ func (a *awsLambda) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_clus
|
|||
|
||||
// PatchFilter patches the provided envoy filter with an inserted lambda filter being careful not to
|
||||
// overwrite the http filters.
|
||||
func (a *awsLambda) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error) {
|
||||
func (a *awsLambda) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
||||
// Only patch outbound filters.
|
||||
if isInboundListener {
|
||||
return filter, false, nil
|
||||
}
|
||||
|
||||
if filter.Name != "envoy.filters.network.http_connection_manager" {
|
||||
return filter, false, nil
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ func TestConstructor(t *testing.T) {
|
|||
|
||||
if tc.ok {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &extensioncommon.BasicEnvoyExtender{Extension: &tc.expected}, e)
|
||||
require.Equal(t, &extensioncommon.UpstreamEnvoyExtender{Extension: &tc.expected}, e)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
@ -324,6 +324,7 @@ func TestPatchFilter(t *testing.T) {
|
|||
}
|
||||
tests := map[string]struct {
|
||||
filter *envoy_listener_v3.Filter
|
||||
isInboundFilter bool
|
||||
expectFilter *envoy_listener_v3.Filter
|
||||
expectBool bool
|
||||
expectErr string
|
||||
|
@ -416,6 +417,36 @@ func TestPatchFilter(t *testing.T) {
|
|||
},
|
||||
expectBool: true,
|
||||
},
|
||||
"inbound filter ignored": {
|
||||
filter: &envoy_listener_v3.Filter{
|
||||
Name: "envoy.filters.network.http_connection_manager",
|
||||
ConfigType: &envoy_listener_v3.Filter_TypedConfig{
|
||||
TypedConfig: makeAny(&envoy_http_v3.HttpConnectionManager{
|
||||
HttpFilters: []*envoy_http_v3.HttpFilter{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
{Name: "envoy.filters.http.router"},
|
||||
{Name: "three"},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
expectFilter: &envoy_listener_v3.Filter{
|
||||
Name: "envoy.filters.network.http_connection_manager",
|
||||
ConfigType: &envoy_listener_v3.Filter_TypedConfig{
|
||||
TypedConfig: makeAny(&envoy_http_v3.HttpConnectionManager{
|
||||
HttpFilters: []*envoy_http_v3.HttpFilter{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
{Name: "envoy.filters.http.router"},
|
||||
{Name: "three"},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
isInboundFilter: true,
|
||||
expectBool: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
|
@ -425,7 +456,7 @@ func TestPatchFilter(t *testing.T) {
|
|||
PayloadPassthrough: true,
|
||||
InvocationMode: "asynchronous",
|
||||
}
|
||||
f, ok, err := l.PatchFilter(nil, tc.filter)
|
||||
f, ok, err := l.PatchFilter(nil, tc.filter, tc.isInboundFilter)
|
||||
require.Equal(t, tc.expectBool, ok)
|
||||
if tc.expectErr == "" {
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -97,9 +97,7 @@ func (r *ratelimit) validate() error {
|
|||
|
||||
// CanApply determines if the extension can apply to the given extension configuration.
|
||||
func (p *ratelimit) CanApply(config *extensioncommon.RuntimeConfig) bool {
|
||||
// rate limit is only applied to the service itself since the limit is
|
||||
// aggregated from all downstream connections.
|
||||
return string(config.Kind) == p.ProxyType && !config.IsUpstream()
|
||||
return string(config.Kind) == p.ProxyType
|
||||
}
|
||||
|
||||
// PatchRoute does nothing.
|
||||
|
@ -114,7 +112,13 @@ func (p ratelimit) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_clust
|
|||
|
||||
// PatchFilter inserts a http local rate_limit filter at the head of
|
||||
// envoy.filters.network.http_connection_manager filters
|
||||
func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error) {
|
||||
func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
||||
// rate limit is only applied to the inbound listener of the service itself
|
||||
// since the limit is aggregated from all downstream connections.
|
||||
if !isInboundListener {
|
||||
return filter, false, nil
|
||||
}
|
||||
|
||||
if filter.Name != "envoy.filters.network.http_connection_manager" {
|
||||
return filter, false, nil
|
||||
}
|
||||
|
|
|
@ -64,11 +64,11 @@ func (l *lua) validate() error {
|
|||
|
||||
// CanApply determines if the extension can apply to the given extension configuration.
|
||||
func (l *lua) CanApply(config *extensioncommon.RuntimeConfig) bool {
|
||||
return string(config.Kind) == l.ProxyType && l.matchesListenerDirection(config)
|
||||
return string(config.Kind) == l.ProxyType
|
||||
}
|
||||
|
||||
func (l *lua) matchesListenerDirection(config *extensioncommon.RuntimeConfig) bool {
|
||||
return (config.IsUpstream() && l.Listener == "outbound") || (!config.IsUpstream() && l.Listener == "inbound")
|
||||
func (l *lua) matchesListenerDirection(isInboundListener bool) bool {
|
||||
return (!isInboundListener && l.Listener == "outbound") || (isInboundListener && l.Listener == "inbound")
|
||||
}
|
||||
|
||||
// PatchRoute does nothing.
|
||||
|
@ -82,7 +82,12 @@ func (l *lua) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_cluster_v3
|
|||
}
|
||||
|
||||
// PatchFilter inserts a lua filter directly prior to envoy.filters.http.router.
|
||||
func (l *lua) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error) {
|
||||
func (l *lua) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
||||
// Make sure filter matches extension config.
|
||||
if !l.matchesListenerDirection(isInboundListener) {
|
||||
return filter, false, nil
|
||||
}
|
||||
|
||||
if filter.Name != "envoy.filters.network.http_connection_manager" {
|
||||
return filter, false, nil
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@ func (p *pluginConfig) asyncDataSource(rtCfg *extensioncommon.RuntimeConfig) (*e
|
|||
// fetch the data from the upstream source.
|
||||
remote := &p.VmConfig.Code.Remote
|
||||
clusterSNI := ""
|
||||
for service, upstream := range rtCfg.LocalUpstreams {
|
||||
for service, upstream := range rtCfg.Upstreams {
|
||||
if service == remote.HttpURI.Service {
|
||||
for sni := range upstream.SNI {
|
||||
clusterSNI = sni
|
||||
|
|
|
@ -68,12 +68,13 @@ func (w *wasm) fromArguments(args map[string]any) error {
|
|||
|
||||
// CanApply indicates if the WASM extension can be applied to the given extension configuration.
|
||||
// Currently the Wasm extension can be applied if the extension configuration is for an inbound
|
||||
// listener on the a local connect-proxy.
|
||||
// It does not patch extensions for service upstreams.
|
||||
// listener (checked below) on a local connect-proxy.
|
||||
func (w wasm) CanApply(config *extensioncommon.RuntimeConfig) bool {
|
||||
return config.IsLocal() && w.wasmConfig.ListenerType == "inbound" &&
|
||||
config.Kind == w.wasmConfig.ProxyType
|
||||
return config.Kind == w.wasmConfig.ProxyType
|
||||
}
|
||||
|
||||
func (w wasm) matchesConfigDirection(isInboundListener bool) bool {
|
||||
return isInboundListener && w.wasmConfig.ListenerType == "inbound"
|
||||
}
|
||||
|
||||
// PatchRoute does nothing for the WASM extension.
|
||||
|
@ -88,7 +89,11 @@ func (w wasm) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_cluster_v3
|
|||
|
||||
// PatchFilter adds a Wasm filter to the HTTP filter chain.
|
||||
// TODO (wasm/tcp): Add support for TCP filters.
|
||||
func (w wasm) PatchFilter(cfg *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error) {
|
||||
func (w wasm) PatchFilter(cfg *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
||||
if !w.matchesConfigDirection(isInboundListener) {
|
||||
return filter, false, nil
|
||||
}
|
||||
|
||||
if filter.Name != "envoy.filters.network.http_connection_manager" {
|
||||
return filter, false, nil
|
||||
}
|
||||
|
|
|
@ -37,8 +37,10 @@ func TestHttpWasmExtension(t *testing.T) {
|
|||
canApply bool
|
||||
args func(bool) map[string]any
|
||||
rtCfg func(bool) *extensioncommon.RuntimeConfig
|
||||
isInboundFilter bool
|
||||
inputFilters func() []*envoy_http_v3.HttpFilter
|
||||
expFilters func(tc testWasmConfig) []*envoy_http_v3.HttpFilter
|
||||
expPatched bool
|
||||
errStr string
|
||||
debug bool
|
||||
}{
|
||||
|
@ -47,6 +49,7 @@ func TestHttpWasmExtension(t *testing.T) {
|
|||
canApply: true,
|
||||
args: func(ent bool) map[string]any { return makeTestWasmConfig(ent).toMap(t) },
|
||||
rtCfg: func(ent bool) *extensioncommon.RuntimeConfig { return makeTestRuntimeConfig(ent) },
|
||||
isInboundFilter: true,
|
||||
inputFilters: makeTestHttpFilters,
|
||||
expFilters: func(tc testWasmConfig) []*envoy_http_v3.HttpFilter {
|
||||
return []*envoy_http_v3.HttpFilter{
|
||||
|
@ -65,6 +68,7 @@ func TestHttpWasmExtension(t *testing.T) {
|
|||
{Name: "three"},
|
||||
}
|
||||
},
|
||||
expPatched: true,
|
||||
},
|
||||
"local file": {
|
||||
extName: api.BuiltinWasmExtension,
|
||||
|
@ -77,6 +81,7 @@ func TestHttpWasmExtension(t *testing.T) {
|
|||
return cfg.toMap(t)
|
||||
},
|
||||
rtCfg: func(ent bool) *extensioncommon.RuntimeConfig { return makeTestRuntimeConfig(ent) },
|
||||
isInboundFilter: true,
|
||||
inputFilters: makeTestHttpFilters,
|
||||
expFilters: func(tc testWasmConfig) []*envoy_http_v3.HttpFilter {
|
||||
return []*envoy_http_v3.HttpFilter{
|
||||
|
@ -95,6 +100,24 @@ func TestHttpWasmExtension(t *testing.T) {
|
|||
{Name: "three"},
|
||||
}
|
||||
},
|
||||
expPatched: true,
|
||||
},
|
||||
"inbound filters ignored": {
|
||||
extName: api.BuiltinWasmExtension,
|
||||
canApply: true,
|
||||
args: func(ent bool) map[string]any { return makeTestWasmConfig(ent).toMap(t) },
|
||||
rtCfg: func(ent bool) *extensioncommon.RuntimeConfig { return makeTestRuntimeConfig(ent) },
|
||||
isInboundFilter: false,
|
||||
inputFilters: makeTestHttpFilters,
|
||||
expFilters: func(tc testWasmConfig) []*envoy_http_v3.HttpFilter {
|
||||
return []*envoy_http_v3.HttpFilter{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
{Name: "envoy.filters.http.router"},
|
||||
{Name: "three"},
|
||||
}
|
||||
},
|
||||
expPatched: false,
|
||||
},
|
||||
"no cluster for remote file": {
|
||||
extName: api.BuiltinWasmExtension,
|
||||
|
@ -102,11 +125,13 @@ func TestHttpWasmExtension(t *testing.T) {
|
|||
args: func(ent bool) map[string]any { return makeTestWasmConfig(ent).toMap(t) },
|
||||
rtCfg: func(ent bool) *extensioncommon.RuntimeConfig {
|
||||
rt := makeTestRuntimeConfig(ent)
|
||||
rt.LocalUpstreams = nil
|
||||
rt.Upstreams = nil
|
||||
return rt
|
||||
},
|
||||
isInboundFilter: true,
|
||||
inputFilters: makeTestHttpFilters,
|
||||
errStr: "no upstream found for remote service",
|
||||
expPatched: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -140,10 +165,10 @@ func TestHttpWasmExtension(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
inputHttpConMgr := makeHttpConMgr(t, c.inputFilters())
|
||||
obsHttpConMgr, patched, err := w.PatchFilter(c.rtCfg(enterprise), inputHttpConMgr)
|
||||
obsHttpConMgr, patched, err := w.PatchFilter(c.rtCfg(enterprise), inputHttpConMgr, c.isInboundFilter)
|
||||
if c.errStr == "" {
|
||||
require.NoError(t, err)
|
||||
require.True(t, patched)
|
||||
require.Equal(t, c.expPatched, patched)
|
||||
|
||||
cfg := testWasmConfigFromMap(t, c.args(enterprise))
|
||||
expHttpConMgr := makeHttpConMgr(t, c.expFilters(cfg))
|
||||
|
@ -156,6 +181,7 @@ func TestHttpWasmExtension(t *testing.T) {
|
|||
|
||||
prototest.AssertDeepEqual(t, expHttpConMgr, obsHttpConMgr)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.errStr)
|
||||
}
|
||||
|
||||
|
@ -554,7 +580,7 @@ func makeTestRuntimeConfig(enterprise bool) *extensioncommon.RuntimeConfig {
|
|||
return &extensioncommon.RuntimeConfig{
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
ServiceName: api.CompoundServiceName{Name: "test-service"},
|
||||
LocalUpstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
{
|
||||
Name: "test-file-server",
|
||||
Namespace: acl.NamespaceOrDefault(ns),
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
package envoyextensions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -61,3 +63,52 @@ func TestValidateExtensions(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// This test is included here so that we can test all registered extensions without creating a cyclic dependency between
|
||||
// envoyextensions and extensioncommon.
|
||||
func TestUpstreamExtenderLimitations(t *testing.T) {
|
||||
type testCase struct {
|
||||
config *extensioncommon.RuntimeConfig
|
||||
ok bool
|
||||
errMsg string
|
||||
}
|
||||
unauthorizedExtensionCase := func(name string) testCase {
|
||||
return testCase{
|
||||
config: &extensioncommon.RuntimeConfig{
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
ServiceName: api.CompoundServiceName{Name: "api"},
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{},
|
||||
IsSourcedFromUpstream: true,
|
||||
EnvoyExtension: api.EnvoyExtension{
|
||||
Name: name,
|
||||
},
|
||||
},
|
||||
ok: false,
|
||||
errMsg: fmt.Sprintf("extension %q is not permitted to be applied via upstream service config", name),
|
||||
}
|
||||
}
|
||||
cases := map[string]testCase{
|
||||
// Make sure future extensions are theoretically covered, even if not registered in the same way.
|
||||
"unknown extension": unauthorizedExtensionCase("someotherextension"),
|
||||
}
|
||||
for name := range extensionConstructors {
|
||||
// AWS Lambda is the only extension permitted to modify downstream proxy resources.
|
||||
if name == api.BuiltinAWSLambdaExtension {
|
||||
continue
|
||||
}
|
||||
cases[name] = unauthorizedExtensionCase(name)
|
||||
}
|
||||
|
||||
for n, tc := range cases {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
extender := extensioncommon.UpstreamEnvoyExtender{}
|
||||
_, err := extender.Extend(nil, tc.config)
|
||||
if tc.ok {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, tc.errMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,17 +62,16 @@ func TestEnvoyExtenderWithSnapshot(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
makeLuaServiceDefaults := func(inbound bool) *structs.ServiceConfigEntry {
|
||||
// Apply Lua extension to the local service and ensure http is used so the extension can be applied.
|
||||
makeLuaNsFunc := func(inbound bool) func(ns *structs.NodeService) {
|
||||
listener := "inbound"
|
||||
if !inbound {
|
||||
listener = "outbound"
|
||||
}
|
||||
|
||||
return &structs.ServiceConfigEntry{
|
||||
Kind: structs.ServiceDefaults,
|
||||
Name: "db",
|
||||
Protocol: "http",
|
||||
EnvoyExtensions: []structs.EnvoyExtension{
|
||||
return func(ns *structs.NodeService) {
|
||||
ns.Proxy.Config["protocol"] = "http"
|
||||
ns.Proxy.EnvoyExtensions = []structs.EnvoyExtension{
|
||||
{
|
||||
Name: api.BuiltinLuaExtension,
|
||||
Arguments: map[string]interface{}{
|
||||
|
@ -84,7 +83,7 @@ function envoy_on_request(request_handle)
|
|||
end`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,57 +129,47 @@ end`,
|
|||
create: proxycfg.TestConfigSnapshotTerminatingGatewayWithLambdaServiceAndServiceResolvers,
|
||||
},
|
||||
{
|
||||
name: "lua-outbound-applies-to-upstreams",
|
||||
name: "lua-outbound-applies-to-local-upstreams",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, makeLuaServiceDefaults(false))
|
||||
// upstreams need to be http in order for lua to be applied to listeners.
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, makeLuaNsFunc(false), nil, &structs.ServiceConfigEntry{
|
||||
Kind: structs.ServiceDefaults,
|
||||
Name: "db",
|
||||
Protocol: "http",
|
||||
}, &structs.ServiceConfigEntry{
|
||||
Kind: structs.ServiceDefaults,
|
||||
Name: "geo-cache",
|
||||
Protocol: "http",
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lua-inbound-doesnt-applies-to-upstreams",
|
||||
// We expect an inbound public listener lua filter here because the extension targets inbound.
|
||||
// The only difference between goldens for this and lua-inbound-applies-to-inbound
|
||||
// should be that db has HTTP filters rather than TCP.
|
||||
name: "lua-inbound-doesnt-apply-to-local-upstreams",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, makeLuaServiceDefaults(true))
|
||||
// db is made an HTTP upstream so that the extension _could_ apply, but does not because
|
||||
// the direction for the extension is inbound.
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, makeLuaNsFunc(true), nil, &structs.ServiceConfigEntry{
|
||||
Kind: structs.ServiceDefaults,
|
||||
Name: "db",
|
||||
Protocol: "http",
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lua-inbound-applies-to-inbound",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
|
||||
ns.Proxy.Config["protocol"] = "http"
|
||||
ns.Proxy.EnvoyExtensions = []structs.EnvoyExtension{
|
||||
{
|
||||
Name: api.BuiltinLuaExtension,
|
||||
Arguments: map[string]interface{}{
|
||||
"ProxyType": "connect-proxy",
|
||||
"Listener": "inbound",
|
||||
"Script": `
|
||||
function envoy_on_request(request_handle)
|
||||
request_handle:headers():add("test", "test")
|
||||
end`,
|
||||
},
|
||||
},
|
||||
}
|
||||
}, nil)
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, makeLuaNsFunc(true), nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
// We expect _no_ lua filters here, because the extension targets outbound, but there are
|
||||
// no upstream HTTP services. We also should not see public listener, which is HTTP, patched.
|
||||
name: "lua-outbound-doesnt-apply-to-inbound",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
|
||||
ns.Proxy.Config["protocol"] = "http"
|
||||
ns.Proxy.EnvoyExtensions = []structs.EnvoyExtension{
|
||||
{
|
||||
Name: api.BuiltinLuaExtension,
|
||||
Arguments: map[string]interface{}{
|
||||
"ProxyType": "connect-proxy",
|
||||
"Listener": "outbound",
|
||||
"Script": `
|
||||
function envoy_on_request(request_handle)
|
||||
request_handle:headers():add("test", "test")
|
||||
end`,
|
||||
},
|
||||
},
|
||||
}
|
||||
}, nil)
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, makeLuaNsFunc(false), nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -52,10 +52,10 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_TCP(t *testing.T) {
|
|||
var snap *proxycfg.ConfigSnapshot
|
||||
|
||||
testutil.RunStep(t, "initial setup", func(t *testing.T) {
|
||||
snap = newTestSnapshot(t, nil, "", &structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
EnvoyExtensions: []structs.EnvoyExtension{
|
||||
snap = newTestSnapshot(t, nil, "",
|
||||
func(ns *structs.NodeService) {
|
||||
// Add extension for local proxy.
|
||||
ns.Proxy.EnvoyExtensions = []structs.EnvoyExtension{
|
||||
{
|
||||
Name: api.BuiltinLuaExtension,
|
||||
Arguments: map[string]interface{}{
|
||||
|
@ -64,7 +64,7 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_TCP(t *testing.T) {
|
|||
"Script": "x = 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Send initial cluster discover. We'll assume we are testing a partial
|
||||
|
@ -194,7 +194,7 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_TCP(t *testing.T) {
|
|||
assertDeltaChanBlocked(t, envoy.deltaStream.sendCh)
|
||||
|
||||
// now reconfigure the snapshot and JUST edit the endpoints to strike one of the two current endpoints for db.
|
||||
snap = newTestSnapshot(t, snap, "")
|
||||
snap = newTestSnapshot(t, snap, "", nil)
|
||||
deleteAllButOneEndpoint(snap, UID("db"), "db.default.default.dc1")
|
||||
mgr.DeliverConfig(t, sid, snap)
|
||||
|
||||
|
@ -204,7 +204,7 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_TCP(t *testing.T) {
|
|||
|
||||
testutil.RunStep(t, "restore endpoint subscription", func(t *testing.T) {
|
||||
// Restore db's deleted endpoints by generating a new snapshot.
|
||||
snap = newTestSnapshot(t, snap, "")
|
||||
snap = newTestSnapshot(t, snap, "", nil)
|
||||
mgr.DeliverConfig(t, sid, snap)
|
||||
|
||||
// We never send an EDS reply about this change because Envoy is still not subscribed to db.
|
||||
|
@ -266,7 +266,7 @@ func TestServer_DeltaAggregatedResources_v3_NackLoop(t *testing.T) {
|
|||
var snap *proxycfg.ConfigSnapshot
|
||||
|
||||
testutil.RunStep(t, "initial setup", func(t *testing.T) {
|
||||
snap = newTestSnapshot(t, nil, "")
|
||||
snap = newTestSnapshot(t, nil, "", nil)
|
||||
|
||||
// Plug in a bad port for the public listener
|
||||
snap.Port = 1
|
||||
|
@ -402,7 +402,7 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_HTTP2(t *testing.T) {
|
|||
assertDeltaChanBlocked(t, envoy.deltaStream.sendCh)
|
||||
|
||||
// Deliver a new snapshot (tcp with one http upstream)
|
||||
snap := newTestSnapshot(t, nil, "http2", &structs.ServiceConfigEntry{
|
||||
snap := newTestSnapshot(t, nil, "http2", nil, &structs.ServiceConfigEntry{
|
||||
Kind: structs.ServiceDefaults,
|
||||
Name: "db",
|
||||
Protocol: "http2",
|
||||
|
@ -476,7 +476,7 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_HTTP2(t *testing.T) {
|
|||
|
||||
// -- reconfigure with a no-op discovery chain
|
||||
|
||||
snap = newTestSnapshot(t, snap, "http2", &structs.ServiceConfigEntry{
|
||||
snap = newTestSnapshot(t, snap, "http2", nil, &structs.ServiceConfigEntry{
|
||||
Kind: structs.ServiceDefaults,
|
||||
Name: "db",
|
||||
Protocol: "http2",
|
||||
|
@ -565,7 +565,7 @@ func TestServer_DeltaAggregatedResources_v3_SlowEndpointPopulation(t *testing.T)
|
|||
|
||||
var snap *proxycfg.ConfigSnapshot
|
||||
testutil.RunStep(t, "get into initial state", func(t *testing.T) {
|
||||
snap = newTestSnapshot(t, nil, "")
|
||||
snap = newTestSnapshot(t, nil, "", nil)
|
||||
|
||||
// Send initial cluster discover.
|
||||
envoy.SendDeltaReq(t, xdscommon.ClusterType, &envoy_discovery_v3.DeltaDiscoveryRequest{})
|
||||
|
@ -651,7 +651,7 @@ func TestServer_DeltaAggregatedResources_v3_SlowEndpointPopulation(t *testing.T)
|
|||
testutil.RunStep(t, "delayed endpoint update finally comes in", func(t *testing.T) {
|
||||
// Trigger the xds.Server select{} to wake up and notice our hack is disabled.
|
||||
// The actual contents of this change are irrelevant.
|
||||
snap = newTestSnapshot(t, snap, "")
|
||||
snap = newTestSnapshot(t, snap, "", nil)
|
||||
mgr.DeliverConfig(t, sid, snap)
|
||||
|
||||
assertDeltaResponseSent(t, envoy.deltaStream.sendCh, &envoy_discovery_v3.DeltaDiscoveryResponse{
|
||||
|
@ -694,7 +694,7 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_TCP_clusterChangesImpa
|
|||
|
||||
var snap *proxycfg.ConfigSnapshot
|
||||
testutil.RunStep(t, "get into initial state", func(t *testing.T) {
|
||||
snap = newTestSnapshot(t, nil, "")
|
||||
snap = newTestSnapshot(t, nil, "", nil)
|
||||
|
||||
// Send initial cluster discover.
|
||||
envoy.SendDeltaReq(t, xdscommon.ClusterType, &envoy_discovery_v3.DeltaDiscoveryRequest{})
|
||||
|
@ -770,7 +770,7 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_TCP_clusterChangesImpa
|
|||
|
||||
testutil.RunStep(t, "trigger cluster update needing implicit endpoint replacements", func(t *testing.T) {
|
||||
// Update the snapshot in a way that causes a single cluster update.
|
||||
snap = newTestSnapshot(t, snap, "", &structs.ServiceResolverConfigEntry{
|
||||
snap = newTestSnapshot(t, snap, "", nil, &structs.ServiceResolverConfigEntry{
|
||||
Kind: structs.ServiceResolver,
|
||||
Name: "db",
|
||||
ConnectTimeout: 1337 * time.Second,
|
||||
|
@ -839,7 +839,7 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_HTTP2_RDS_listenerChan
|
|||
assertDeltaChanBlocked(t, envoy.deltaStream.sendCh)
|
||||
|
||||
// Deliver a new snapshot (tcp with one http upstream with no-op disco chain)
|
||||
snap = newTestSnapshot(t, nil, "http2", &structs.ServiceConfigEntry{
|
||||
snap = newTestSnapshot(t, nil, "http2", nil, &structs.ServiceConfigEntry{
|
||||
Kind: structs.ServiceDefaults,
|
||||
Name: "db",
|
||||
Protocol: "http2",
|
||||
|
@ -934,7 +934,7 @@ func TestServer_DeltaAggregatedResources_v3_BasicProtocol_HTTP2_RDS_listenerChan
|
|||
// Update the snapshot in a way that causes a single listener update.
|
||||
//
|
||||
// Downgrade from http2 to http
|
||||
snap = newTestSnapshot(t, snap, "http", &structs.ServiceConfigEntry{
|
||||
snap = newTestSnapshot(t, snap, "http", nil, &structs.ServiceConfigEntry{
|
||||
Kind: structs.ServiceDefaults,
|
||||
Name: "db",
|
||||
Protocol: "http",
|
||||
|
@ -1110,7 +1110,7 @@ func TestServer_DeltaAggregatedResources_v3_ACLEnforcement(t *testing.T) {
|
|||
// Deliver a new snapshot
|
||||
snap := tt.cfgSnap
|
||||
if snap == nil {
|
||||
snap = newTestSnapshot(t, nil, "")
|
||||
snap = newTestSnapshot(t, nil, "", nil)
|
||||
}
|
||||
mgr.DeliverConfig(t, sid, snap)
|
||||
|
||||
|
@ -1236,7 +1236,7 @@ func TestServer_DeltaAggregatedResources_v3_ACLTokenDeleted_StreamTerminatedDuri
|
|||
}
|
||||
|
||||
// Deliver a new snapshot
|
||||
snap := newTestSnapshot(t, nil, "")
|
||||
snap := newTestSnapshot(t, nil, "", nil)
|
||||
mgr.DeliverConfig(t, sid, snap)
|
||||
|
||||
assertDeltaResponseSent(t, envoy.deltaStream.sendCh, &envoy_discovery_v3.DeltaDiscoveryResponse{
|
||||
|
@ -1334,7 +1334,7 @@ func TestServer_DeltaAggregatedResources_v3_ACLTokenDeleted_StreamTerminatedInBa
|
|||
}
|
||||
|
||||
// Deliver a new snapshot
|
||||
snap := newTestSnapshot(t, nil, "")
|
||||
snap := newTestSnapshot(t, nil, "", nil)
|
||||
mgr.DeliverConfig(t, sid, snap)
|
||||
|
||||
assertDeltaResponseSent(t, envoy.deltaStream.sendCh, &envoy_discovery_v3.DeltaDiscoveryResponse{
|
||||
|
@ -1444,7 +1444,7 @@ func TestServer_DeltaAggregatedResources_v3_CapacityReached(t *testing.T) {
|
|||
mgr.RegisterProxy(t, sid)
|
||||
mgr.DrainStreams(sid)
|
||||
|
||||
snap := newTestSnapshot(t, nil, "")
|
||||
snap := newTestSnapshot(t, nil, "", nil)
|
||||
|
||||
envoy.SendDeltaReq(t, xdscommon.ClusterType, &envoy_discovery_v3.DeltaDiscoveryRequest{
|
||||
InitialResourceVersions: mustMakeVersionMap(t,
|
||||
|
@ -1477,7 +1477,7 @@ func TestServer_DeltaAggregatedResources_v3_StreamDrained(t *testing.T) {
|
|||
mgr.RegisterProxy(t, sid)
|
||||
|
||||
testutil.RunStep(t, "successful request/response", func(t *testing.T) {
|
||||
snap := newTestSnapshot(t, nil, "")
|
||||
snap := newTestSnapshot(t, nil, "", nil)
|
||||
|
||||
envoy.SendDeltaReq(t, xdscommon.ClusterType, &envoy_discovery_v3.DeltaDiscoveryRequest{
|
||||
InitialResourceVersions: mustMakeVersionMap(t,
|
||||
|
|
|
@ -88,9 +88,8 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound
|
|||
extCfg := extensioncommon.RuntimeConfig{
|
||||
EnvoyExtension: ext,
|
||||
ServiceName: localSvc,
|
||||
// Upstreams is nil to signify this extension is not being applied to an upstream service, but rather to the local service.
|
||||
Upstreams: nil,
|
||||
LocalUpstreams: upstreamMap,
|
||||
IsSourcedFromUpstream: false,
|
||||
Upstreams: upstreamMap,
|
||||
Kind: kind,
|
||||
Protocol: proxyConfigProtocol(cfgSnap.Proxy.Config),
|
||||
}
|
||||
|
@ -124,19 +123,24 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound
|
|||
}
|
||||
}
|
||||
|
||||
// If applicable, include extension configuration for remote upstreams of the local service.
|
||||
// This only applies to specific extensions authorized to apply to remote proxies.
|
||||
for svc, exts := range extensionsMap {
|
||||
extensionConfigurationsMap[svc] = []extensioncommon.RuntimeConfig{}
|
||||
for _, ext := range exts {
|
||||
if appliesToRemoteDownstreams(ext) {
|
||||
extCfg := extensioncommon.RuntimeConfig{
|
||||
EnvoyExtension: ext,
|
||||
Kind: kind,
|
||||
ServiceName: svc,
|
||||
IsSourcedFromUpstream: true,
|
||||
Upstreams: upstreamMap,
|
||||
Protocol: proxyConfigProtocol(cfgSnap.Proxy.Config),
|
||||
}
|
||||
extensionConfigurationsMap[svc] = append(extensionConfigurationsMap[svc], extCfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extensionConfigurationsMap
|
||||
}
|
||||
|
@ -169,3 +173,16 @@ func proxyConfigProtocol(cfg map[string]any) string {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// appliesToRemoteDownstreams returns true if the given extension should be applied to remote downstream proxies of the
|
||||
// service targeted by the extension, rather than just the local proxy. In the context of GetRuntimeConfigurations, this
|
||||
// determines whether the extension should apply to the local proxy (a downstream of the configured service).
|
||||
//
|
||||
// Currently, only the AWS Lambda and Validate extensions are allowed to apply to downstream proxies.
|
||||
//
|
||||
// See extensioncommon.RuntimeConfig.IsSourcedFromUpstream and UpstreamEnvoyExtender doc for more information. We make
|
||||
// this check here out of precaution s.t. even if an unauthorized extension is erroneously constructed with the
|
||||
// UpstreamEnvoyExtender, this check will not allow the upstream extension configuration to be provided.
|
||||
func appliesToRemoteDownstreams(extension api.EnvoyExtension) bool {
|
||||
return extension.Name == api.BuiltinAWSLambdaExtension || extension.Name == api.BuiltinValidateExtension
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ func TestGetRuntimeConfigurations_TerminatingGateway(t *testing.T) {
|
|||
},
|
||||
},
|
||||
ServiceName: webService,
|
||||
IsSourcedFromUpstream: true,
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
apiService: {
|
||||
SNI: map[string]struct{}{
|
||||
|
@ -107,7 +108,8 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
|
|||
Namespace: "default",
|
||||
}
|
||||
|
||||
// Setup multiple extensions to ensure all of them are in the ExtensionConfiguration map.
|
||||
// Setup multiple extensions to ensure only the expected one (AWS) is in the ExtensionConfiguration map
|
||||
// sourced from upstreams, and all local extensions are included.
|
||||
envoyExtensions := []structs.EnvoyExtension{
|
||||
{
|
||||
Name: api.BuiltinAWSLambdaExtension,
|
||||
|
@ -159,26 +161,7 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
ServiceName: dbService,
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
dbService: {
|
||||
SNI: map[string]struct{}{
|
||||
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
|
||||
},
|
||||
EnvoyID: "db",
|
||||
OutgoingProxyKind: "connect-proxy",
|
||||
},
|
||||
},
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
},
|
||||
{
|
||||
EnvoyExtension: api.EnvoyExtension{
|
||||
Name: "ext2",
|
||||
Arguments: map[string]interface{}{
|
||||
"arg1": 1,
|
||||
"arg2": "val2",
|
||||
},
|
||||
},
|
||||
ServiceName: dbService,
|
||||
IsSourcedFromUpstream: true,
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
dbService: {
|
||||
SNI: map[string]struct{}{
|
||||
|
@ -207,26 +190,7 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
ServiceName: dbService,
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
dbService: {
|
||||
SNI: map[string]struct{}{
|
||||
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
|
||||
},
|
||||
EnvoyID: "db",
|
||||
OutgoingProxyKind: "terminating-gateway",
|
||||
},
|
||||
},
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
},
|
||||
{
|
||||
EnvoyExtension: api.EnvoyExtension{
|
||||
Name: "ext2",
|
||||
Arguments: map[string]interface{}{
|
||||
"arg1": 1,
|
||||
"arg2": "val2",
|
||||
},
|
||||
},
|
||||
ServiceName: dbService,
|
||||
IsSourcedFromUpstream: true,
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
dbService: {
|
||||
SNI: map[string]struct{}{
|
||||
|
@ -257,8 +221,8 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
|
|||
},
|
||||
ServiceName: webService,
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
Upstreams: nil,
|
||||
LocalUpstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
IsSourcedFromUpstream: false,
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
dbService: {
|
||||
SNI: map[string]struct{}{
|
||||
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
|
||||
|
@ -278,8 +242,8 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
|
|||
},
|
||||
ServiceName: webService,
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
Upstreams: nil,
|
||||
LocalUpstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
IsSourcedFromUpstream: false,
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
dbService: {
|
||||
SNI: map[string]struct{}{
|
||||
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "db:127.0.0.1:9191",
|
||||
"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",
|
||||
"statPrefix": "upstream.db.default.default.dc1",
|
||||
"routeConfig": {
|
||||
"name": "db",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"name": "db.default.default.dc1",
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||
"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",
|
||||
"statPrefix": "upstream.prepared_query_geo-cache",
|
||||
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "public_listener:0.0.0.0:9999",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "0.0.0.0",
|
||||
"portValue": 9999
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.rbac",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC",
|
||||
"rules": {},
|
||||
"statPrefix": "connect_authz"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"statPrefix": "public_listener",
|
||||
"cluster": "local_app"
|
||||
}
|
||||
}
|
||||
],
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"trafficDirection": "INBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "db:127.0.0.1:9191",
|
||||
"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",
|
||||
"statPrefix": "upstream.db.default.default.dc1",
|
||||
"routeConfig": {
|
||||
"name": "db",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"name": "db.default.default.dc1",
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||
"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",
|
||||
"statPrefix": "upstream.prepared_query_geo-cache",
|
||||
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "public_listener:0.0.0.0:9999",
|
||||
"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",
|
||||
"statPrefix": "public_listener",
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"name": "public_listener",
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "local_app"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "trust-domain",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "partition",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "namespace",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "datacenter",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\4"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "service",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\5"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.lua",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua",
|
||||
"inlineCode": "\nfunction envoy_on_request(request_handle)\n request_handle:headers():add(\"test\", \"test\")\nend"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
},
|
||||
"forwardClientCertDetails": "APPEND_FORWARD",
|
||||
"setCurrentClientCertDetails": {
|
||||
"subject": true,
|
||||
"cert": true,
|
||||
"chain": true,
|
||||
"dns": true,
|
||||
"uri": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"alpnProtocols": [
|
||||
"http/1.1"
|
||||
]
|
||||
},
|
||||
"requireClientCertificate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"trafficDirection": "INBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
272
agent/xds/testdata/builtin_extension/listeners/lua-outbound-applies-to-local-upstreams.latest.golden
vendored
Normal file
272
agent/xds/testdata/builtin_extension/listeners/lua-outbound-applies-to-local-upstreams.latest.golden
vendored
Normal file
|
@ -0,0 +1,272 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "db:127.0.0.1:9191",
|
||||
"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",
|
||||
"statPrefix": "upstream.db.default.default.dc1",
|
||||
"routeConfig": {
|
||||
"name": "db",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"name": "db.default.default.dc1",
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.lua",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua",
|
||||
"inlineCode": "\nfunction envoy_on_request(request_handle)\n request_handle:headers():add(\"test\", \"test\")\nend"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||
"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",
|
||||
"statPrefix": "upstream.prepared_query_geo-cache",
|
||||
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "public_listener:0.0.0.0:9999",
|
||||
"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",
|
||||
"statPrefix": "public_listener",
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"name": "public_listener",
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "local_app"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "trust-domain",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "partition",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "namespace",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "datacenter",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\4"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "service",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
},
|
||||
"forwardClientCertDetails": "APPEND_FORWARD",
|
||||
"setCurrentClientCertDetails": {
|
||||
"subject": true,
|
||||
"cert": true,
|
||||
"chain": true,
|
||||
"dns": true,
|
||||
"uri": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"alpnProtocols": [
|
||||
"http/1.1"
|
||||
]
|
||||
},
|
||||
"requireClientCertificate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"trafficDirection": "INBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "db:127.0.0.1:9191",
|
||||
"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",
|
||||
"statPrefix": "upstream.db.default.default.dc1",
|
||||
"routeConfig": {
|
||||
"name": "db",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"name": "db.default.default.dc1",
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.lua",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua",
|
||||
"inlineCode": "\nfunction envoy_on_request(request_handle)\n request_handle:headers():add(\"test\", \"test\")\nend"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||
"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",
|
||||
"statPrefix": "upstream.prepared_query_geo-cache",
|
||||
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "public_listener:0.0.0.0:9999",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "0.0.0.0",
|
||||
"portValue": 9999
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.rbac",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC",
|
||||
"rules": {},
|
||||
"statPrefix": "connect_authz"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"statPrefix": "public_listener",
|
||||
"cluster": "local_app"
|
||||
}
|
||||
}
|
||||
],
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"trafficDirection": "INBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -48,9 +48,10 @@ func newTestSnapshot(
|
|||
t *testing.T,
|
||||
prevSnap *proxycfg.ConfigSnapshot,
|
||||
dbServiceProtocol string,
|
||||
nsFn func(ns *structs.NodeService),
|
||||
additionalEntries ...structs.ConfigEntry,
|
||||
) *proxycfg.ConfigSnapshot {
|
||||
snap := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, additionalEntries...)
|
||||
snap := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nsFn, nil, additionalEntries...)
|
||||
snap.ConnectProxy.PreparedQueryEndpoints = map[proxycfg.UpstreamID]structs.CheckServiceNodes{
|
||||
UID("prepared_query:geo-cache"): proxycfg.TestPreparedQueryNodes(t, "geo-cache"),
|
||||
}
|
||||
|
|
|
@ -43,6 +43,10 @@ const (
|
|||
BuiltinLuaExtension string = "builtin/lua"
|
||||
BuiltinLocalRatelimitExtension string = "builtin/http/localratelimit"
|
||||
BuiltinWasmExtension string = "builtin/wasm"
|
||||
// BuiltinValidateExtension should not be exposed directly or accepted as a valid configured
|
||||
// extension type, as it is only used indirectly via troubleshooting tools. It is included here
|
||||
// for common reference alongside other builtin extensions.
|
||||
BuiltinValidateExtension string = "builtin/proxy/validate"
|
||||
)
|
||||
|
||||
type ConfigEntry interface {
|
||||
|
|
|
@ -5,8 +5,6 @@ package extensioncommon
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
|
@ -37,7 +35,7 @@ type BasicExtension interface {
|
|||
|
||||
// PatchFilter patches an Envoy filter to include the custom Envoy
|
||||
// configuration required to integrate with the built in extension template.
|
||||
PatchFilter(*RuntimeConfig, *envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error)
|
||||
PatchFilter(cfg *RuntimeConfig, f *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error)
|
||||
}
|
||||
|
||||
var _ EnvoyExtender = (*BasicEnvoyExtender)(nil)
|
||||
|
@ -48,20 +46,26 @@ type BasicEnvoyExtender struct {
|
|||
Extension BasicExtension
|
||||
}
|
||||
|
||||
func (envoyExtension *BasicEnvoyExtender) Validate(config *RuntimeConfig) error {
|
||||
func (b *BasicEnvoyExtender) Validate(_ *RuntimeConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (envoyExtender *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config *RuntimeConfig) (*xdscommon.IndexedResources, error) {
|
||||
func (b *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config *RuntimeConfig) (*xdscommon.IndexedResources, error) {
|
||||
var resultErr error
|
||||
|
||||
// We don't support patching the local proxy with an upstream's config except in special
|
||||
// cases supported by UpstreamEnvoyExtender.
|
||||
if config.IsSourcedFromUpstream {
|
||||
return nil, fmt.Errorf("%q extension applied as local config but is sourced from an upstream of the local service", config.EnvoyExtension.Name)
|
||||
}
|
||||
|
||||
switch config.Kind {
|
||||
case api.ServiceKindTerminatingGateway, api.ServiceKindConnectProxy:
|
||||
default:
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
if !envoyExtender.Extension.CanApply(config) {
|
||||
if !b.Extension.CanApply(config) {
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
|
@ -73,19 +77,7 @@ func (envoyExtender *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedReso
|
|||
for nameOrSNI, msg := range resources.Index[indexType] {
|
||||
switch resource := msg.(type) {
|
||||
case *envoy_cluster_v3.Cluster:
|
||||
// If the Envoy extension configuration is for an upstream service, the Cluster's
|
||||
// name must match the upstream service's SNI.
|
||||
if config.IsUpstream() && !config.MatchesUpstreamServiceSNI(nameOrSNI) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the extension's config is for an an inbound listener, the Cluster's name
|
||||
// must be xdscommon.LocalAppClusterName.
|
||||
if !config.IsUpstream() && nameOrSNI == xdscommon.LocalAppClusterName {
|
||||
continue
|
||||
}
|
||||
|
||||
newCluster, patched, err := envoyExtender.Extension.PatchCluster(config, resource)
|
||||
newCluster, patched, err := b.Extension.PatchCluster(config, resource)
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster: %w", err))
|
||||
continue
|
||||
|
@ -95,7 +87,7 @@ func (envoyExtender *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedReso
|
|||
}
|
||||
|
||||
case *envoy_listener_v3.Listener:
|
||||
newListener, patched, err := envoyExtender.patchListener(config, resource)
|
||||
newListener, patched, err := b.patchListener(config, resource)
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener: %w", err))
|
||||
continue
|
||||
|
@ -105,19 +97,7 @@ func (envoyExtender *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedReso
|
|||
}
|
||||
|
||||
case *envoy_route_v3.RouteConfiguration:
|
||||
// If the Envoy extension configuration is for an upstream service, the route's
|
||||
// name must match the upstream service's Envoy ID.
|
||||
matchesEnvoyID := config.EnvoyID() == nameOrSNI
|
||||
if config.IsUpstream() && !config.MatchesUpstreamServiceSNI(nameOrSNI) && !matchesEnvoyID {
|
||||
continue
|
||||
}
|
||||
|
||||
// There aren't routes for inbound services.
|
||||
if !config.IsUpstream() {
|
||||
continue
|
||||
}
|
||||
|
||||
newRoute, patched, err := envoyExtender.Extension.PatchRoute(config, resource)
|
||||
newRoute, patched, err := b.Extension.PatchRoute(config, resource)
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching route: %w", err))
|
||||
continue
|
||||
|
@ -134,40 +114,25 @@ func (envoyExtender *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedReso
|
|||
return resources, resultErr
|
||||
}
|
||||
|
||||
func (envoyExtension BasicEnvoyExtender) patchListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
func (b *BasicEnvoyExtender) patchListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
switch config.Kind {
|
||||
case api.ServiceKindTerminatingGateway:
|
||||
return envoyExtension.patchTerminatingGatewayListener(config, l)
|
||||
return b.patchTerminatingGatewayListener(config, l)
|
||||
case api.ServiceKindConnectProxy:
|
||||
return envoyExtension.patchConnectProxyListener(config, l)
|
||||
return b.patchConnectProxyListener(config, l)
|
||||
}
|
||||
return l, false, nil
|
||||
}
|
||||
|
||||
func (b BasicEnvoyExtender) patchTerminatingGatewayListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
// We don't support directly targeting terminating gateways with extensions.
|
||||
if !config.IsUpstream() {
|
||||
return l, false, nil
|
||||
}
|
||||
|
||||
func (b *BasicEnvoyExtender) patchTerminatingGatewayListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
var resultErr error
|
||||
patched := false
|
||||
|
||||
for _, filterChain := range l.FilterChains {
|
||||
sni := getSNI(filterChain)
|
||||
|
||||
if sni == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// The filter chain's SNI must match the upstream service's SNI.
|
||||
if !config.MatchesUpstreamServiceSNI(sni) {
|
||||
continue
|
||||
}
|
||||
|
||||
var filters []*envoy_listener_v3.Filter
|
||||
|
||||
for _, filter := range filterChain.Filters {
|
||||
newFilter, ok, err := b.Extension.PatchFilter(config, filter)
|
||||
newFilter, ok, err := b.Extension.PatchFilter(config, filter, IsInboundPublicListener(l))
|
||||
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
|
||||
|
@ -187,37 +152,19 @@ func (b BasicEnvoyExtender) patchTerminatingGatewayListener(config *RuntimeConfi
|
|||
return l, patched, resultErr
|
||||
}
|
||||
|
||||
func (b BasicEnvoyExtender) patchConnectProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
func (b *BasicEnvoyExtender) patchConnectProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
var resultErr error
|
||||
patched := false
|
||||
|
||||
envoyID := ""
|
||||
if i := strings.IndexByte(l.Name, ':'); i != -1 {
|
||||
envoyID = l.Name[:i]
|
||||
}
|
||||
|
||||
if config.IsUpstream() && envoyID == xdscommon.OutboundListenerName {
|
||||
if IsOutboundTProxyListener(l) {
|
||||
return b.patchTProxyListener(config, l)
|
||||
}
|
||||
|
||||
// If the Envoy extension configuration is for an upstream service, the listener's
|
||||
// name must match the upstream service's EnvoyID or be the outbound listener.
|
||||
if config.IsUpstream() && envoyID != config.EnvoyID() {
|
||||
return l, false, nil
|
||||
}
|
||||
|
||||
// If the Envoy extension configuration is for inbound resources, the
|
||||
// listener must be named xdscommon.PublicListenerName.
|
||||
if !config.IsUpstream() && envoyID != xdscommon.PublicListenerName {
|
||||
return l, false, nil
|
||||
}
|
||||
|
||||
var patched bool
|
||||
|
||||
for _, filterChain := range l.FilterChains {
|
||||
var filters []*envoy_listener_v3.Filter
|
||||
|
||||
for _, filter := range filterChain.Filters {
|
||||
newFilter, ok, err := b.Extension.PatchFilter(config, filter)
|
||||
newFilter, ok, err := b.Extension.PatchFilter(config, filter, IsInboundPublicListener(l))
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
|
||||
filters = append(filters, filter)
|
||||
|
@ -237,7 +184,7 @@ func (b BasicEnvoyExtender) patchConnectProxyListener(config *RuntimeConfig, l *
|
|||
return l, patched, resultErr
|
||||
}
|
||||
|
||||
func (b BasicEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
func (b *BasicEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
var resultErr error
|
||||
patched := false
|
||||
|
||||
|
@ -252,7 +199,7 @@ func (b BasicEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l *envoy_
|
|||
}
|
||||
|
||||
for _, filter := range filterChain.Filters {
|
||||
newFilter, ok, err := b.Extension.PatchFilter(config, filter)
|
||||
newFilter, ok, err := b.Extension.PatchFilter(config, filter, IsInboundPublicListener(l))
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
|
||||
filters = append(filters, filter)
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package extensioncommon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUpstreamConfigSourceLimitations(t *testing.T) {
|
||||
type testCase struct {
|
||||
extender EnvoyExtender
|
||||
config *RuntimeConfig
|
||||
ok bool
|
||||
errMsg string
|
||||
}
|
||||
cases := map[string]testCase{
|
||||
"upstream extender non-upstream config": {
|
||||
extender: &UpstreamEnvoyExtender{},
|
||||
config: &RuntimeConfig{
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
ServiceName: api.CompoundServiceName{Name: "api"},
|
||||
Upstreams: map[api.CompoundServiceName]*UpstreamData{},
|
||||
IsSourcedFromUpstream: false,
|
||||
EnvoyExtension: api.EnvoyExtension{
|
||||
Name: api.BuiltinAWSLambdaExtension,
|
||||
},
|
||||
},
|
||||
ok: false,
|
||||
errMsg: fmt.Sprintf("%q extension applied as upstream config but is not sourced from an upstream of the local service", api.BuiltinAWSLambdaExtension),
|
||||
},
|
||||
"basic extender upstream config": {
|
||||
extender: &BasicEnvoyExtender{},
|
||||
config: &RuntimeConfig{
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
ServiceName: api.CompoundServiceName{Name: "api"},
|
||||
Upstreams: map[api.CompoundServiceName]*UpstreamData{},
|
||||
IsSourcedFromUpstream: true,
|
||||
EnvoyExtension: api.EnvoyExtension{
|
||||
Name: api.BuiltinLuaExtension,
|
||||
},
|
||||
},
|
||||
ok: false,
|
||||
errMsg: fmt.Sprintf("%q extension applied as local config but is sourced from an upstream of the local service", api.BuiltinLuaExtension),
|
||||
},
|
||||
"list extender upstream config": {
|
||||
extender: &ListEnvoyExtender{},
|
||||
config: &RuntimeConfig{
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
ServiceName: api.CompoundServiceName{Name: "api"},
|
||||
Upstreams: map[api.CompoundServiceName]*UpstreamData{},
|
||||
IsSourcedFromUpstream: true,
|
||||
EnvoyExtension: api.EnvoyExtension{
|
||||
Name: api.BuiltinLuaExtension,
|
||||
},
|
||||
},
|
||||
ok: false,
|
||||
errMsg: fmt.Sprintf("%q extension applied as local config but is sourced from an upstream of the local service", api.BuiltinLuaExtension),
|
||||
},
|
||||
}
|
||||
|
||||
for n, tc := range cases {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
_, err := tc.extender.Extend(nil, tc.config)
|
||||
if tc.ok {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, tc.errMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,8 +5,6 @@ package extensioncommon
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
|
@ -54,6 +52,12 @@ func (*ListEnvoyExtender) Validate(config *RuntimeConfig) error {
|
|||
func (e *ListEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config *RuntimeConfig) (*xdscommon.IndexedResources, error) {
|
||||
var resultErr error
|
||||
|
||||
// We don't support patching the local proxy with an upstream's config except in special
|
||||
// cases supported by UpstreamEnvoyExtender.
|
||||
if config.IsSourcedFromUpstream {
|
||||
return nil, fmt.Errorf("%q extension applied as local config but is sourced from an upstream of the local service", config.EnvoyExtension.Name)
|
||||
}
|
||||
|
||||
switch config.Kind {
|
||||
case api.ServiceKindTerminatingGateway, api.ServiceKindConnectProxy:
|
||||
default:
|
||||
|
@ -67,7 +71,6 @@ func (e *ListEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config
|
|||
clusters := make(ClusterMap)
|
||||
routes := make(RouteMap)
|
||||
listeners := make(ListenerMap)
|
||||
isUpstream := config.IsUpstream()
|
||||
|
||||
for _, indexType := range []string{
|
||||
xdscommon.ListenerType,
|
||||
|
@ -77,36 +80,12 @@ func (e *ListEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config
|
|||
for nameOrSNI, msg := range resources.Index[indexType] {
|
||||
switch resource := msg.(type) {
|
||||
case *envoy_cluster_v3.Cluster:
|
||||
// If the Envoy extension configuration is for an upstream service, the Cluster's
|
||||
// name must match the upstream service's SNI.
|
||||
if isUpstream && !config.MatchesUpstreamServiceSNI(nameOrSNI) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the extension's config is for an an inbound listener, the Cluster's name
|
||||
// must be xdscommon.LocalAppClusterName.
|
||||
if !isUpstream && nameOrSNI == xdscommon.LocalAppClusterName {
|
||||
continue
|
||||
}
|
||||
|
||||
clusters[nameOrSNI] = resource
|
||||
|
||||
case *envoy_listener_v3.Listener:
|
||||
listeners[nameOrSNI] = resource
|
||||
|
||||
case *envoy_route_v3.RouteConfiguration:
|
||||
// If the Envoy extension configuration is for an upstream service, the route's
|
||||
// name must match the upstream service's Envoy ID.
|
||||
matchesEnvoyID := config.EnvoyID() == nameOrSNI
|
||||
if isUpstream && !config.MatchesUpstreamServiceSNI(nameOrSNI) && !matchesEnvoyID {
|
||||
continue
|
||||
}
|
||||
|
||||
// There aren't routes for inbound services.
|
||||
if !isUpstream {
|
||||
continue
|
||||
}
|
||||
|
||||
routes[nameOrSNI] = resource
|
||||
|
||||
default:
|
||||
|
@ -156,11 +135,6 @@ func (e ListEnvoyExtender) patchListeners(config *RuntimeConfig, m ListenerMap)
|
|||
}
|
||||
|
||||
func (e ListEnvoyExtender) patchTerminatingGatewayListeners(config *RuntimeConfig, l ListenerMap) (ListenerMap, error) {
|
||||
// We don't support directly targeting terminating gateways with extensions.
|
||||
if !config.IsUpstream() {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
var resultErr error
|
||||
for _, listener := range l {
|
||||
for _, filterChain := range listener.FilterChains {
|
||||
|
@ -170,11 +144,6 @@ func (e ListEnvoyExtender) patchTerminatingGatewayListeners(config *RuntimeConfi
|
|||
continue
|
||||
}
|
||||
|
||||
// The filter chain's SNI must match the upstream service's SNI.
|
||||
if !config.MatchesUpstreamServiceSNI(sni) {
|
||||
continue
|
||||
}
|
||||
|
||||
patchedFilters, err := e.Extension.PatchFilters(config, filterChain.Filters)
|
||||
if err == nil {
|
||||
filterChain.Filters = patchedFilters
|
||||
|
@ -191,14 +160,8 @@ func (e ListEnvoyExtender) patchTerminatingGatewayListeners(config *RuntimeConfi
|
|||
func (e ListEnvoyExtender) patchConnectProxyListeners(config *RuntimeConfig, l ListenerMap) (ListenerMap, error) {
|
||||
var resultErr error
|
||||
|
||||
isUpstream := config.IsUpstream()
|
||||
for nameOrSNI, listener := range l {
|
||||
envoyID := ""
|
||||
if id, _, found := strings.Cut(listener.Name, ":"); found {
|
||||
envoyID = id
|
||||
}
|
||||
|
||||
if isUpstream && envoyID == xdscommon.OutboundListenerName {
|
||||
if IsOutboundTProxyListener(listener) {
|
||||
patchedListener, err := e.patchTProxyListener(config, listener)
|
||||
if err == nil {
|
||||
l[nameOrSNI] = patchedListener
|
||||
|
@ -208,18 +171,6 @@ func (e ListEnvoyExtender) patchConnectProxyListeners(config *RuntimeConfig, l L
|
|||
continue
|
||||
}
|
||||
|
||||
// If the Envoy extension configuration is for an upstream service, the listener's
|
||||
// name must match the upstream service's EnvoyID or be the outbound listener.
|
||||
if isUpstream && envoyID != config.EnvoyID() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the Envoy extension configuration is for inbound resources, the
|
||||
// listener must be named xdscommon.PublicListenerName.
|
||||
if config.IsLocal() && envoyID != xdscommon.PublicListenerName {
|
||||
continue
|
||||
}
|
||||
|
||||
patchedListener, err := e.patchConnectProxyListener(config, listener)
|
||||
if err == nil {
|
||||
l[nameOrSNI] = patchedListener
|
||||
|
|
|
@ -4,12 +4,16 @@
|
|||
package extensioncommon
|
||||
|
||||
import (
|
||||
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MakeUpstreamTLSTransportSocket generates an Envoy transport socket for the given TLS context.
|
||||
|
@ -59,3 +63,40 @@ func MakeFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, erro
|
|||
ConfigType: &envoy_listener_v3.Filter_TypedConfig{TypedConfig: any},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetListenerEnvoyID returns the Envoy ID string parsed from the name of the given Listener. If none is found, it
|
||||
// returns the empty string.
|
||||
func GetListenerEnvoyID(l *envoy_listener_v3.Listener) string {
|
||||
if id, _, found := strings.Cut(l.Name, ":"); found {
|
||||
return id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsLocalAppCluster returns true if the given Cluster represents the local Cluster, which receives inbound traffic to
|
||||
// the local proxy.
|
||||
func IsLocalAppCluster(c *envoy_cluster_v3.Cluster) bool {
|
||||
return c.Name == xdscommon.LocalAppClusterName
|
||||
}
|
||||
|
||||
// IsRouteToLocalAppCluster takes a RouteConfiguration and returns true if all routes within it target the local
|
||||
// Cluster. Note that because we currently target RouteConfiguration in PatchRoute, we have to check multiple individual
|
||||
// Route resources.
|
||||
func IsRouteToLocalAppCluster(r *envoy_route_v3.RouteConfiguration) bool {
|
||||
clusterNames := RouteClusterNames(r)
|
||||
_, match := clusterNames[xdscommon.LocalAppClusterName]
|
||||
|
||||
return match && len(clusterNames) == 1
|
||||
}
|
||||
|
||||
// IsInboundPublicListener returns true if the given Listener represents the inbound public Listener for the local
|
||||
// service.
|
||||
func IsInboundPublicListener(l *envoy_listener_v3.Listener) bool {
|
||||
return GetListenerEnvoyID(l) == xdscommon.PublicListenerName
|
||||
}
|
||||
|
||||
// IsOutboundTProxyListener returns true if the given Listener represents the outbound TProxy Listener for the local
|
||||
// service.
|
||||
func IsOutboundTProxyListener(l *envoy_listener_v3.Listener) bool {
|
||||
return GetListenerEnvoyID(l) == xdscommon.OutboundListenerName
|
||||
}
|
||||
|
|
|
@ -30,16 +30,30 @@ type RuntimeConfig struct {
|
|||
// EnvoyExtension is the extension that will patch Envoy resources.
|
||||
EnvoyExtension api.EnvoyExtension
|
||||
|
||||
// ServiceName is the name of the service the EnvoyExtension is being applied to. It could be the local service or
|
||||
// an upstream of the local service.
|
||||
// ServiceName is the name of the service the EnvoyExtension is being applied to. It is typically the local service
|
||||
// (IsSourcedFromUpstream = false), but can also be an upstream of the local service (IsSourcedFromUpstream = true).
|
||||
ServiceName api.CompoundServiceName
|
||||
|
||||
// Upstreams will only be configured if the EnvoyExtension is being applied to an upstream.
|
||||
// If there are no Upstreams, then EnvoyExtension is being applied to the local service's resources.
|
||||
// Upstreams represent the upstreams of the local service. This is consistent regardless of the value of
|
||||
// IsSourcedFromUpstream, which refers to the Envoy extension source.
|
||||
Upstreams map[api.CompoundServiceName]*UpstreamData
|
||||
|
||||
// LocalUpstreams will only be configured if the EnvoyExtension is being applied to the local service.
|
||||
LocalUpstreams map[api.CompoundServiceName]*UpstreamData
|
||||
// IsSourcedFromUpstream is set to true only in the exceptional cases where upstream service config contains
|
||||
// extensions that apply to the configured service's downstreams. In those cases, this value will be true when such
|
||||
// a downstream is the local service. In all other cases, IsSourcedFromUpstream will be false.
|
||||
//
|
||||
// This is used exclusively for specific extensions (currently, only AWS Lambda and Validate) in which we
|
||||
// intentionally apply the extension to downstreams rather than the local proxy of the configured service itself.
|
||||
// This is generally dangerous, since it circumvents ACLs for the affected downstream services (the upstream owner
|
||||
// may not have `service:write` for the downstreams).
|
||||
//
|
||||
// Extensions used this way MUST be designed to allow only trusted modifications of downstream proxies that impact
|
||||
// their ability to call the upstream service. Remote configurations MUST NOT be allowed to otherwise modify local
|
||||
// proxies until we support explicit extension capability controls or require privileges higher than the typical
|
||||
// `service:write` required to configure extensions.
|
||||
//
|
||||
// See UpstreamEnvoyExtender for the code that applies RuntimeConfig with this flag set.
|
||||
IsSourcedFromUpstream bool
|
||||
|
||||
// Kind is mode the local Envoy proxy is running in. For now, only connect proxy and
|
||||
// terminating gateways are supported.
|
||||
|
@ -49,33 +63,27 @@ type RuntimeConfig struct {
|
|||
Protocol string
|
||||
}
|
||||
|
||||
// IsLocal indicates if the extension configuration is for the proxy's local service.
|
||||
func (ec RuntimeConfig) IsLocal() bool {
|
||||
return !ec.IsUpstream()
|
||||
}
|
||||
|
||||
// IsUpstream indicates if the extension configuration is for an upstream service.
|
||||
func (ec RuntimeConfig) IsUpstream() bool {
|
||||
_, ok := ec.Upstreams[ec.ServiceName]
|
||||
return ok
|
||||
}
|
||||
|
||||
// MatchesUpstreamServiceSNI indicates if the extension configuration is for an upstream service
|
||||
// that matches the given SNI.
|
||||
func (ec RuntimeConfig) MatchesUpstreamServiceSNI(sni string) bool {
|
||||
u := ec.Upstreams[ec.ServiceName]
|
||||
// that matches the given SNI, if the RuntimeConfig corresponds to an upstream of the local service.
|
||||
// Only used when IsSourcedFromUpstream is true.
|
||||
func (c RuntimeConfig) MatchesUpstreamServiceSNI(sni string) bool {
|
||||
u := c.Upstreams[c.ServiceName]
|
||||
_, match := u.SNI[sni]
|
||||
return match
|
||||
}
|
||||
|
||||
// EnvoyID returns the unique Envoy identifier of the upstream service.
|
||||
func (ec RuntimeConfig) EnvoyID() string {
|
||||
u := ec.Upstreams[ec.ServiceName]
|
||||
// UpstreamEnvoyID returns the unique Envoy identifier of the upstream service, if the RuntimeConfig corresponds to an
|
||||
// upstream of the local service. Note that this could be the local service if it targets itself as an upstream.
|
||||
// Only used when IsSourcedFromUpstream is true.
|
||||
func (c RuntimeConfig) UpstreamEnvoyID() string {
|
||||
u := c.Upstreams[c.ServiceName]
|
||||
return u.EnvoyID
|
||||
}
|
||||
|
||||
// OutgoingProxyKind returns the service kind for the outgoing listener of an upstream service.
|
||||
func (ec RuntimeConfig) OutgoingProxyKind() api.ServiceKind {
|
||||
u := ec.Upstreams[ec.ServiceName]
|
||||
// UpstreamOutgoingProxyKind returns the service kind for the outgoing listener of the upstream service, if the
|
||||
// RuntimeConfig corresponds to an upstream of the local service.
|
||||
// Only used when IsSourcedFromUpstream is true.
|
||||
func (c RuntimeConfig) UpstreamOutgoingProxyKind() api.ServiceKind {
|
||||
u := c.Upstreams[c.ServiceName]
|
||||
return u.OutgoingProxyKind
|
||||
}
|
||||
|
|
|
@ -30,13 +30,6 @@ func makeTestRuntimeConfig() RuntimeConfig {
|
|||
return rc
|
||||
}
|
||||
|
||||
func TestRuntimeConfig_IsUpstream(t *testing.T) {
|
||||
rc := makeTestRuntimeConfig()
|
||||
require.True(t, rc.IsUpstream())
|
||||
delete(rc.Upstreams, rc.ServiceName)
|
||||
require.False(t, rc.IsUpstream())
|
||||
}
|
||||
|
||||
func TestRuntimeConfig_MatchesUpstreamServiceSNI(t *testing.T) {
|
||||
rc := makeTestRuntimeConfig()
|
||||
require.True(t, rc.MatchesUpstreamServiceSNI("sni1"))
|
||||
|
@ -46,10 +39,10 @@ func TestRuntimeConfig_MatchesUpstreamServiceSNI(t *testing.T) {
|
|||
|
||||
func TestRuntimeConfig_EnvoyID(t *testing.T) {
|
||||
rc := makeTestRuntimeConfig()
|
||||
require.Equal(t, "eid", rc.EnvoyID())
|
||||
require.Equal(t, "eid", rc.UpstreamEnvoyID())
|
||||
}
|
||||
|
||||
func TestRuntimeConfig_OutgoingProxyKind(t *testing.T) {
|
||||
rc := makeTestRuntimeConfig()
|
||||
require.Equal(t, api.ServiceKindTerminatingGateway, rc.OutgoingProxyKind())
|
||||
require.Equal(t, api.ServiceKindTerminatingGateway, rc.UpstreamOutgoingProxyKind())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package extensioncommon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// UpstreamEnvoyExtender facilitates uncommon scenarios in which an upstream service's extension needs to apply changes
|
||||
// to downstram proxies. Separating this mode from the more typical case of extensions patching just the local proxy for
|
||||
// the configured service allows us to more effectively enforce controls over this elevated level of privilege.
|
||||
//
|
||||
// THIS EXTENDER SHOULD NOT BE USED BY ANY NEW EXTENSIONS! It is only intended for use by the builtin AWS Lambda
|
||||
// extension and Validate (read-only) pseudo-extension to support their existing behavior. Future changes to the
|
||||
// extension API will introduce stronger controls around privileged capabilities, at which point this extender can be
|
||||
// removed.
|
||||
//
|
||||
// See documentation in RuntimeConfig.IsSourcedFromUpstream for more details.
|
||||
type UpstreamEnvoyExtender struct {
|
||||
Extension BasicExtension
|
||||
}
|
||||
|
||||
var _ EnvoyExtender = (*UpstreamEnvoyExtender)(nil)
|
||||
|
||||
func (ext *UpstreamEnvoyExtender) Validate(_ *RuntimeConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ext *UpstreamEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config *RuntimeConfig) (*xdscommon.IndexedResources, error) {
|
||||
var resultErr error
|
||||
|
||||
// Assert that extension configuration is exclusively from upstreams of the local service.
|
||||
if !config.IsSourcedFromUpstream {
|
||||
return nil, fmt.Errorf("%q extension applied as upstream config but is not sourced from an upstream of the local service", config.EnvoyExtension.Name)
|
||||
}
|
||||
|
||||
// Only the AWS Lambda and Validate extensions are allowed to apply to downstream proxies.
|
||||
switch config.EnvoyExtension.Name {
|
||||
case api.BuiltinAWSLambdaExtension, api.BuiltinValidateExtension:
|
||||
default:
|
||||
return nil, fmt.Errorf("extension %q is not permitted to be applied via upstream service config", config.EnvoyExtension.Name)
|
||||
}
|
||||
|
||||
// The extensions used by this extender only support terminating gateways and connect proxies.
|
||||
switch config.Kind {
|
||||
case api.ServiceKindTerminatingGateway, api.ServiceKindConnectProxy:
|
||||
default:
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
if !ext.Extension.CanApply(config) {
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
for _, indexType := range []string{
|
||||
xdscommon.ListenerType,
|
||||
xdscommon.RouteType,
|
||||
xdscommon.ClusterType,
|
||||
} {
|
||||
for nameOrSNI, msg := range resources.Index[indexType] {
|
||||
switch resource := msg.(type) {
|
||||
case *envoy_cluster_v3.Cluster:
|
||||
// If the Envoy extension configuration is for an upstream service, the Cluster's
|
||||
// name must match the upstream service's SNI.
|
||||
if !config.MatchesUpstreamServiceSNI(nameOrSNI) {
|
||||
continue
|
||||
}
|
||||
|
||||
newCluster, patched, err := ext.Extension.PatchCluster(config, resource)
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster: %w", err))
|
||||
continue
|
||||
}
|
||||
if patched {
|
||||
resources.Index[xdscommon.ClusterType][nameOrSNI] = newCluster
|
||||
}
|
||||
|
||||
case *envoy_listener_v3.Listener:
|
||||
newListener, patched, err := ext.patchListener(config, resource)
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener: %w", err))
|
||||
continue
|
||||
}
|
||||
if patched {
|
||||
resources.Index[xdscommon.ListenerType][nameOrSNI] = newListener
|
||||
}
|
||||
|
||||
case *envoy_route_v3.RouteConfiguration:
|
||||
// If the Envoy extension configuration is for an upstream service, the Route's
|
||||
// name must match the upstream service's Envoy ID.
|
||||
matchesEnvoyID := config.UpstreamEnvoyID() == nameOrSNI
|
||||
if !config.MatchesUpstreamServiceSNI(nameOrSNI) && !matchesEnvoyID {
|
||||
continue
|
||||
}
|
||||
|
||||
newRoute, patched, err := ext.Extension.PatchRoute(config, resource)
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching route: %w", err))
|
||||
continue
|
||||
}
|
||||
if patched {
|
||||
resources.Index[xdscommon.RouteType][nameOrSNI] = newRoute
|
||||
}
|
||||
default:
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("unsupported type was skipped: %T", resource))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resources, resultErr
|
||||
}
|
||||
|
||||
func (ext *UpstreamEnvoyExtender) patchListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
switch config.Kind {
|
||||
case api.ServiceKindTerminatingGateway:
|
||||
return ext.patchTerminatingGatewayListener(config, l)
|
||||
case api.ServiceKindConnectProxy:
|
||||
return ext.patchConnectProxyListener(config, l)
|
||||
}
|
||||
return l, false, nil
|
||||
}
|
||||
|
||||
func (ext *UpstreamEnvoyExtender) patchTerminatingGatewayListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
var resultErr error
|
||||
patched := false
|
||||
for _, filterChain := range l.FilterChains {
|
||||
sni := getSNI(filterChain)
|
||||
|
||||
if sni == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// The filter chain's SNI must match the upstream service's SNI.
|
||||
if !config.MatchesUpstreamServiceSNI(sni) {
|
||||
continue
|
||||
}
|
||||
|
||||
var filters []*envoy_listener_v3.Filter
|
||||
|
||||
for _, filter := range filterChain.Filters {
|
||||
newFilter, ok, err := ext.Extension.PatchFilter(config, filter, IsInboundPublicListener(l))
|
||||
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
|
||||
filters = append(filters, filter)
|
||||
continue
|
||||
}
|
||||
if ok {
|
||||
filters = append(filters, newFilter)
|
||||
patched = true
|
||||
} else {
|
||||
filters = append(filters, filter)
|
||||
}
|
||||
}
|
||||
filterChain.Filters = filters
|
||||
}
|
||||
|
||||
return l, patched, resultErr
|
||||
}
|
||||
|
||||
func (ext *UpstreamEnvoyExtender) patchConnectProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
var resultErr error
|
||||
envoyID := GetListenerEnvoyID(l)
|
||||
|
||||
// TProxy outbound listeners must be targeted _carefully_ by upstream extensions
|
||||
// because they will affect any downstream's local proxy (there's a single outbound
|
||||
// listener for all upstreams). Resources specific to that upstream such as the
|
||||
// individual filter that targets the upstream should be targeted.
|
||||
if IsOutboundTProxyListener(l) {
|
||||
return ext.patchTProxyListener(config, l)
|
||||
}
|
||||
|
||||
// If the Envoy extension configuration is for an upstream service, the listener's
|
||||
// name must match the upstream service's EnvoyID or be the outbound listener.
|
||||
if envoyID != config.UpstreamEnvoyID() {
|
||||
return l, false, nil
|
||||
}
|
||||
|
||||
// Below is where we handle upstream listeners when not in TProxy mode.
|
||||
var patched bool
|
||||
for _, filterChain := range l.FilterChains {
|
||||
var filters []*envoy_listener_v3.Filter
|
||||
|
||||
for _, filter := range filterChain.Filters {
|
||||
newFilter, ok, err := ext.Extension.PatchFilter(config, filter, IsInboundPublicListener(l))
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
|
||||
filters = append(filters, filter)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok {
|
||||
filters = append(filters, newFilter)
|
||||
patched = true
|
||||
} else {
|
||||
filters = append(filters, filter)
|
||||
}
|
||||
}
|
||||
filterChain.Filters = filters
|
||||
}
|
||||
|
||||
return l, patched, resultErr
|
||||
}
|
||||
|
||||
func (ext *UpstreamEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
||||
var resultErr error
|
||||
patched := false
|
||||
|
||||
vip := config.Upstreams[config.ServiceName].VIP
|
||||
|
||||
for _, filterChain := range l.FilterChains {
|
||||
var filters []*envoy_listener_v3.Filter
|
||||
|
||||
match := filterChainTProxyMatch(vip, filterChain)
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, filter := range filterChain.Filters {
|
||||
newFilter, ok, err := ext.Extension.PatchFilter(config, filter, IsInboundPublicListener(l))
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
|
||||
filters = append(filters, filter)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok {
|
||||
filters = append(filters, newFilter)
|
||||
patched = true
|
||||
} else {
|
||||
filters = append(filters, filter)
|
||||
}
|
||||
}
|
||||
filterChain.Filters = filters
|
||||
}
|
||||
|
||||
return l, patched, resultErr
|
||||
}
|
|
@ -79,6 +79,7 @@ func Validate(indexedResources *xdscommon.IndexedResources, envoyID string, vip
|
|||
},
|
||||
},
|
||||
ServiceName: emptyServiceKey,
|
||||
IsSourcedFromUpstream: true,
|
||||
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
emptyServiceKey: {
|
||||
VIP: vip,
|
||||
|
@ -92,12 +93,12 @@ func Validate(indexedResources *xdscommon.IndexedResources, envoyID string, vip
|
|||
},
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
}
|
||||
basicExtension, err := validate.MakeValidate(extConfig)
|
||||
ext, err := validate.MakeValidate(extConfig)
|
||||
if err != nil {
|
||||
return []validate.Message{{Message: err.Error()}}
|
||||
}
|
||||
extender := extensioncommon.BasicEnvoyExtender{
|
||||
Extension: basicExtension,
|
||||
extender := extensioncommon.UpstreamEnvoyExtender{
|
||||
Extension: ext,
|
||||
}
|
||||
err = extender.Validate(&extConfig)
|
||||
if err != nil {
|
||||
|
|
|
@ -21,8 +21,6 @@ import (
|
|||
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
||||
)
|
||||
|
||||
const builtinValidateExtension = "builtin/proxy/validate"
|
||||
|
||||
// Validate contains input information about which proxy resources to validate and output information about resources it
|
||||
// has validated.
|
||||
type Validate struct {
|
||||
|
@ -81,8 +79,8 @@ func MakeValidate(ext extensioncommon.RuntimeConfig) (extensioncommon.BasicExten
|
|||
var resultErr error
|
||||
var plugin Validate
|
||||
|
||||
if name := ext.EnvoyExtension.Name; name != builtinValidateExtension {
|
||||
return nil, fmt.Errorf("expected extension name 'builtin/proxy/validate' but got %q", name)
|
||||
if name := ext.EnvoyExtension.Name; name != api.BuiltinValidateExtension {
|
||||
return nil, fmt.Errorf("expected extension name '%s' but got %q", api.BuiltinValidateExtension, name)
|
||||
}
|
||||
|
||||
envoyID, _ := ext.EnvoyExtension.Arguments["envoyID"]
|
||||
|
@ -366,7 +364,7 @@ func (p *Validate) PatchCluster(config *extensioncommon.RuntimeConfig, c *envoy_
|
|||
return c, false, nil
|
||||
}
|
||||
|
||||
func (p *Validate) PatchFilter(config *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error) {
|
||||
func (p *Validate) PatchFilter(config *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, _ bool) (*envoy_listener_v3.Filter, bool, error) {
|
||||
// If a single filter exists for a listener we say it exists.
|
||||
p.listener = true
|
||||
|
||||
|
|
|
@ -335,7 +335,7 @@ func TestMakeValidate(t *testing.T) {
|
|||
for n, tc := range cases {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
|
||||
extensionName := builtinValidateExtension
|
||||
extensionName := api.BuiltinValidateExtension
|
||||
if tc.extensionName != "" {
|
||||
extensionName = tc.extensionName
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue