Add Upstream Service Targeting to Property Override Extension (#17517)

* add upstream service targeting to property override extension

* Also add baseline goldens for service specific property override extension.
* Refactor the extension framework to put more logic into the templates.

* fix up the golden tests
This commit is contained in:
Eric Haberkorn 2023-05-30 14:53:42 -04:00 committed by GitHub
parent e5cdb702e5
commit bbf0b70b52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2634 additions and 247 deletions

View File

@ -72,16 +72,19 @@ func (a *awsLambda) CanApply(config *extensioncommon.RuntimeConfig) bool {
// PatchRoute modifies the routing configuration for a service of kind TerminatingGateway. If the kind is // PatchRoute modifies the routing configuration for a service of kind TerminatingGateway. If the kind is
// not TerminatingGateway, then it can not be modified. // not TerminatingGateway, then it can not be modified.
func (a *awsLambda) PatchRoute(r *extensioncommon.RuntimeConfig, route *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) { func (a *awsLambda) PatchRoute(p extensioncommon.RoutePayload) (*envoy_route_v3.RouteConfiguration, bool, error) {
if r.Kind != api.ServiceKindTerminatingGateway { cfg := p.RuntimeConfig
return route, false, nil if cfg.Kind != api.ServiceKindTerminatingGateway {
return p.Message, false, nil
} }
// Only patch outbound routes. // Only patch outbound routes.
if extensioncommon.IsRouteToLocalAppCluster(route) { if p.IsInbound() {
return route, false, nil return p.Message, false, nil
} }
route := p.Message
for _, virtualHost := range route.VirtualHosts { for _, virtualHost := range route.VirtualHosts {
for _, route := range virtualHost.Routes { for _, route := range virtualHost.Routes {
action, ok := route.Action.(*envoy_route_v3.Route_Route) action, ok := route.Action.(*envoy_route_v3.Route_Route)
@ -101,16 +104,18 @@ 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 // 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) { func (a *awsLambda) PatchCluster(p extensioncommon.ClusterPayload) (*envoy_cluster_v3.Cluster, bool, error) {
// Only patch outbound clusters. // Only patch outbound clusters.
if extensioncommon.IsLocalAppCluster(c) { if p.IsInbound() {
return c, false, nil return p.Message, false, nil
} }
transportSocket, err := extensioncommon.MakeUpstreamTLSTransportSocket(&envoy_tls_v3.UpstreamTlsContext{ transportSocket, err := extensioncommon.MakeUpstreamTLSTransportSocket(&envoy_tls_v3.UpstreamTlsContext{
Sni: "*.amazonaws.com", Sni: "*.amazonaws.com",
}) })
c := p.Message
if err != nil { if err != nil {
return c, false, fmt.Errorf("failed to make transport socket: %w", err) return c, false, fmt.Errorf("failed to make transport socket: %w", err)
} }
@ -168,9 +173,11 @@ func (a *awsLambda) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_clus
// PatchFilter patches the provided envoy filter with an inserted lambda filter being careful not to // PatchFilter patches the provided envoy filter with an inserted lambda filter being careful not to
// overwrite the http filters. // overwrite the http filters.
func (a *awsLambda) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) { func (a *awsLambda) PatchFilter(p extensioncommon.FilterPayload) (*envoy_listener_v3.Filter, bool, error) {
filter := p.Message
// Only patch outbound filters. // Only patch outbound filters.
if isInboundListener { if p.IsInbound() {
return filter, false, nil return filter, false, nil
} }

View File

@ -202,7 +202,10 @@ func TestPatchCluster(t *testing.T) {
// Test patching the cluster // Test patching the cluster
rc := extensioncommon.RuntimeConfig{} rc := extensioncommon.RuntimeConfig{}
patchedCluster, patchSuccess, err := tc.lambda.PatchCluster(&rc, tc.input) patchedCluster, patchSuccess, err := tc.lambda.PatchCluster(extensioncommon.ClusterPayload{
RuntimeConfig: &rc,
Message: tc.input,
})
if tc.isErrExpected { if tc.isErrExpected {
assert.Error(t, err) assert.Error(t, err)
assert.False(t, patchSuccess) assert.False(t, patchSuccess)
@ -307,7 +310,10 @@ func TestPatchRoute(t *testing.T) {
for name, tc := range tests { for name, tc := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
l := awsLambda{} l := awsLambda{}
r, ok, err := l.PatchRoute(tc.conf, tc.route) r, ok, err := l.PatchRoute(extensioncommon.RoutePayload{
RuntimeConfig: tc.conf,
Message: tc.route,
})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, tc.expectRoute, r) require.Equal(t, tc.expectRoute, r)
require.Equal(t, tc.expectBool, ok) require.Equal(t, tc.expectBool, ok)
@ -456,7 +462,14 @@ func TestPatchFilter(t *testing.T) {
PayloadPassthrough: true, PayloadPassthrough: true,
InvocationMode: "asynchronous", InvocationMode: "asynchronous",
} }
f, ok, err := l.PatchFilter(nil, tc.filter, tc.isInboundFilter) d := extensioncommon.TrafficDirectionOutbound
if tc.isInboundFilter {
d = extensioncommon.TrafficDirectionInbound
}
f, ok, err := l.PatchFilter(extensioncommon.FilterPayload{
Message: tc.filter,
TrafficDirection: d,
})
require.Equal(t, tc.expectBool, ok) require.Equal(t, tc.expectBool, ok)
if tc.expectErr == "" { if tc.expectErr == "" {
require.NoError(t, err) require.NoError(t, err)

View File

@ -601,7 +601,7 @@ func (t Target) clusterName(cfg *cmn.RuntimeConfig) (string, error) {
for service, upstream := range cfg.Upstreams { for service, upstream := range cfg.Upstreams {
if service == t.Service { if service == t.Service {
for sni := range upstream.SNI { for sni := range upstream.SNIs {
return sni, nil return sni, nil
} }
} }

View File

@ -102,10 +102,11 @@ func (p *ratelimit) CanApply(config *extensioncommon.RuntimeConfig) bool {
// PatchFilter inserts a http local rate_limit filter at the head of // PatchFilter inserts a http local rate_limit filter at the head of
// envoy.filters.network.http_connection_manager filters // envoy.filters.network.http_connection_manager filters
func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) { func (r ratelimit) PatchFilter(p extensioncommon.FilterPayload) (*envoy_listener_v3.Filter, bool, error) {
filter := p.Message
// rate limit is only applied to the inbound listener of the service itself // rate limit is only applied to the inbound listener of the service itself
// since the limit is aggregated from all downstream connections. // since the limit is aggregated from all downstream connections.
if !isInboundListener { if !p.IsInbound() {
return filter, false, nil return filter, false, nil
} }
@ -123,34 +124,34 @@ func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_l
tokenBucket := envoy_type_v3.TokenBucket{} tokenBucket := envoy_type_v3.TokenBucket{}
if p.TokensPerFill != nil { if r.TokensPerFill != nil {
tokenBucket.TokensPerFill = &wrappers.UInt32Value{ tokenBucket.TokensPerFill = &wrappers.UInt32Value{
Value: uint32(*p.TokensPerFill), Value: uint32(*r.TokensPerFill),
} }
} }
if p.MaxTokens != nil { if r.MaxTokens != nil {
tokenBucket.MaxTokens = uint32(*p.MaxTokens) tokenBucket.MaxTokens = uint32(*r.MaxTokens)
} }
if p.FillInterval != nil { if r.FillInterval != nil {
tokenBucket.FillInterval = durationpb.New(time.Duration(*p.FillInterval) * time.Second) tokenBucket.FillInterval = durationpb.New(time.Duration(*r.FillInterval) * time.Second)
} }
var FilterEnabledDefault *envoy_core_v3.RuntimeFractionalPercent var FilterEnabledDefault *envoy_core_v3.RuntimeFractionalPercent
if p.FilterEnabled != nil { if r.FilterEnabled != nil {
FilterEnabledDefault = &envoy_core_v3.RuntimeFractionalPercent{ FilterEnabledDefault = &envoy_core_v3.RuntimeFractionalPercent{
DefaultValue: &envoy_type_v3.FractionalPercent{ DefaultValue: &envoy_type_v3.FractionalPercent{
Numerator: *p.FilterEnabled, Numerator: *r.FilterEnabled,
Denominator: envoy_type_v3.FractionalPercent_HUNDRED, Denominator: envoy_type_v3.FractionalPercent_HUNDRED,
}, },
} }
} }
var FilterEnforcedDefault *envoy_core_v3.RuntimeFractionalPercent var FilterEnforcedDefault *envoy_core_v3.RuntimeFractionalPercent
if p.FilterEnforced != nil { if r.FilterEnforced != nil {
FilterEnforcedDefault = &envoy_core_v3.RuntimeFractionalPercent{ FilterEnforcedDefault = &envoy_core_v3.RuntimeFractionalPercent{
DefaultValue: &envoy_type_v3.FractionalPercent{ DefaultValue: &envoy_type_v3.FractionalPercent{
Numerator: *p.FilterEnforced, Numerator: *r.FilterEnforced,
Denominator: envoy_type_v3.FractionalPercent_HUNDRED, Denominator: envoy_type_v3.FractionalPercent_HUNDRED,
}, },
} }

View File

@ -67,14 +67,16 @@ func (l *lua) CanApply(config *extensioncommon.RuntimeConfig) bool {
return string(config.Kind) == l.ProxyType return string(config.Kind) == l.ProxyType
} }
func (l *lua) matchesListenerDirection(isInboundListener bool) bool { func (l *lua) matchesListenerDirection(p extensioncommon.FilterPayload) bool {
isInboundListener := p.IsInbound()
return (!isInboundListener && l.Listener == "outbound") || (isInboundListener && l.Listener == "inbound") return (!isInboundListener && l.Listener == "outbound") || (isInboundListener && l.Listener == "inbound")
} }
// PatchFilter inserts a lua filter directly prior to envoy.filters.http.router. // PatchFilter inserts a lua filter directly prior to envoy.filters.http.router.
func (l *lua) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) { func (l *lua) PatchFilter(p extensioncommon.FilterPayload) (*envoy_listener_v3.Filter, bool, error) {
filter := p.Message
// Make sure filter matches extension config. // Make sure filter matches extension config.
if !l.matchesListenerDirection(isInboundListener) { if !l.matchesListenerDirection(p) {
return filter, false, nil return filter, false, nil
} }

View File

@ -2,7 +2,6 @@ package propertyoverride
import ( import (
"fmt" "fmt"
"strings"
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
@ -14,14 +13,10 @@ import (
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/envoyextensions/extensioncommon" "github.com/hashicorp/consul/envoyextensions/extensioncommon"
"github.com/hashicorp/consul/lib/maps"
) )
type stringSet map[string]struct{}
type propertyOverride struct { type propertyOverride struct {
extensioncommon.BasicExtensionAdapter extensioncommon.BasicExtensionAdapter
// Patches are an array of Patch operations to be applied to the target resource(s). // Patches are an array of Patch operations to be applied to the target resource(s).
Patches []Patch Patches []Patch
// Debug controls error messages when Path matching fails. // Debug controls error messages when Path matching fails.
@ -43,7 +38,42 @@ type ResourceFilter struct {
// TrafficDirection determines whether the patch will be applied to a service's inbound // TrafficDirection determines whether the patch will be applied to a service's inbound
// or outbound resources. // or outbound resources.
// This field is required. // This field is required.
TrafficDirection TrafficDirection TrafficDirection extensioncommon.TrafficDirection
// Services indicates which upstream services will have corresponding Envoy resources patched.
// This includes directly targeted and discovery chain services. If Services is omitted or
// empty, all resources matching the filter will be targeted (including TProxy, which
// implicitly corresponds to any number of upstreams). Services must be omitted unless
// TrafficDirection is set to outbound.
Services []*ServiceName
}
func matchesResourceFilter[K proto.Message](rf ResourceFilter, resourceType ResourceType, payload extensioncommon.Payload[K]) bool {
if resourceType != rf.ResourceType {
return false
}
if payload.TrafficDirection != rf.TrafficDirection {
return false
}
if len(rf.Services) == 0 {
return true
}
for _, s := range rf.Services {
if payload.ServiceName == nil || s.CompoundServiceName != *payload.ServiceName {
continue
}
return true
}
return false
}
type ServiceName struct {
api.CompoundServiceName
} }
// ResourceType is the type of Envoy resource being patched. // ResourceType is the type of Envoy resource being patched.
@ -56,23 +86,13 @@ const (
ResourceTypeRoute ResourceType = "route" ResourceTypeRoute ResourceType = "route"
) )
var ResourceTypes = stringSet{ var ResourceTypes = extensioncommon.StringSet{
string(ResourceTypeCluster): {}, string(ResourceTypeCluster): {},
string(ResourceTypeClusterLoadAssignment): {}, string(ResourceTypeClusterLoadAssignment): {},
string(ResourceTypeListener): {},
string(ResourceTypeRoute): {}, string(ResourceTypeRoute): {},
string(ResourceTypeListener): {},
} }
// TrafficDirection determines whether inbound or outbound Envoy resources will be patched.
type TrafficDirection string
const (
TrafficDirectionInbound TrafficDirection = "inbound"
TrafficDirectionOutbound TrafficDirection = "outbound"
)
var TrafficDirections = stringSet{string(TrafficDirectionInbound): {}, string(TrafficDirectionOutbound): {}}
// Op is the type of JSON Patch operation being applied. // Op is the type of JSON Patch operation being applied.
type Op string type Op string
@ -81,10 +101,10 @@ const (
OpRemove Op = "remove" OpRemove Op = "remove"
) )
var Ops = stringSet{string(OpAdd): {}, string(OpRemove): {}} var Ops = extensioncommon.StringSet{string(OpAdd): {}, string(OpRemove): {}}
// validProxyTypes is the set of supported proxy types for this extension. // validProxyTypes is the set of supported proxy types for this extension.
var validProxyTypes = stringSet{ var validProxyTypes = extensioncommon.StringSet{
string(api.ServiceKindConnectProxy): struct{}{}, string(api.ServiceKindConnectProxy): struct{}{},
string(api.ServiceKindTerminatingGateway): struct{}{}, string(api.ServiceKindTerminatingGateway): struct{}{},
} }
@ -139,27 +159,58 @@ type Patch struct {
var _ extensioncommon.BasicExtension = (*propertyOverride)(nil) var _ extensioncommon.BasicExtension = (*propertyOverride)(nil)
func (c *stringSet) checkRequired(v, fieldName string) error { func (f *ResourceFilter) isEmpty() bool {
if _, ok := (*c)[v]; !ok { if f == nil {
if v == "" { return true
return fmt.Errorf("field %s is required", fieldName)
} }
return fmt.Errorf("invalid %s '%q'; supported values: %s",
fieldName, v, strings.Join(maps.SliceOfKeys(*c), ", ")) if len(f.Services) > 0 {
return false
} }
return nil
if string(f.TrafficDirection) != "" {
return false
}
if string(f.ResourceType) != "" {
return false
}
return true
} }
func (f *ResourceFilter) validate() error { func (f *ResourceFilter) validate() error {
if f == nil || *f == (ResourceFilter{}) { if f == nil || f.isEmpty() {
return fmt.Errorf("field ResourceFilter is required") return fmt.Errorf("field ResourceFilter is required")
} }
if err := ResourceTypes.checkRequired(string(f.ResourceType), "ResourceType"); err != nil { if err := ResourceTypes.CheckRequired(string(f.ResourceType), "ResourceType"); err != nil {
return err return err
} }
if err := TrafficDirections.checkRequired(string(f.TrafficDirection), "TrafficDirection"); err != nil { if err := extensioncommon.TrafficDirections.CheckRequired(string(f.TrafficDirection), "TrafficDirection"); err != nil {
return err return err
} }
for i := range f.Services {
sn := f.Services[i]
sn.normalize()
if err := sn.validate(); err != nil {
return err
}
}
return nil
}
func (sn *ServiceName) normalize() {
extensioncommon.NormalizeServiceName(&sn.CompoundServiceName)
}
func (sn *ServiceName) validate() error {
if sn.Name == "" {
return fmt.Errorf("service name is required")
}
return nil return nil
} }
@ -169,7 +220,7 @@ func (p *Patch) validate(debug bool) error {
return err return err
} }
if err := Ops.checkRequired(string(p.Op), "Op"); err != nil { if err := Ops.CheckRequired(string(p.Op), "Op"); err != nil {
return err return err
} }
@ -209,7 +260,7 @@ func (p *propertyOverride) validate() error {
} }
} }
if err := validProxyTypes.checkRequired(string(p.ProxyType), "ProxyType"); err != nil { if err := validProxyTypes.CheckRequired(string(p.ProxyType), "ProxyType"); err != nil {
resultErr = multierror.Append(resultErr, err) resultErr = multierror.Append(resultErr, err)
} }
@ -243,57 +294,35 @@ func (p *propertyOverride) CanApply(config *extensioncommon.RuntimeConfig) bool
} }
// PatchRoute patches the provided Envoy Route with any applicable `route` ResourceType patches. // PatchRoute patches the provided Envoy Route with any applicable `route` ResourceType patches.
func (p *propertyOverride) PatchRoute(_ *extensioncommon.RuntimeConfig, r *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) { func (p *propertyOverride) PatchRoute(payload extensioncommon.RoutePayload) (*envoy_route_v3.RouteConfiguration, bool, error) {
d := TrafficDirectionOutbound return patchResourceType[*envoy_route_v3.RouteConfiguration](p, ResourceTypeRoute, payload, &defaultStructPatcher[*envoy_route_v3.RouteConfiguration]{})
if extensioncommon.IsRouteToLocalAppCluster(r) {
d = TrafficDirectionInbound
}
return patchResourceType[*envoy_route_v3.RouteConfiguration](r, p, ResourceTypeRoute, d, &defaultStructPatcher[*envoy_route_v3.RouteConfiguration]{})
} }
// PatchCluster patches the provided Envoy Cluster with any applicable `cluster` ResourceType patches. // PatchCluster patches the provided Envoy Cluster with any applicable `cluster` ResourceType patches.
func (p *propertyOverride) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) { func (p *propertyOverride) PatchCluster(payload extensioncommon.ClusterPayload) (*envoy_cluster_v3.Cluster, bool, error) {
d := TrafficDirectionOutbound return patchResourceType[*envoy_cluster_v3.Cluster](p, ResourceTypeCluster, payload, &defaultStructPatcher[*envoy_cluster_v3.Cluster]{})
if extensioncommon.IsLocalAppCluster(c) {
d = TrafficDirectionInbound
}
return patchResourceType[*envoy_cluster_v3.Cluster](c, p, ResourceTypeCluster, d, &defaultStructPatcher[*envoy_cluster_v3.Cluster]{})
} }
// PatchClusterLoadAssignment patches the provided Envoy ClusterLoadAssignment with any applicable `cluster-load-assignment` ResourceType patches. // PatchClusterLoadAssignment patches the provided Envoy ClusterLoadAssignment with any applicable `cluster-load-assignment` ResourceType patches.
func (p *propertyOverride) PatchClusterLoadAssignment(_ *extensioncommon.RuntimeConfig, c *envoy_endpoint_v3.ClusterLoadAssignment) (*envoy_endpoint_v3.ClusterLoadAssignment, bool, error) { func (p *propertyOverride) PatchClusterLoadAssignment(payload extensioncommon.ClusterLoadAssignmentPayload) (*envoy_endpoint_v3.ClusterLoadAssignment, bool, error) {
d := TrafficDirectionOutbound return patchResourceType[*envoy_endpoint_v3.ClusterLoadAssignment](p, ResourceTypeClusterLoadAssignment, payload, &defaultStructPatcher[*envoy_endpoint_v3.ClusterLoadAssignment]{})
if extensioncommon.IsLocalAppClusterLoadAssignment(c) {
d = TrafficDirectionInbound
}
return patchResourceType[*envoy_endpoint_v3.ClusterLoadAssignment](c, p, ResourceTypeClusterLoadAssignment, d, &defaultStructPatcher[*envoy_endpoint_v3.ClusterLoadAssignment]{})
} }
// PatchListener patches the provided Envoy Listener with any applicable `listener` ResourceType patches. // PatchListener patches the provided Envoy Listener with any applicable `listener` ResourceType patches.
func (p *propertyOverride) PatchListener(_ *extensioncommon.RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, bool, error) { func (p *propertyOverride) PatchListener(payload extensioncommon.ListenerPayload) (*envoy_listener_v3.Listener, bool, error) {
d := TrafficDirectionOutbound return patchResourceType[*envoy_listener_v3.Listener](p, ResourceTypeListener, payload, &defaultStructPatcher[*envoy_listener_v3.Listener]{})
if extensioncommon.IsInboundPublicListener(l) {
d = TrafficDirectionInbound
}
return patchResourceType[*envoy_listener_v3.Listener](l, p, ResourceTypeListener, d, &defaultStructPatcher[*envoy_listener_v3.Listener]{})
}
// PatchFilter does nothing as this extension does not target Filters directly.
func (p *propertyOverride) PatchFilter(_ *extensioncommon.RuntimeConfig, f *envoy_listener_v3.Filter, _ bool) (*envoy_listener_v3.Filter, bool, error) {
return f, false, nil
} }
// patchResourceType applies Patches matching the given ResourceType to the target K. // patchResourceType applies Patches matching the given ResourceType to the target K.
// This helper simplifies implementation of the above per-type patch methods defined by BasicExtension. // This helper simplifies implementation of the above per-type patch methods defined by BasicExtension.
func patchResourceType[K proto.Message](k K, p *propertyOverride, t ResourceType, d TrafficDirection, patcher structPatcher[K]) (K, bool, error) { func patchResourceType[K proto.Message](p *propertyOverride, resourceType ResourceType, payload extensioncommon.Payload[K], patcher structPatcher[K]) (K, bool, error) {
resultPatched := false resultPatched := false
var resultErr error var resultErr error
k := payload.Message
for _, patch := range p.Patches { for _, patch := range p.Patches {
if patch.ResourceFilter.ResourceType != t { if !matchesResourceFilter(patch.ResourceFilter, resourceType, payload) {
continue
}
if patch.ResourceFilter.TrafficDirection != d {
continue continue
} }
newK, err := patcher.applyPatch(k, patch, p.Debug) newK, err := patcher.applyPatch(k, patch, p.Debug)

View File

@ -2,11 +2,13 @@ package propertyoverride
import ( import (
"fmt" "fmt"
routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
"strings" "strings"
"testing" "testing"
clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/envoyextensions/extensioncommon" "github.com/hashicorp/consul/envoyextensions/extensioncommon"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -32,7 +34,7 @@ func TestConstructor(t *testing.T) {
makeResourceFilter := func(overrides map[string]any) map[string]any { makeResourceFilter := func(overrides map[string]any) map[string]any {
f := map[string]any{ f := map[string]any{
"ResourceType": ResourceTypeRoute, "ResourceType": ResourceTypeRoute,
"TrafficDirection": TrafficDirectionOutbound, "TrafficDirection": extensioncommon.TrafficDirectionOutbound,
} }
return applyOverrides(f, overrides) return applyOverrides(f, overrides)
} }
@ -63,7 +65,7 @@ func TestConstructor(t *testing.T) {
errMsg string errMsg string
} }
validTestCase := func(o Op, d TrafficDirection, t ResourceType) testCase { validTestCase := func(o Op, d extensioncommon.TrafficDirection, t ResourceType) testCase {
var v any = "foo" var v any = "foo"
if o != OpAdd { if o != OpAdd {
v = nil v = nil
@ -214,6 +216,19 @@ func TestConstructor(t *testing.T) {
ok: false, ok: false,
errMsg: fmt.Sprintf("field Value is not supported for %s operation", OpRemove), errMsg: fmt.Sprintf("field Value is not supported for %s operation", OpRemove),
}, },
"empty service name": {
arguments: makeArguments(map[string]any{"Patches": []map[string]any{
makePatch(map[string]any{
"ResourceFilter": makeResourceFilter(map[string]any{
"Services": []ServiceName{
{CompoundServiceName: api.CompoundServiceName{}},
},
}),
}),
}}),
ok: false,
errMsg: "service name is required",
},
// See decode.HookWeakDecodeFromSlice for more details. In practice, we can end up // See decode.HookWeakDecodeFromSlice for more details. In practice, we can end up
// with a "Patches" field decoded to the single "Patch" value contained in the // with a "Patches" field decoded to the single "Patch" value contained in the
// serialized slice (raised from the containing slice). Using WeakDecode solves // serialized slice (raised from the containing slice). Using WeakDecode solves
@ -222,7 +237,7 @@ func TestConstructor(t *testing.T) {
// by WeakDecode as it is a more-permissive version of the default behavior. // by WeakDecode as it is a more-permissive version of the default behavior.
"single value Patches decoded as map construction succeeds": { "single value Patches decoded as map construction succeeds": {
arguments: makeArguments(map[string]any{"Patches": makePatch(map[string]any{})}), arguments: makeArguments(map[string]any{"Patches": makePatch(map[string]any{})}),
expected: validTestCase(OpAdd, TrafficDirectionOutbound, ResourceTypeRoute).expected, expected: validTestCase(OpAdd, extensioncommon.TrafficDirectionOutbound, ResourceTypeRoute).expected,
ok: true, ok: true,
}, },
"invalid ProxyType": { "invalid ProxyType": {
@ -248,10 +263,10 @@ func TestConstructor(t *testing.T) {
} }
for o := range Ops { for o := range Ops {
for d := range TrafficDirections { for d := range extensioncommon.TrafficDirections {
for t := range ResourceTypes { for t := range ResourceTypes {
cases["valid everything: "+strings.Join([]string{o, d, t}, ",")] = cases["valid everything: "+strings.Join([]string{o, d, t}, ",")] =
validTestCase(Op(o), TrafficDirection(d), ResourceType(t)) validTestCase(Op(o), extensioncommon.TrafficDirection(d), ResourceType(t))
} }
} }
} }
@ -294,32 +309,62 @@ func Test_patchResourceType(t *testing.T) {
Patches: patches, Patches: patches,
} }
} }
makePatchWithPath := func(t ResourceType, d TrafficDirection, p string) Patch { makePatchWithPath := func(filter ResourceFilter, p string) Patch {
return Patch{ return Patch{
ResourceFilter: ResourceFilter{ ResourceFilter: filter,
ResourceType: t,
TrafficDirection: d,
},
Op: OpAdd, Op: OpAdd,
Path: p, Path: p,
Value: 1, Value: 1,
} }
} }
makePatch := func(t ResourceType, d TrafficDirection) Patch { makePatch := func(filter ResourceFilter) Patch {
return makePatchWithPath(t, d, "/foo") return makePatchWithPath(filter, "/foo")
} }
clusterOutbound := makePatch(ResourceTypeCluster, TrafficDirectionOutbound) svc1 := ServiceName{
clusterInbound := makePatch(ResourceTypeCluster, TrafficDirectionInbound) CompoundServiceName: api.CompoundServiceName{Name: "svc1"},
routeOutbound := makePatch(ResourceTypeRoute, TrafficDirectionOutbound) }
routeOutbound2 := makePatchWithPath(ResourceTypeRoute, TrafficDirectionOutbound, "/bar") svc2 := ServiceName{
routeInbound := makePatch(ResourceTypeRoute, TrafficDirectionInbound) CompoundServiceName: api.CompoundServiceName{Name: "svc2"},
}
clusterOutbound := makePatch(ResourceFilter{
ResourceType: ResourceTypeCluster,
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
})
clusterInbound := makePatch(ResourceFilter{
ResourceType: ResourceTypeCluster,
TrafficDirection: extensioncommon.TrafficDirectionInbound,
})
listenerOutbound := makePatch(ResourceFilter{
ResourceType: ResourceTypeListener,
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
})
listenerOutbound2 := makePatchWithPath(ResourceFilter{
ResourceType: ResourceTypeListener,
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
}, "/bar")
listenerInbound := makePatch(ResourceFilter{
ResourceType: ResourceTypeListener,
TrafficDirection: extensioncommon.TrafficDirectionInbound,
})
routeOutbound := makePatch(ResourceFilter{
ResourceType: ResourceTypeRoute,
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
})
routeOutbound2 := makePatchWithPath(ResourceFilter{
ResourceType: ResourceTypeRoute,
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
}, "/bar")
routeInbound := makePatch(ResourceFilter{
ResourceType: ResourceTypeRoute,
TrafficDirection: extensioncommon.TrafficDirectionInbound,
})
type args struct { type args struct {
d TrafficDirection resourceType ResourceType
k proto.Message payload extensioncommon.Payload[proto.Message]
p *propertyOverride p *propertyOverride
t ResourceType
} }
type testCase struct { type testCase struct {
args args args args
@ -329,79 +374,179 @@ func Test_patchResourceType(t *testing.T) {
cases := map[string]testCase{ cases := map[string]testCase{
"outbound gets matching patch": { "outbound gets matching patch": {
args: args{ args: args{
d: TrafficDirectionOutbound, resourceType: ResourceTypeCluster,
k: &clusterv3.Cluster{}, payload: extensioncommon.Payload[proto.Message]{
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
Message: &clusterv3.Cluster{},
},
p: makeExtension(clusterOutbound), p: makeExtension(clusterOutbound),
t: ResourceTypeCluster,
}, },
expectPatched: true, expectPatched: true,
wantApplied: []Patch{clusterOutbound}, wantApplied: []Patch{clusterOutbound},
}, },
"inbound gets matching patch": { "inbound gets matching patch": {
args: args{ args: args{
d: TrafficDirectionInbound, resourceType: ResourceTypeCluster,
k: &clusterv3.Cluster{}, payload: extensioncommon.Payload[proto.Message]{
TrafficDirection: extensioncommon.TrafficDirectionInbound,
Message: &clusterv3.Cluster{},
},
p: makeExtension(clusterInbound), p: makeExtension(clusterInbound),
t: ResourceTypeCluster,
}, },
expectPatched: true, expectPatched: true,
wantApplied: []Patch{clusterInbound}, wantApplied: []Patch{clusterInbound},
}, },
"multiple resources same direction only gets matching resource": { "multiple resources same direction only gets matching resource": {
args: args{ args: args{
d: TrafficDirectionOutbound, resourceType: ResourceTypeCluster,
k: &clusterv3.Cluster{}, payload: extensioncommon.Payload[proto.Message]{
p: makeExtension(clusterOutbound, routeOutbound), TrafficDirection: extensioncommon.TrafficDirectionOutbound,
t: ResourceTypeCluster, Message: &clusterv3.Cluster{},
},
p: makeExtension(clusterOutbound, listenerOutbound),
}, },
expectPatched: true, expectPatched: true,
wantApplied: []Patch{clusterOutbound}, wantApplied: []Patch{clusterOutbound},
}, },
"multiple directions same resource only gets matching direction": { "multiple directions same resource only gets matching direction": {
args: args{ args: args{
d: TrafficDirectionOutbound, resourceType: ResourceTypeCluster,
k: &clusterv3.Cluster{}, payload: extensioncommon.Payload[proto.Message]{
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
Message: &clusterv3.Cluster{},
},
p: makeExtension(clusterOutbound, clusterInbound), p: makeExtension(clusterOutbound, clusterInbound),
t: ResourceTypeCluster,
}, },
expectPatched: true, expectPatched: true,
wantApplied: []Patch{clusterOutbound}, wantApplied: []Patch{clusterOutbound},
}, },
"multiple directions and resources only gets matching patch": { "multiple directions and resources only gets matching patch": {
args: args{ args: args{
d: TrafficDirectionInbound, resourceType: ResourceTypeRoute,
k: &routev3.RouteConfiguration{}, payload: extensioncommon.Payload[proto.Message]{
p: makeExtension(clusterOutbound, clusterInbound, routeOutbound, routeInbound), TrafficDirection: extensioncommon.TrafficDirectionInbound,
t: ResourceTypeRoute, Message: &routev3.RouteConfiguration{},
},
p: makeExtension(clusterOutbound, clusterInbound, listenerOutbound, listenerInbound, routeOutbound, routeOutbound2, routeInbound),
}, },
expectPatched: true, expectPatched: true,
wantApplied: []Patch{routeInbound}, wantApplied: []Patch{routeInbound},
}, },
"multiple directions and resources multiple matches gets all matching patches": { "multiple directions and resources multiple matches gets all matching patches": {
args: args{ args: args{
d: TrafficDirectionOutbound, resourceType: ResourceTypeRoute,
k: &routev3.RouteConfiguration{}, payload: extensioncommon.Payload[proto.Message]{
p: makeExtension(clusterOutbound, clusterInbound, routeOutbound, routeInbound, routeOutbound2), TrafficDirection: extensioncommon.TrafficDirectionOutbound,
t: ResourceTypeRoute, Message: &routev3.RouteConfiguration{},
},
p: makeExtension(clusterOutbound, clusterInbound, listenerOutbound, listenerInbound, listenerOutbound2, routeOutbound, routeOutbound2, routeInbound),
}, },
expectPatched: true, expectPatched: true,
wantApplied: []Patch{routeOutbound, routeOutbound2}, wantApplied: []Patch{routeOutbound, routeOutbound2},
}, },
"multiple directions and resources no matches gets no patches": { "multiple directions and resources no matches gets no patches": {
args: args{ args: args{
d: TrafficDirectionOutbound, resourceType: ResourceTypeCluster,
k: &routev3.RouteConfiguration{}, payload: extensioncommon.Payload[proto.Message]{
p: makeExtension(clusterInbound, routeOutbound, routeInbound, routeOutbound2), TrafficDirection: extensioncommon.TrafficDirectionOutbound,
t: ResourceTypeCluster, Message: &clusterv3.Cluster{},
},
p: makeExtension(clusterInbound, listenerOutbound, listenerInbound, listenerOutbound2, routeInbound, routeOutbound),
}, },
expectPatched: false, expectPatched: false,
wantApplied: nil, wantApplied: nil,
}, },
} }
type resourceTypeServiceMatch struct {
resourceType ResourceType
message proto.Message
}
resourceTypeCases := []resourceTypeServiceMatch{
{
resourceType: ResourceTypeCluster,
message: &clusterv3.Cluster{},
},
{
resourceType: ResourceTypeListener,
message: &listenerv3.Listener{},
},
{
resourceType: ResourceTypeRoute,
message: &routev3.RouteConfiguration{},
},
{
resourceType: ResourceTypeClusterLoadAssignment,
message: &endpointv3.ClusterLoadAssignment{},
},
}
for _, tc := range resourceTypeCases {
{
patch := makePatch(ResourceFilter{
ResourceType: tc.resourceType,
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
Services: []*ServiceName{
{CompoundServiceName: svc2.CompoundServiceName},
},
})
cases[fmt.Sprintf("%s - no match", tc.resourceType)] = testCase{
args: args{
resourceType: tc.resourceType,
payload: extensioncommon.Payload[proto.Message]{
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
ServiceName: &svc1.CompoundServiceName,
Message: tc.message,
RuntimeConfig: &extensioncommon.RuntimeConfig{
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
svc1.CompoundServiceName: {},
},
},
},
p: makeExtension(patch),
},
expectPatched: false,
wantApplied: nil,
}
}
{
patch := makePatch(ResourceFilter{
ResourceType: tc.resourceType,
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
Services: []*ServiceName{
{CompoundServiceName: svc2.CompoundServiceName},
{CompoundServiceName: svc1.CompoundServiceName},
},
})
cases[fmt.Sprintf("%s - match", tc.resourceType)] = testCase{
args: args{
resourceType: tc.resourceType,
payload: extensioncommon.Payload[proto.Message]{
TrafficDirection: extensioncommon.TrafficDirectionOutbound,
ServiceName: &svc1.CompoundServiceName,
Message: tc.message,
RuntimeConfig: &extensioncommon.RuntimeConfig{
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
svc1.CompoundServiceName: {},
},
},
},
p: makeExtension(patch),
},
expectPatched: true,
wantApplied: []Patch{patch},
}
}
}
for n, tc := range cases { for n, tc := range cases {
t.Run(n, func(t *testing.T) { t.Run(n, func(t *testing.T) {
mockPatcher := MockPatcher[proto.Message]{} mockPatcher := MockPatcher[proto.Message]{}
_, patched, err := patchResourceType[proto.Message](tc.args.k, tc.args.p, tc.args.t, tc.args.d, &mockPatcher) _, patched, err := patchResourceType[proto.Message](tc.args.p, tc.args.resourceType, tc.args.payload, &mockPatcher)
require.NoError(t, err, "unexpected error from mock") require.NoError(t, err, "unexpected error from mock")
require.Equal(t, tc.expectPatched, patched) require.Equal(t, tc.expectPatched, patched)

View File

@ -195,7 +195,7 @@ func (p *pluginConfig) asyncDataSource(rtCfg *extensioncommon.RuntimeConfig) (*e
clusterSNI := "" clusterSNI := ""
for service, upstream := range rtCfg.Upstreams { for service, upstream := range rtCfg.Upstreams {
if service == remote.HttpURI.Service { if service == remote.HttpURI.Service {
for sni := range upstream.SNI { for sni := range upstream.SNIs {
clusterSNI = sni clusterSNI = sni
break break
} }

View File

@ -609,7 +609,7 @@ func makeTestRuntimeConfig(protocol string, enterprise bool) *extensioncommon.Ru
Namespace: acl.NamespaceOrDefault(ns), Namespace: acl.NamespaceOrDefault(ns),
Partition: acl.PartitionOrDefault(ap), Partition: acl.PartitionOrDefault(ap),
}: { }: {
SNI: map[string]struct{}{"test-file-server": {}}, SNIs: map[string]struct{}{"test-file-server": {}},
EnvoyID: "test-file-server", EnvoyID: "test-file-server",
}, },
}, },

View File

@ -34,7 +34,6 @@ import (
const ( const (
meshGatewayExportedClusterNamePrefix = "exported~" meshGatewayExportedClusterNamePrefix = "exported~"
failoverClusterNamePrefix = "failover-target~"
) )
// clustersFromSnapshot returns the xDS API representation of the "clusters" in the snapshot. // clustersFromSnapshot returns the xDS API representation of the "clusters" in the snapshot.
@ -1915,7 +1914,7 @@ func (s *ResourceGenerator) getTargetClusterName(upstreamsSnapshot *proxycfg.Con
} }
clusterName = CustomizeClusterName(clusterName, chain) clusterName = CustomizeClusterName(clusterName, chain)
if failover { if failover {
clusterName = failoverClusterNamePrefix + clusterName clusterName = xdscommon.FailoverClusterNamePrefix + clusterName
} }
if forMeshGateway { if forMeshGateway {
clusterName = meshGatewayExportedClusterNamePrefix + clusterName clusterName = meshGatewayExportedClusterNamePrefix + clusterName

View File

@ -26,6 +26,7 @@ import (
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/xds/extensionruntime" "github.com/hashicorp/consul/agent/xds/extensionruntime"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
"github.com/hashicorp/consul/envoyextensions/xdscommon" "github.com/hashicorp/consul/envoyextensions/xdscommon"
"github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil"
) )
@ -110,7 +111,7 @@ end`,
{ {
"ResourceFilter": map[string]interface{}{ "ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeCluster, "ResourceType": propertyoverride.ResourceTypeCluster,
"TrafficDirection": propertyoverride.TrafficDirectionOutbound, "TrafficDirection": extensioncommon.TrafficDirectionOutbound,
}, },
"Op": "add", "Op": "add",
"Path": "/outlier_detection/success_rate_minimum_hosts", "Path": "/outlier_detection/success_rate_minimum_hosts",
@ -125,7 +126,7 @@ end`,
{ {
"ResourceFilter": map[string]interface{}{ "ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeCluster, "ResourceType": propertyoverride.ResourceTypeCluster,
"TrafficDirection": propertyoverride.TrafficDirectionOutbound, "TrafficDirection": extensioncommon.TrafficDirectionOutbound,
}, },
"Op": "add", "Op": "add",
"Path": "/outlier_detection", "Path": "/outlier_detection",
@ -143,7 +144,7 @@ end`,
{ {
"ResourceFilter": map[string]interface{}{ "ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeCluster, "ResourceType": propertyoverride.ResourceTypeCluster,
"TrafficDirection": propertyoverride.TrafficDirectionOutbound, "TrafficDirection": extensioncommon.TrafficDirectionOutbound,
}, },
"Op": "remove", "Op": "remove",
"Path": "/outlier_detection", "Path": "/outlier_detection",
@ -157,7 +158,7 @@ end`,
{ {
"ResourceFilter": map[string]interface{}{ "ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeCluster, "ResourceType": propertyoverride.ResourceTypeCluster,
"TrafficDirection": propertyoverride.TrafficDirectionOutbound, "TrafficDirection": extensioncommon.TrafficDirectionOutbound,
}, },
"Op": "add", "Op": "add",
"Path": "/upstream_connection_options/tcp_keepalive/keepalive_probes", "Path": "/upstream_connection_options/tcp_keepalive/keepalive_probes",
@ -172,7 +173,7 @@ end`,
{ {
"ResourceFilter": map[string]interface{}{ "ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeCluster, "ResourceType": propertyoverride.ResourceTypeCluster,
"TrafficDirection": propertyoverride.TrafficDirectionOutbound, "TrafficDirection": extensioncommon.TrafficDirectionOutbound,
}, },
"Op": "add", "Op": "add",
"Path": "/round_robin_lb_config", "Path": "/round_robin_lb_config",
@ -181,6 +182,169 @@ end`,
}, },
}) })
propertyOverrideServiceDefaultsClusterLoadAssignmentOutboundAdd := makePropOverrideNsFunc(
map[string]interface{}{
"Patches": []map[string]interface{}{
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeClusterLoadAssignment,
"TrafficDirection": extensioncommon.TrafficDirectionOutbound,
},
"Op": "add",
"Path": "/policy/overprovisioning_factor",
"Value": 123,
},
},
})
propertyOverrideServiceDefaultsClusterLoadAssignmentInboundAdd := makePropOverrideNsFunc(
map[string]interface{}{
"Patches": []map[string]interface{}{
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeClusterLoadAssignment,
"TrafficDirection": extensioncommon.TrafficDirectionInbound,
},
"Op": "add",
"Path": "/policy/overprovisioning_factor",
"Value": 123,
},
},
})
propertyOverrideServiceDefaultsListenerInboundAdd := makePropOverrideNsFunc(
map[string]interface{}{
"Patches": []map[string]interface{}{
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeListener,
"TrafficDirection": extensioncommon.TrafficDirectionInbound,
},
"Op": "add",
"Path": "/stat_prefix",
"Value": "custom.stats",
},
},
})
propertyOverrideServiceDefaultsListenerOutboundAdd := makePropOverrideNsFunc(
map[string]interface{}{
"Patches": []map[string]interface{}{
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeListener,
"TrafficDirection": extensioncommon.TrafficDirectionOutbound,
},
"Op": "add",
"Path": "/stat_prefix",
"Value": "custom.stats",
},
},
})
propertyOverrideServiceDefaultsListenerOutboundDoesntApplyToInbound := makePropOverrideNsFunc(
map[string]interface{}{
"Patches": []map[string]interface{}{
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeListener,
"TrafficDirection": extensioncommon.TrafficDirectionInbound,
},
"Op": "add",
"Path": "/stat_prefix",
"Value": "custom.stats.inbound.only",
},
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeListener,
"TrafficDirection": extensioncommon.TrafficDirectionOutbound,
},
"Op": "add",
"Path": "/stat_prefix",
"Value": "custom.stats.outbound.only",
},
},
})
// Reverse order of above patches, to prove order is inconsequential
propertyOverrideServiceDefaultsListenerInboundDoesntApplyToOutbound := makePropOverrideNsFunc(
map[string]interface{}{
"Patches": []map[string]interface{}{
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeListener,
"TrafficDirection": extensioncommon.TrafficDirectionOutbound,
},
"Op": "add",
"Path": "/stat_prefix",
"Value": "custom.stats.outbound.only",
},
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeListener,
"TrafficDirection": extensioncommon.TrafficDirectionInbound,
},
"Op": "add",
"Path": "/stat_prefix",
"Value": "custom.stats.inbound.only",
},
},
})
propertyOverridePatchSpecificUpstreamService := makePropOverrideNsFunc(
map[string]interface{}{
"Patches": []map[string]interface{}{
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeListener,
"TrafficDirection": extensioncommon.TrafficDirectionOutbound,
"Services": []propertyoverride.ServiceName{
{CompoundServiceName: api.CompoundServiceName{Name: "db"}},
},
},
"Op": "add",
"Path": "/stat_prefix",
"Value": "custom.stats.outbound.only",
},
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeRoute,
"TrafficDirection": extensioncommon.TrafficDirectionOutbound,
"Services": []propertyoverride.ServiceName{
{CompoundServiceName: api.CompoundServiceName{Name: "db"}},
},
},
"Op": "add",
"Path": "/most_specific_header_mutations_wins",
"Value": true,
},
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeCluster,
"TrafficDirection": extensioncommon.TrafficDirectionOutbound,
"Services": []propertyoverride.ServiceName{
{CompoundServiceName: api.CompoundServiceName{Name: "db"}},
},
},
"Op": "add",
"Path": "/outlier_detection/success_rate_minimum_hosts",
"Value": 1234,
},
{
"ResourceFilter": map[string]interface{}{
"ResourceType": propertyoverride.ResourceTypeClusterLoadAssignment,
"TrafficDirection": extensioncommon.TrafficDirectionOutbound,
"Services": []propertyoverride.ServiceName{
{CompoundServiceName: api.CompoundServiceName{Name: "db"}},
},
},
"Op": "add",
"Path": "/policy/overprovisioning_factor",
"Value": 1234,
},
},
})
tests := []struct { tests := []struct {
name string name string
create func(t testinf.T) *proxycfg.ConfigSnapshot create func(t testinf.T) *proxycfg.ConfigSnapshot
@ -215,6 +379,54 @@ end`,
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, propertyOverrideServiceDefaultsRemoveOutlierDetection, nil) return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, propertyOverrideServiceDefaultsRemoveOutlierDetection, nil)
}, },
}, },
{
name: "propertyoverride-cluster-load-assignment-outbound-add",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, propertyOverrideServiceDefaultsClusterLoadAssignmentOutboundAdd, nil)
},
},
{
name: "propertyoverride-cluster-load-assignment-inbound-add",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, propertyOverrideServiceDefaultsClusterLoadAssignmentInboundAdd, nil)
},
},
{
name: "propertyoverride-listener-outbound-add",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, propertyOverrideServiceDefaultsListenerOutboundAdd, nil)
},
},
{
name: "propertyoverride-listener-inbound-add",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, propertyOverrideServiceDefaultsListenerInboundAdd, nil)
},
},
{
name: "propertyoverride-outbound-doesnt-apply-to-inbound",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, propertyOverrideServiceDefaultsListenerOutboundDoesntApplyToInbound, nil)
},
},
{
name: "propertyoverride-inbound-doesnt-apply-to-outbound",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, propertyOverrideServiceDefaultsListenerInboundDoesntApplyToOutbound, nil)
},
},
{
name: "propertyoverride-patch-specific-upstream-service-splitter",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "splitter-with-resolver-redirect-multidc", false, propertyOverridePatchSpecificUpstreamService, nil)
},
},
{
name: "propertyoverride-patch-specific-upstream-service-failover",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", false, propertyOverridePatchSpecificUpstreamService, nil)
},
},
{ {
name: "lambda-connect-proxy", name: "lambda-connect-proxy",
create: func(t testinf.T) *proxycfg.ConfigSnapshot { create: func(t testinf.T) *proxycfg.ConfigSnapshot {

View File

@ -56,19 +56,27 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound
// TODO(peering): consider PeerUpstreamEndpoints in addition to DiscoveryChain // TODO(peering): consider PeerUpstreamEndpoints in addition to DiscoveryChain
// These are the discovery chains for upstreams which have the Envoy Extensions applied to the local service. // These are the discovery chains for upstreams which have the Envoy Extensions applied to the local service.
for uid, dc := range cfgSnap.ConnectProxy.DiscoveryChain { for uid, chain := range cfgSnap.ConnectProxy.DiscoveryChain {
compoundServiceName := upstreamIDToCompoundServiceName(uid) compoundServiceName := upstreamIDToCompoundServiceName(uid)
extensionsMap[compoundServiceName] = convertEnvoyExtensions(dc.EnvoyExtensions) extensionsMap[compoundServiceName] = convertEnvoyExtensions(chain.EnvoyExtensions)
primarySNI := connect.ServiceSNI(uid.Name, "", chain.Namespace, chain.Partition, cfgSnap.Datacenter, trustDomain)
snis := make(map[string]struct{})
for _, t := range chain.Targets {
// SNI isn't set for peered services. We don't support peered services yet.
if t.SNI != "" {
snis[t.SNI] = struct{}{}
}
}
meta := uid.EnterpriseMeta
sni := connect.ServiceSNI(uid.Name, "", meta.NamespaceOrDefault(), meta.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain)
outgoingKind, ok := outgoingKindByService[compoundServiceName] outgoingKind, ok := outgoingKindByService[compoundServiceName]
if !ok { if !ok {
outgoingKind = api.ServiceKindConnectProxy outgoingKind = api.ServiceKindConnectProxy
} }
upstreamMap[compoundServiceName] = &extensioncommon.UpstreamData{ upstreamMap[compoundServiceName] = &extensioncommon.UpstreamData{
SNI: map[string]struct{}{sni: {}}, PrimarySNI: primarySNI,
SNIs: snis,
VIP: vipForService[compoundServiceName], VIP: vipForService[compoundServiceName],
EnvoyID: uid.EnvoyID(), EnvoyID: uid.EnvoyID(),
OutgoingProxyKind: outgoingKind, OutgoingProxyKind: outgoingKind,
@ -101,10 +109,10 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound
compoundServiceName := serviceNameToCompoundServiceName(svc) compoundServiceName := serviceNameToCompoundServiceName(svc)
extensionsMap[compoundServiceName] = convertEnvoyExtensions(c.EnvoyExtensions) extensionsMap[compoundServiceName] = convertEnvoyExtensions(c.EnvoyExtensions)
sni := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain) primarySNI := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain)
envoyID := proxycfg.NewUpstreamIDFromServiceName(svc) envoyID := proxycfg.NewUpstreamIDFromServiceName(svc)
snis := map[string]struct{}{sni: {}} snis := map[string]struct{}{primarySNI: {}}
resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc] resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc]
if hasResolver { if hasResolver {
@ -115,7 +123,8 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound
} }
upstreamMap[compoundServiceName] = &extensioncommon.UpstreamData{ upstreamMap[compoundServiceName] = &extensioncommon.UpstreamData{
SNI: snis, PrimarySNI: primarySNI,
SNIs: snis,
EnvoyID: envoyID.EnvoyID(), EnvoyID: envoyID.EnvoyID(),
OutgoingProxyKind: api.ServiceKindTerminatingGateway, OutgoingProxyKind: api.ServiceKindTerminatingGateway,
} }

View File

@ -58,28 +58,32 @@ func TestGetRuntimeConfigurations_TerminatingGateway(t *testing.T) {
IsSourcedFromUpstream: true, IsSourcedFromUpstream: true,
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
apiService: { apiService: {
SNI: map[string]struct{}{ PrimarySNI: "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
SNIs: map[string]struct{}{
"api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
}, },
EnvoyID: "api", EnvoyID: "api",
OutgoingProxyKind: "terminating-gateway", OutgoingProxyKind: "terminating-gateway",
}, },
cacheService: { cacheService: {
SNI: map[string]struct{}{ PrimarySNI: "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
SNIs: map[string]struct{}{
"cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
}, },
EnvoyID: "cache", EnvoyID: "cache",
OutgoingProxyKind: "terminating-gateway", OutgoingProxyKind: "terminating-gateway",
}, },
dbService: { dbService: {
SNI: map[string]struct{}{ PrimarySNI: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
SNIs: map[string]struct{}{
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
}, },
EnvoyID: "db", EnvoyID: "db",
OutgoingProxyKind: "terminating-gateway", OutgoingProxyKind: "terminating-gateway",
}, },
webService: { webService: {
SNI: map[string]struct{}{ PrimarySNI: "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
SNIs: map[string]struct{}{
"canary1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "canary1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
"canary2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "canary2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
"web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
@ -134,10 +138,25 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
EnvoyExtensions: envoyExtensions, EnvoyExtensions: envoyExtensions,
} }
serviceDefaultsV2 := &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db-v2",
Protocol: "http",
}
serviceSplitter := &structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "db",
Splits: []structs.ServiceSplit{
{Weight: 50},
{Weight: 50, Service: "db-v2"},
},
}
// Setup a snapshot where the db upstream is on a connect proxy. // Setup a snapshot where the db upstream is on a connect proxy.
snapConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, serviceDefaults) snapConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, serviceDefaults, serviceDefaultsV2, serviceSplitter)
// Setup a snapshot where the db upstream is on a terminating gateway. // Setup a snapshot where the db upstream is on a terminating gateway.
snapTermGw := proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", false, nil, nil, serviceDefaults) snapTermGw := proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", false, nil, nil, serviceDefaults, serviceDefaultsV2, serviceSplitter)
// Setup a snapshot with the local service web has extensions. // Setup a snapshot with the local service web has extensions.
snapWebConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, func(ns *structs.NodeService) { snapWebConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, func(ns *structs.NodeService) {
ns.Proxy.EnvoyExtensions = envoyExtensions ns.Proxy.EnvoyExtensions = envoyExtensions
@ -164,8 +183,10 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
IsSourcedFromUpstream: true, IsSourcedFromUpstream: true,
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
dbService: { dbService: {
SNI: map[string]struct{}{ PrimarySNI: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
SNIs: map[string]struct{}{
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
"db-v2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
}, },
EnvoyID: "db", EnvoyID: "db",
OutgoingProxyKind: "connect-proxy", OutgoingProxyKind: "connect-proxy",
@ -193,8 +214,10 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
IsSourcedFromUpstream: true, IsSourcedFromUpstream: true,
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
dbService: { dbService: {
SNI: map[string]struct{}{ PrimarySNI: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
SNIs: map[string]struct{}{
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
"db-v2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
}, },
EnvoyID: "db", EnvoyID: "db",
OutgoingProxyKind: "terminating-gateway", OutgoingProxyKind: "terminating-gateway",
@ -224,7 +247,8 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
IsSourcedFromUpstream: false, IsSourcedFromUpstream: false,
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
dbService: { dbService: {
SNI: map[string]struct{}{ PrimarySNI: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
SNIs: map[string]struct{}{
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
}, },
EnvoyID: "db", EnvoyID: "db",
@ -245,7 +269,8 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
IsSourcedFromUpstream: false, IsSourcedFromUpstream: false,
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
dbService: { dbService: {
SNI: map[string]struct{}{ PrimarySNI: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
SNIs: map[string]struct{}{
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
}, },
EnvoyID: "db", EnvoyID: "db",

View File

@ -11,6 +11,7 @@ import (
"github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/proxycfg" "github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/envoyextensions/xdscommon"
) )
type discoChainTargets struct { type discoChainTargets struct {
@ -149,7 +150,7 @@ func (ft discoChainTargets) sequential() ([]discoChainTargetGroup, error) {
var targetGroups []discoChainTargetGroup var targetGroups []discoChainTargetGroup
for i, t := range ft.targets { for i, t := range ft.targets {
targetGroups = append(targetGroups, discoChainTargetGroup{ targetGroups = append(targetGroups, discoChainTargetGroup{
ClusterName: fmt.Sprintf("%s%d~%s", failoverClusterNamePrefix, i, ft.baseClusterName), ClusterName: fmt.Sprintf("%s%d~%s", xdscommon.FailoverClusterNamePrefix, i, ft.baseClusterName),
Targets: []targetInfo{t}, Targets: []targetInfo{t},
}) })
} }

View File

@ -0,0 +1,199 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"clusterType": {
"name": "envoy.clusters.aggregate",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig",
"clusters": [
"failover-target~0~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"failover-target~1~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
]
}
},
"connectTimeout": "33s",
"lbPolicy": "CLUSTER_PROVIDED",
"outlierDetection": {
"successRateMinimumHosts": 1234
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "failover-target~0~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"altStatName": "failover-target~0~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "33s",
"circuitBreakers": {},
"outlierDetection": {
"successRateMinimumHosts": 1234
},
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"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"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db"
}
]
}
},
"sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "failover-target~1~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"altStatName": "failover-target~1~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "33s",
"circuitBreakers": {},
"outlierDetection": {
"successRateMinimumHosts": 1234
},
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"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"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/fail"
}
]
}
},
"sni": "fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "5s",
"circuitBreakers": {},
"outlierDetection": {},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"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"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target"
},
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target"
}
]
}
},
"sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
}
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "local_app",
"type": "STATIC",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "local_app",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "127.0.0.1",
"portValue": 8080
}
}
}
}
]
}
]
}
}
],
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"nonce": "00000001"
}

View File

@ -0,0 +1,179 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "5s",
"circuitBreakers": {},
"outlierDetection": {},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"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"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target"
},
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target"
}
]
}
},
"sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
}
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "local_app",
"type": "STATIC",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "local_app",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "127.0.0.1",
"portValue": 8080
}
}
}
}
]
}
]
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "v1.db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"altStatName": "v1.db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "5s",
"circuitBreakers": {},
"outlierDetection": {
"successRateMinimumHosts": 1234
},
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"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"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db"
}
]
}
},
"sni": "v1.db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "v2.db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul",
"altStatName": "v2.db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "5s",
"circuitBreakers": {},
"outlierDetection": {
"successRateMinimumHosts": 1234
},
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"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"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/db"
}
]
}
},
"sni": "v2.db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
}
],
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"nonce": "00000001"
}

View File

@ -0,0 +1,192 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"clusterType": {
"name": "envoy.clusters.aggregate",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig",
"clusters": [
"failover-target~0~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"failover-target~1~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
]
}
},
"connectTimeout": "33s",
"lbPolicy": "CLUSTER_PROVIDED"
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "failover-target~0~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"altStatName": "failover-target~0~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "33s",
"circuitBreakers": {},
"outlierDetection": {},
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"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"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db"
}
]
}
},
"sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "failover-target~1~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"altStatName": "failover-target~1~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "33s",
"circuitBreakers": {},
"outlierDetection": {},
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"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"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/fail"
}
]
}
},
"sni": "fail.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "5s",
"circuitBreakers": {},
"outlierDetection": {},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"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"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target"
},
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target"
}
]
}
},
"sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
}
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "local_app",
"type": "STATIC",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "local_app",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "127.0.0.1",
"portValue": 8080
}
}
}
}
]
}
]
}
}
],
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"nonce": "00000001"
}

View File

@ -0,0 +1,115 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "failover-target~0~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.10.1.1",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
},
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.10.1.2",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
],
"policy": {
"overprovisioningFactor": 1234
}
},
{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "failover-target~1~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.20.1.1",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
},
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.20.1.2",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
],
"policy": {
"overprovisioningFactor": 1234
}
},
{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.10.1.1",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
},
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.20.1.2",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
]
}
],
"typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"nonce": "00000001"
}

View File

@ -0,0 +1,115 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.10.1.1",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
},
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.20.1.2",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "v1.db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.10.1.1",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
},
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.10.1.2",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
],
"policy": {
"overprovisioningFactor": 1234
}
},
{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "v2.db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.20.1.1",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
},
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.20.1.2",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
],
"policy": {
"overprovisioningFactor": 1234
}
}
],
"typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"nonce": "00000001"
}

View File

@ -0,0 +1,109 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "failover-target~0~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.10.1.1",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
},
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.10.1.2",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "failover-target~1~db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.20.1.1",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
},
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.20.1.2",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.10.1.1",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
},
{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.20.1.2",
"portValue": 8080
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
]
}
],
"typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"nonce": "00000001"
}

View File

@ -0,0 +1,235 @@
{
"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
}
},
"statPrefix": "custom.stats.outbound.only",
"filterChains": [
{
"filters": [
{
"name": "envoy.filters.network.tcp_proxy",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
"statPrefix": "upstream.db.default.default.dc1",
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
}
],
"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"
}

View File

@ -0,0 +1,252 @@
{
"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
}
},
"statPrefix": "custom.stats.outbound.only",
"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",
"rds": {
"configSource": {
"ads": {},
"resourceApiVersion": "V3"
},
"routeConfigName": "db"
},
"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.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"
}

View File

@ -0,0 +1,234 @@
{
"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.tcp_proxy",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
"statPrefix": "upstream.db.default.default.dc1",
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
}
],
"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"
}

View File

@ -0,0 +1,32 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "db",
"virtualHosts": [
{
"name": "db",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"timeout": "33s"
}
}
]
}
],
"mostSpecificHeaderMutationsWins": true,
"validateClusters": true
}
],
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"nonce": "00000001"
}

View File

@ -0,0 +1,43 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "db",
"virtualHosts": [
{
"name": "db",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"weightedClusters": {
"clusters": [
{
"name": "v1.db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"weight": 5000
},
{
"name": "v2.db.default.dc2.internal.11111111-2222-3333-4444-555555555555.consul",
"weight": 5000
}
],
"totalWeight": 10000
}
}
}
]
}
],
"mostSpecificHeaderMutationsWins": true,
"validateClusters": true
}
],
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"nonce": "00000001"
}

View File

@ -0,0 +1,31 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "db",
"virtualHosts": [
{
"name": "db",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"timeout": "33s"
}
}
]
}
],
"validateClusters": true
}
],
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"nonce": "00000001"
}

View File

@ -38,7 +38,7 @@ type BasicExtension interface {
// PatchRoute patches a route to include the custom Envoy configuration // PatchRoute patches a route to include the custom Envoy configuration
// required to integrate with the built in extension template. // required to integrate with the built in extension template.
// See also PatchRoutes. // See also PatchRoutes.
PatchRoute(*RuntimeConfig, *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) PatchRoute(RoutePayload) (*envoy_route_v3.RouteConfiguration, bool, error)
// PatchRoutes patches routes to include the custom Envoy configuration // PatchRoutes patches routes to include the custom Envoy configuration
// required to integrate with the built in extension template. // required to integrate with the built in extension template.
@ -51,7 +51,7 @@ type BasicExtension interface {
// PatchCluster patches a cluster to include the custom Envoy configuration // PatchCluster patches a cluster to include the custom Envoy configuration
// required to integrate with the built in extension template. // required to integrate with the built in extension template.
// See also PatchClusters. // See also PatchClusters.
PatchCluster(*RuntimeConfig, *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) PatchCluster(ClusterPayload) (*envoy_cluster_v3.Cluster, bool, error)
// PatchClusters patches clusters to include the custom Envoy configuration // PatchClusters patches clusters to include the custom Envoy configuration
// required to integrate with the built in extension template. // required to integrate with the built in extension template.
@ -63,12 +63,12 @@ type BasicExtension interface {
// PatchClusterLoadAssignment patches a cluster load assignment to include the custom Envoy configuration // PatchClusterLoadAssignment patches a cluster load assignment to include the custom Envoy configuration
// required to integrate with the built in extension template. // required to integrate with the built in extension template.
PatchClusterLoadAssignment(*RuntimeConfig, *envoy_endpoint_v3.ClusterLoadAssignment) (*envoy_endpoint_v3.ClusterLoadAssignment, bool, error) PatchClusterLoadAssignment(ClusterLoadAssignmentPayload) (*envoy_endpoint_v3.ClusterLoadAssignment, bool, error)
// PatchListener patches a listener to include the custom Envoy configuration // PatchListener patches a listener to include the custom Envoy configuration
// required to integrate with the built in extension template. // required to integrate with the built in extension template.
// See also PatchListeners. // See also PatchListeners.
PatchListener(*RuntimeConfig, *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, bool, error) PatchListener(ListenerPayload) (*envoy_listener_v3.Listener, bool, error)
// PatchListeners patches listeners to include the custom Envoy configuration // PatchListeners patches listeners to include the custom Envoy configuration
// required to integrate with the built in extension template. // required to integrate with the built in extension template.
@ -81,7 +81,7 @@ type BasicExtension interface {
// PatchFilter patches an Envoy filter to include the custom Envoy // PatchFilter patches an Envoy filter to include the custom Envoy
// configuration required to integrate with the built in extension template. // configuration required to integrate with the built in extension template.
// See also PatchFilters. // See also PatchFilters.
PatchFilter(cfg *RuntimeConfig, f *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) PatchFilter(FilterPayload) (*envoy_listener_v3.Filter, bool, error)
// PatchFilters patches Envoy filters to include the custom Envoy // PatchFilters patches Envoy filters to include the custom Envoy
// configuration required to integrate with the built in extension template. // configuration required to integrate with the built in extension template.
@ -197,7 +197,7 @@ func (b *BasicEnvoyExtender) patchClusters(config *RuntimeConfig, clusters Clust
return clusters, fmt.Errorf("error patching clusters: %w", err) return clusters, fmt.Errorf("error patching clusters: %w", err)
} }
for nameOrSNI, cluster := range clusters { for nameOrSNI, cluster := range clusters {
patchedCluster, patched, err := b.Extension.PatchCluster(config, cluster) patchedCluster, patched, err := b.Extension.PatchCluster(config.GetClusterPayload(cluster))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster %q: %w", nameOrSNI, err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster %q: %w", nameOrSNI, err))
} }
@ -208,7 +208,7 @@ func (b *BasicEnvoyExtender) patchClusters(config *RuntimeConfig, clusters Clust
// We patch cluster load assignments directly above for EDS, but also here for CDS, // We patch cluster load assignments directly above for EDS, but also here for CDS,
// since updates can come from either. // since updates can come from either.
if patchedCluster.LoadAssignment != nil { if patchedCluster.LoadAssignment != nil {
patchedClusterLoadAssignment, patched, err := b.Extension.PatchClusterLoadAssignment(config, patchedCluster.LoadAssignment) patchedClusterLoadAssignment, patched, err := b.Extension.PatchClusterLoadAssignment(config.GetClusterLoadAssignmentPayload(patchedCluster.LoadAssignment))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching load assignment for cluster %q: %w", nameOrSNI, err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching load assignment for cluster %q: %w", nameOrSNI, err))
} else if patched { } else if patched {
@ -225,7 +225,7 @@ func (b *BasicEnvoyExtender) patchClusterLoadAssignments(config *RuntimeConfig,
var resultErr error var resultErr error
for nameOrSNI, clusterLoadAssignment := range clusterLoadAssignments { for nameOrSNI, clusterLoadAssignment := range clusterLoadAssignments {
patchedClusterLoadAssignment, patched, err := b.Extension.PatchClusterLoadAssignment(config, clusterLoadAssignment) patchedClusterLoadAssignment, patched, err := b.Extension.PatchClusterLoadAssignment(config.GetClusterLoadAssignmentPayload(clusterLoadAssignment))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster load assignment %q: %w", nameOrSNI, err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster load assignment %q: %w", nameOrSNI, err))
} }
@ -246,7 +246,7 @@ func (b *BasicEnvoyExtender) patchRoutes(config *RuntimeConfig, routes RouteMap)
return routes, fmt.Errorf("error patching routes: %w", err) return routes, fmt.Errorf("error patching routes: %w", err)
} }
for nameOrSNI, route := range patchedRoutes { for nameOrSNI, route := range patchedRoutes {
patchedRoute, patched, err := b.Extension.PatchRoute(config, route) patchedRoute, patched, err := b.Extension.PatchRoute(config.GetRoutePayload(route))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching route %q: %w", nameOrSNI, err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching route %q: %w", nameOrSNI, err))
} }
@ -267,7 +267,7 @@ func (b *BasicEnvoyExtender) patchListeners(config *RuntimeConfig, listeners Lis
return listeners, fmt.Errorf("error patching listeners: %w", err) return listeners, fmt.Errorf("error patching listeners: %w", err)
} }
for nameOrSNI, listener := range listeners { for nameOrSNI, listener := range listeners {
patchedListener, patched, err := b.Extension.PatchListener(config, listener) patchedListener, patched, err := b.Extension.PatchListener(config.GetListenerPayload(listener))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener %q: %w", nameOrSNI, err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener %q: %w", nameOrSNI, err))
} }
@ -298,7 +298,7 @@ func (b *BasicEnvoyExtender) patchListenerFilterChains(config *RuntimeConfig, l
func (b *BasicEnvoyExtender) patchTerminatingGatewayListenerFilterChains(config *RuntimeConfig, l *envoy_listener_v3.Listener, nameOrSNI string) (*envoy_listener_v3.Listener, error) { func (b *BasicEnvoyExtender) patchTerminatingGatewayListenerFilterChains(config *RuntimeConfig, l *envoy_listener_v3.Listener, nameOrSNI string) (*envoy_listener_v3.Listener, error) {
var resultErr error var resultErr error
for idx, filterChain := range l.FilterChains { for idx, filterChain := range l.FilterChains {
if patchedFilterChain, err := b.patchFilterChain(config, filterChain, IsInboundPublicListener(l)); err == nil { if patchedFilterChain, err := b.patchFilterChain(config, filterChain, l); err == nil {
l.FilterChains[idx] = patchedFilterChain l.FilterChains[idx] = patchedFilterChain
} else { } else {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter chain of terminating gateway listener %q: %w", nameOrSNI, err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter chain of terminating gateway listener %q: %w", nameOrSNI, err))
@ -330,10 +330,8 @@ func (b *BasicEnvoyExtender) patchConnectProxyListenerFilterChains(config *Runti
func (b *BasicEnvoyExtender) patchNonTProxyConnectProxyListenerFilterChains(config *RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, error) { func (b *BasicEnvoyExtender) patchNonTProxyConnectProxyListenerFilterChains(config *RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, error) {
var resultErr error var resultErr error
inbound := IsInboundPublicListener(l)
for idx, filterChain := range l.FilterChains { for idx, filterChain := range l.FilterChains {
if patchedFilterChain, err := b.patchFilterChain(config, filterChain, inbound); err == nil { if patchedFilterChain, err := b.patchFilterChain(config, filterChain, l); err == nil {
l.FilterChains[idx] = patchedFilterChain l.FilterChains[idx] = patchedFilterChain
} else { } else {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter chain: %w", err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter chain: %w", err))
@ -346,7 +344,6 @@ func (b *BasicEnvoyExtender) patchTProxyListenerFilterChains(config *RuntimeConf
var resultErr error var resultErr error
vip := config.Upstreams[config.ServiceName].VIP vip := config.Upstreams[config.ServiceName].VIP
inbound := IsInboundPublicListener(l)
for idx, filterChain := range l.FilterChains { for idx, filterChain := range l.FilterChains {
match := filterChainTProxyMatch(vip, filterChain) match := filterChainTProxyMatch(vip, filterChain)
@ -354,7 +351,7 @@ func (b *BasicEnvoyExtender) patchTProxyListenerFilterChains(config *RuntimeConf
continue continue
} }
if patchedFilterChain, err := b.patchFilterChain(config, filterChain, inbound); err == nil { if patchedFilterChain, err := b.patchFilterChain(config, filterChain, l); err == nil {
l.FilterChains[idx] = patchedFilterChain l.FilterChains[idx] = patchedFilterChain
} else { } else {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter chain for %q: %w", vip, err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter chain for %q: %w", vip, err))
@ -364,14 +361,15 @@ func (b *BasicEnvoyExtender) patchTProxyListenerFilterChains(config *RuntimeConf
return l, resultErr return l, resultErr
} }
func (b *BasicEnvoyExtender) patchFilterChain(config *RuntimeConfig, filterChain *envoy_listener_v3.FilterChain, isInboundListener bool) (*envoy_listener_v3.FilterChain, error) { func (b *BasicEnvoyExtender) patchFilterChain(config *RuntimeConfig, filterChain *envoy_listener_v3.FilterChain, l *envoy_listener_v3.Listener) (*envoy_listener_v3.FilterChain, error) {
var resultErr error var resultErr error
patchedFilters, err := b.Extension.PatchFilters(config, filterChain.Filters, isInboundListener) inbound := IsInboundPublicListener(l)
patchedFilters, err := b.Extension.PatchFilters(config, filterChain.Filters, inbound)
if err != nil { if err != nil {
return filterChain, fmt.Errorf("error patching filters: %w", err) return filterChain, fmt.Errorf("error patching filters: %w", err)
} }
for idx, filter := range patchedFilters { for idx, filter := range patchedFilters {
patchedFilter, patched, err := b.Extension.PatchFilter(config, filter, isInboundListener) patchedFilter, patched, err := b.Extension.PatchFilter(config.GetFilterPayload(filter, l))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter: %w", err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter: %w", err))
} }

View File

@ -20,8 +20,8 @@ type BasicExtensionAdapter struct{}
func (BasicExtensionAdapter) CanApply(_ *RuntimeConfig) bool { return false } func (BasicExtensionAdapter) CanApply(_ *RuntimeConfig) bool { return false }
// PatchCluster provides a default implementation of the PatchCluster interface that does nothing. // PatchCluster provides a default implementation of the PatchCluster interface that does nothing.
func (BasicExtensionAdapter) PatchCluster(_ *RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) { func (BasicExtensionAdapter) PatchCluster(p ClusterPayload) (*envoy_cluster_v3.Cluster, bool, error) {
return c, false, nil return p.Message, false, nil
} }
// PatchClusters provides a default implementation of the PatchClusters interface that does nothing. // PatchClusters provides a default implementation of the PatchClusters interface that does nothing.
@ -30,13 +30,13 @@ func (BasicExtensionAdapter) PatchClusters(_ *RuntimeConfig, c ClusterMap) (Clus
} }
// PatchClusterLoadAssignment provides a default implementation of the PatchClusterLoadAssignment interface that does nothing. // PatchClusterLoadAssignment provides a default implementation of the PatchClusterLoadAssignment interface that does nothing.
func (BasicExtensionAdapter) PatchClusterLoadAssignment(_ *RuntimeConfig, c *envoy_endpoint_v3.ClusterLoadAssignment) (*envoy_endpoint_v3.ClusterLoadAssignment, bool, error) { func (BasicExtensionAdapter) PatchClusterLoadAssignment(p ClusterLoadAssignmentPayload) (*envoy_endpoint_v3.ClusterLoadAssignment, bool, error) {
return c, false, nil return p.Message, false, nil
} }
// PatchListener provides a default implementation of the PatchListener interface that does nothing. // PatchListener provides a default implementation of the PatchListener interface that does nothing.
func (BasicExtensionAdapter) PatchListener(_ *RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, bool, error) { func (BasicExtensionAdapter) PatchListener(p ListenerPayload) (*envoy_listener_v3.Listener, bool, error) {
return l, false, nil return p.Message, false, nil
} }
// PatchListeners provides a default implementation of the PatchListeners interface that does nothing. // PatchListeners provides a default implementation of the PatchListeners interface that does nothing.
@ -45,8 +45,8 @@ func (BasicExtensionAdapter) PatchListeners(_ *RuntimeConfig, l ListenerMap) (Li
} }
// PatchFilter provides a default implementation of the PatchFilter interface that does nothing. // PatchFilter provides a default implementation of the PatchFilter interface that does nothing.
func (BasicExtensionAdapter) PatchFilter(_ *RuntimeConfig, f *envoy_listener_v3.Filter, _ bool) (*envoy_listener_v3.Filter, bool, error) { func (BasicExtensionAdapter) PatchFilter(p FilterPayload) (*envoy_listener_v3.Filter, bool, error) {
return f, false, nil return p.Message, false, nil
} }
// PatchFilters provides a default implementation of the PatchFilters interface that does nothing. // PatchFilters provides a default implementation of the PatchFilters interface that does nothing.
@ -55,8 +55,8 @@ func (BasicExtensionAdapter) PatchFilters(_ *RuntimeConfig, f []*envoy_listener_
} }
// PatchRoute provides a default implementation of the PatchRoute interface that does nothing. // PatchRoute provides a default implementation of the PatchRoute interface that does nothing.
func (BasicExtensionAdapter) PatchRoute(_ *RuntimeConfig, r *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) { func (BasicExtensionAdapter) PatchRoute(p RoutePayload) (*envoy_route_v3.RouteConfiguration, bool, error) {
return r, false, nil return p.Message, false, nil
} }
// PatchRoutes provides a default implementation of the PatchRoutes interface that does nothing. // PatchRoutes provides a default implementation of the PatchRoutes interface that does nothing.

View File

@ -17,9 +17,11 @@ import (
envoy_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3" envoy_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
"github.com/hashicorp/consul/envoyextensions/xdscommon"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/envoyextensions/xdscommon"
) )
// MakeUpstreamTLSTransportSocket generates an Envoy transport socket for the given TLS context. // MakeUpstreamTLSTransportSocket generates an Envoy transport socket for the given TLS context.
@ -70,6 +72,206 @@ func MakeFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, erro
}, nil }, nil
} }
// TrafficDirection determines whether inbound or outbound Envoy resources will be patched.
type TrafficDirection string
const (
TrafficDirectionInbound TrafficDirection = "inbound"
TrafficDirectionOutbound TrafficDirection = "outbound"
)
var TrafficDirections = StringSet{string(TrafficDirectionInbound): {}, string(TrafficDirectionOutbound): {}}
type StringSet map[string]struct{}
func (c *StringSet) CheckRequired(v, fieldName string) error {
if _, ok := (*c)[v]; !ok {
if v == "" {
return fmt.Errorf("field %s is required", fieldName)
}
var keys []string
for k := range *c {
keys = append(keys, k)
}
return fmt.Errorf("invalid %s '%q'; supported values: %s",
fieldName, v, strings.Join(keys, ", "))
}
return nil
}
func NormalizeEmptyToDefault(s string) string {
if s == "" {
return "default"
}
return s
}
func NormalizeServiceName(sn *api.CompoundServiceName) {
sn.Namespace = NormalizeEmptyToDefault(sn.Namespace)
sn.Partition = NormalizeEmptyToDefault(sn.Partition)
}
// Payload represents a single Envoy resource to be modified by extensions.
// It associates the RuntimeConfig of the local proxy, the TrafficDirection
// of the resource, and the CompoundServiceName and UpstreamData (if outbound)
// of a service the Envoy resource corresponds to.
type Payload[K proto.Message] struct {
RuntimeConfig *RuntimeConfig
ServiceName *api.CompoundServiceName
Upstream *UpstreamData
TrafficDirection TrafficDirection
Message K
}
func (p Payload[K]) IsInbound() bool {
return p.TrafficDirection == TrafficDirectionInbound
}
type ClusterPayload = Payload[*envoy_cluster_v3.Cluster]
type ClusterLoadAssignmentPayload = Payload[*envoy_endpoint_v3.ClusterLoadAssignment]
type ListenerPayload = Payload[*envoy_listener_v3.Listener]
type FilterPayload = Payload[*envoy_listener_v3.Filter]
type RoutePayload = Payload[*envoy_route_v3.RouteConfiguration]
func (cfg *RuntimeConfig) GetClusterPayload(c *envoy_cluster_v3.Cluster) ClusterPayload {
d := TrafficDirectionOutbound
var u *UpstreamData
var sn *api.CompoundServiceName
if IsLocalAppCluster(c) {
d = TrafficDirectionInbound
} else {
u, sn = cfg.findUpstreamBySNI(c.Name)
}
return ClusterPayload{
RuntimeConfig: cfg,
ServiceName: sn,
Upstream: u,
TrafficDirection: d,
Message: c,
}
}
func (c *RuntimeConfig) GetListenerPayload(l *envoy_listener_v3.Listener) ListenerPayload {
d := TrafficDirectionOutbound
var u *UpstreamData
var sn *api.CompoundServiceName
if IsInboundPublicListener(l) {
d = TrafficDirectionInbound
} else {
u, sn = c.findUpstreamByEnvoyID(GetListenerEnvoyID(l))
}
return ListenerPayload{
RuntimeConfig: c,
ServiceName: sn,
Upstream: u,
TrafficDirection: d,
Message: l,
}
}
func (c *RuntimeConfig) GetFilterPayload(f *envoy_listener_v3.Filter, l *envoy_listener_v3.Listener) FilterPayload {
d := TrafficDirectionOutbound
var u *UpstreamData
var sn *api.CompoundServiceName
if IsInboundPublicListener(l) {
d = TrafficDirectionInbound
} else {
u, sn = c.findUpstreamByEnvoyID(GetListenerEnvoyID(l))
}
return FilterPayload{
RuntimeConfig: c,
ServiceName: sn,
Upstream: u,
TrafficDirection: d,
Message: f,
}
}
func (c *RuntimeConfig) GetRoutePayload(r *envoy_route_v3.RouteConfiguration) RoutePayload {
d := TrafficDirectionOutbound
var u *UpstreamData
var sn *api.CompoundServiceName
if IsRouteToLocalAppCluster(r) {
d = TrafficDirectionInbound
} else {
u, sn = c.findUpstreamByEnvoyID(r.Name)
}
return RoutePayload{
RuntimeConfig: c,
ServiceName: sn,
Upstream: u,
TrafficDirection: d,
Message: r,
}
}
func (cfg *RuntimeConfig) GetClusterLoadAssignmentPayload(c *envoy_endpoint_v3.ClusterLoadAssignment) ClusterLoadAssignmentPayload {
d := TrafficDirectionOutbound
var u *UpstreamData
var sn *api.CompoundServiceName
if IsLocalAppClusterLoadAssignment(c) {
d = TrafficDirectionInbound
} else {
u, sn = cfg.findUpstreamBySNI(c.ClusterName)
}
return ClusterLoadAssignmentPayload{
RuntimeConfig: cfg,
ServiceName: sn,
Upstream: u,
TrafficDirection: d,
Message: c,
}
}
func (c *RuntimeConfig) findUpstreamByEnvoyID(envoyID string) (*UpstreamData, *api.CompoundServiceName) {
for sn, u := range c.Upstreams {
if u.EnvoyID == envoyID {
return u, &sn
}
}
return nil, nil
}
func (c *RuntimeConfig) findUpstreamBySNI(sni string) (*UpstreamData, *api.CompoundServiceName) {
for sn, u := range c.Upstreams {
_, ok := u.SNIs[sni]
if ok {
return u, &sn
}
if strings.HasPrefix(sni, xdscommon.FailoverClusterNamePrefix) {
parts := strings.Split(sni, "~")
if len(parts) != 3 {
continue
}
id := parts[2]
_, ok := u.SNIs[id]
if ok {
return u, &sn
}
}
}
return nil, nil
}
// GetListenerEnvoyID returns the Envoy ID string parsed from the name of the given Listener. If none is found, it // GetListenerEnvoyID returns the Envoy ID string parsed from the name of the given Listener. If none is found, it
// returns the empty string. // returns the empty string.
func GetListenerEnvoyID(l *envoy_listener_v3.Listener) string { func GetListenerEnvoyID(l *envoy_listener_v3.Listener) string {

View File

@ -8,8 +8,11 @@ import "github.com/hashicorp/consul/api"
// UpstreamData has the SNI, EnvoyID, and OutgoingProxyKind of the upstream services for the local proxy and this data // UpstreamData has the SNI, EnvoyID, and OutgoingProxyKind of the upstream services for the local proxy and this data
// is used to choose which Envoy resources to patch. // is used to choose which Envoy resources to patch.
type UpstreamData struct { type UpstreamData struct {
// SNI is the SNI header used to reach an upstream service. // This is the SNI for the upstream service without accounting for any discovery chain magic.
SNI map[string]struct{} PrimarySNI string
// SNIs is the SNIs header used to reach an upstream service.
SNIs map[string]struct{}
// EnvoyID is the envoy ID of an upstream service, structured <service> or <partition>/<ns>/<service> when using a // EnvoyID is the envoy ID of an upstream service, structured <service> or <partition>/<ns>/<service> when using a
// non-default namespace or partition. // non-default namespace or partition.
@ -68,7 +71,7 @@ type RuntimeConfig struct {
// Only used when IsSourcedFromUpstream is true. // Only used when IsSourcedFromUpstream is true.
func (c RuntimeConfig) MatchesUpstreamServiceSNI(sni string) bool { func (c RuntimeConfig) MatchesUpstreamServiceSNI(sni string) bool {
u := c.Upstreams[c.ServiceName] u := c.Upstreams[c.ServiceName]
_, match := u.SNI[sni] _, match := u.SNIs[sni]
return match return match
} }

View File

@ -20,7 +20,7 @@ func makeTestRuntimeConfig() RuntimeConfig {
sn: { sn: {
EnvoyID: "eid", EnvoyID: "eid",
OutgoingProxyKind: api.ServiceKindTerminatingGateway, OutgoingProxyKind: api.ServiceKindTerminatingGateway,
SNI: map[string]struct{}{ SNIs: map[string]struct{}{
"sni1": {}, "sni1": {},
"sni2": {}, "sni2": {},
}, },

View File

@ -74,7 +74,7 @@ func (ext *UpstreamEnvoyExtender) Extend(resources *xdscommon.IndexedResources,
continue continue
} }
newCluster, patched, err := ext.Extension.PatchCluster(config, resource) newCluster, patched, err := ext.Extension.PatchCluster(config.GetClusterPayload(resource))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster: %w", err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster: %w", err))
continue continue
@ -101,7 +101,7 @@ func (ext *UpstreamEnvoyExtender) Extend(resources *xdscommon.IndexedResources,
continue continue
} }
newRoute, patched, err := ext.Extension.PatchRoute(config, resource) newRoute, patched, err := ext.Extension.PatchRoute(config.GetRoutePayload(resource))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching route: %w", err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching route: %w", err))
continue continue
@ -146,7 +146,7 @@ func (ext *UpstreamEnvoyExtender) patchTerminatingGatewayListener(config *Runtim
var filters []*envoy_listener_v3.Filter var filters []*envoy_listener_v3.Filter
for _, filter := range filterChain.Filters { for _, filter := range filterChain.Filters {
newFilter, ok, err := ext.Extension.PatchFilter(config, filter, IsInboundPublicListener(l)) newFilter, ok, err := ext.Extension.PatchFilter(config.GetFilterPayload(filter, l))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
@ -190,7 +190,7 @@ func (ext *UpstreamEnvoyExtender) patchConnectProxyListener(config *RuntimeConfi
var filters []*envoy_listener_v3.Filter var filters []*envoy_listener_v3.Filter
for _, filter := range filterChain.Filters { for _, filter := range filterChain.Filters {
newFilter, ok, err := ext.Extension.PatchFilter(config, filter, IsInboundPublicListener(l)) newFilter, ok, err := ext.Extension.PatchFilter(config.GetFilterPayload(filter, l))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
filters = append(filters, filter) filters = append(filters, filter)
@ -225,7 +225,7 @@ func (ext *UpstreamEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l *
} }
for _, filter := range filterChain.Filters { for _, filter := range filterChain.Filters {
newFilter, ok, err := ext.Extension.PatchFilter(config, filter, IsInboundPublicListener(l)) newFilter, ok, err := ext.Extension.PatchFilter(config.GetFilterPayload(filter, l))
if err != nil { if err != nil {
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err)) resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
filters = append(filters, filter) filters = append(filters, filter)

View File

@ -52,6 +52,8 @@ const (
// SecretType is the TypeURL for Secret discovery responses. // SecretType is the TypeURL for Secret discovery responses.
SecretType = apiTypePrefix + "envoy.extensions.transport_sockets.tls.v3.Secret" SecretType = apiTypePrefix + "envoy.extensions.transport_sockets.tls.v3.Secret"
FailoverClusterNamePrefix = "failover-target~"
) )
type IndexedResources struct { type IndexedResources struct {

View File

@ -87,7 +87,7 @@ func Validate(indexedResources *xdscommon.IndexedResources, envoyID string, vip
// the cluster SNIs configured on this proxy, not just the upstream being validated. This means the // the cluster SNIs configured on this proxy, not just the upstream being validated. This means the
// PatchCluster function in the Validate plugin will be run on all clusters, but errors will only // PatchCluster function in the Validate plugin will be run on all clusters, but errors will only
// surface for clusters related to the upstream being validated. // surface for clusters related to the upstream being validated.
SNI: snis, SNIs: snis,
EnvoyID: envoyID, EnvoyID: envoyID,
}, },
}, },

View File

@ -92,10 +92,10 @@ func MakeValidate(ext extensioncommon.RuntimeConfig) (extensioncommon.BasicExten
upstream, ok := ext.Upstreams[ext.ServiceName] upstream, ok := ext.Upstreams[ext.ServiceName]
if ok { if ok {
vip = upstream.VIP vip = upstream.VIP
if upstream.SNI == nil || len(upstream.SNI) == 0 { if upstream.SNIs == nil || len(upstream.SNIs) == 0 {
return nil, fmt.Errorf("no SNIs were set, unable to validate Envoy clusters") return nil, fmt.Errorf("no SNIs were set, unable to validate Envoy clusters")
} }
snis = upstream.SNI snis = upstream.SNIs
} }
if mainEnvoyID == "" && vip == "" { if mainEnvoyID == "" && vip == "" {
return nil, fmt.Errorf("envoyID or virtual IP is required") return nil, fmt.Errorf("envoyID or virtual IP is required")
@ -306,84 +306,87 @@ func (p *Validate) CanApply(config *extensioncommon.RuntimeConfig) bool {
return true return true
} }
func (p *Validate) PatchRoute(config *extensioncommon.RuntimeConfig, route *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) { func (v *Validate) PatchRoute(p extensioncommon.RoutePayload) (*envoy_route_v3.RouteConfiguration, bool, error) {
route := p.Message
// Route name on connect proxies will be the envoy ID. We are only validating routes for the specific upstream with // Route name on connect proxies will be the envoy ID. We are only validating routes for the specific upstream with
// the envoyID configured. // the envoyID configured.
if route.Name != p.envoyID { if route.Name != p.Upstream.EnvoyID {
return route, false, nil return route, false, nil
} }
p.route = true v.route = true
for sni := range extensioncommon.RouteClusterNames(route) { for sni := range extensioncommon.RouteClusterNames(route) {
if _, ok := p.resources[sni]; ok { if _, ok := v.resources[sni]; ok {
continue continue
} }
p.resources[sni] = &resource{required: true} v.resources[sni] = &resource{required: true}
} }
return route, false, nil return route, false, nil
} }
func (p *Validate) PatchCluster(config *extensioncommon.RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) { func (v *Validate) PatchCluster(p extensioncommon.ClusterPayload) (*envoy_cluster_v3.Cluster, bool, error) {
v, ok := p.resources[c.Name] c := p.Message
val, ok := v.resources[c.Name]
if !ok { if !ok {
v = &resource{} val = &resource{}
p.resources[c.Name] = v v.resources[c.Name] = val
} }
v.cluster = true val.cluster = true
// If it's an aggregate cluster, add the child clusters to p.resources if they are not already there. // If it's an aggregate cluster, add the child clusters to p.resources if they are not already there.
aggregateCluster, ok := isAggregateCluster(c) aggregateCluster, ok := isAggregateCluster(c)
if ok { if ok {
// Mark this as an aggregate cluster, so we know we do not need to validate its endpoints directly. // Mark this as an aggregate cluster, so we know we do not need to validate its endpoints directly.
v.aggregateCluster = true val.aggregateCluster = true
for _, clusterName := range aggregateCluster.Clusters { for _, clusterName := range aggregateCluster.Clusters {
r, ok := p.resources[clusterName] r, ok := v.resources[clusterName]
if !ok { if !ok {
r = &resource{} r = &resource{}
p.resources[clusterName] = r v.resources[clusterName] = r
} }
if v.aggregateClusterChildren == nil { if val.aggregateClusterChildren == nil {
v.aggregateClusterChildren = []string{} val.aggregateClusterChildren = []string{}
} }
// On the parent cluster, add the children. // On the parent cluster, add the children.
v.aggregateClusterChildren = append(v.aggregateClusterChildren, clusterName) val.aggregateClusterChildren = append(val.aggregateClusterChildren, clusterName)
// On the child cluster, set the parent. // On the child cluster, set the parent.
r.parentCluster = c.Name r.parentCluster = c.Name
// The child clusters of an aggregate cluster will be required if the parent cluster is. // The child clusters of an aggregate cluster will be required if the parent cluster is.
r.required = v.required r.required = val.required
} }
return c, false, nil return c, false, nil
} }
if c.EdsClusterConfig != nil { if c.EdsClusterConfig != nil {
v.usesEDS = true val.usesEDS = true
} else { } else {
la := c.LoadAssignment la := c.LoadAssignment
if la == nil { if la == nil {
return c, false, nil return c, false, nil
} }
v.endpoints = len(la.Endpoints) + len(la.NamedEndpoints) val.endpoints = len(la.Endpoints) + len(la.NamedEndpoints)
} }
return c, false, nil return c, false, nil
} }
func (p *Validate) PatchFilter(config *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, _ bool) (*envoy_listener_v3.Filter, bool, error) { func (v *Validate) PatchFilter(p extensioncommon.FilterPayload) (*envoy_listener_v3.Filter, bool, error) {
filter := p.Message
// If a single filter exists for a listener we say it exists. // If a single filter exists for a listener we say it exists.
p.listener = true v.listener = true
if httpConfig := envoy_resource_v3.GetHTTPConnectionManager(filter); httpConfig != nil { if httpConfig := envoy_resource_v3.GetHTTPConnectionManager(filter); httpConfig != nil {
// If the http filter uses RDS, then the clusters we need to validate exist in the route, and there's nothing // If the http filter uses RDS, then the clusters we need to validate exist in the route, and there's nothing
// else we need to do with the filter. // else we need to do with the filter.
if httpConfig.GetRds() != nil { if httpConfig.GetRds() != nil {
p.usesRDS = true v.usesRDS = true
// Edit the runtime configuration to add an envoy ID based on the route name in the filter. This is because // Edit the runtime configuration to add an envoy ID based on the route name in the filter. This is because
// routes are matched by envoyID and in the transparent proxy case, we only have the VIP set in the // routes are matched by envoyID and in the transparent proxy case, we only have the VIP set in the
// RuntimeConfig. // RuntimeConfig.
p.envoyID = httpConfig.GetRds().RouteConfigName v.envoyID = httpConfig.GetRds().RouteConfigName
emptyServiceKey := api.CompoundServiceName{} emptyServiceKey := api.CompoundServiceName{}
upstream, ok := config.Upstreams[emptyServiceKey] upstream, ok := p.RuntimeConfig.Upstreams[emptyServiceKey]
if ok { if ok {
upstream.EnvoyID = p.envoyID upstream.EnvoyID = v.envoyID
} }
return filter, true, nil return filter, true, nil
} }
@ -392,10 +395,10 @@ func (p *Validate) PatchFilter(config *extensioncommon.RuntimeConfig, filter *en
// FilterClusterNames handles the filter being an http or tcp filter. // FilterClusterNames handles the filter being an http or tcp filter.
for sni := range extensioncommon.FilterClusterNames(filter) { for sni := range extensioncommon.FilterClusterNames(filter) {
// Mark any clusters we see as required resources. // Mark any clusters we see as required resources.
if r, ok := p.resources[sni]; ok { if r, ok := v.resources[sni]; ok {
r.required = true r.required = true
} else { } else {
p.resources[sni] = &resource{required: true} v.resources[sni] = &resource{required: true}
} }
} }

View File

@ -349,7 +349,7 @@ func TestMakeValidate(t *testing.T) {
}, },
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
svc: { svc: {
SNI: tc.snis, SNIs: tc.snis,
}, },
}, },
} }