diff --git a/agent/envoyextensions/builtin/aws-lambda/aws_lambda.go b/agent/envoyextensions/builtin/aws-lambda/aws_lambda.go index cc7577f3fe..fa36d6fa50 100644 --- a/agent/envoyextensions/builtin/aws-lambda/aws_lambda.go +++ b/agent/envoyextensions/builtin/aws-lambda/aws_lambda.go @@ -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 // 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) { - if r.Kind != api.ServiceKindTerminatingGateway { - return route, false, nil +func (a *awsLambda) PatchRoute(p extensioncommon.RoutePayload) (*envoy_route_v3.RouteConfiguration, bool, error) { + cfg := p.RuntimeConfig + if cfg.Kind != api.ServiceKindTerminatingGateway { + return p.Message, false, nil } // Only patch outbound routes. - if extensioncommon.IsRouteToLocalAppCluster(route) { - return route, false, nil + if p.IsInbound() { + return p.Message, false, nil } + route := p.Message + for _, virtualHost := range route.VirtualHosts { for _, route := range virtualHost.Routes { 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 -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. - if extensioncommon.IsLocalAppCluster(c) { - return c, false, nil + if p.IsInbound() { + return p.Message, false, nil } transportSocket, err := extensioncommon.MakeUpstreamTLSTransportSocket(&envoy_tls_v3.UpstreamTlsContext{ Sni: "*.amazonaws.com", }) + c := p.Message + if err != nil { 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 // 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. - if isInboundListener { + if p.IsInbound() { return filter, false, nil } diff --git a/agent/envoyextensions/builtin/aws-lambda/aws_lambda_test.go b/agent/envoyextensions/builtin/aws-lambda/aws_lambda_test.go index af7b95fd8d..26f49eef4b 100644 --- a/agent/envoyextensions/builtin/aws-lambda/aws_lambda_test.go +++ b/agent/envoyextensions/builtin/aws-lambda/aws_lambda_test.go @@ -202,7 +202,10 @@ func TestPatchCluster(t *testing.T) { // Test patching the cluster 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 { assert.Error(t, err) assert.False(t, patchSuccess) @@ -307,7 +310,10 @@ func TestPatchRoute(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { 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.Equal(t, tc.expectRoute, r) require.Equal(t, tc.expectBool, ok) @@ -456,7 +462,14 @@ func TestPatchFilter(t *testing.T) { PayloadPassthrough: true, 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) if tc.expectErr == "" { require.NoError(t, err) diff --git a/agent/envoyextensions/builtin/ext-authz/structs.go b/agent/envoyextensions/builtin/ext-authz/structs.go index 746b2ca7cb..11b7f49453 100644 --- a/agent/envoyextensions/builtin/ext-authz/structs.go +++ b/agent/envoyextensions/builtin/ext-authz/structs.go @@ -601,7 +601,7 @@ func (t Target) clusterName(cfg *cmn.RuntimeConfig) (string, error) { for service, upstream := range cfg.Upstreams { if service == t.Service { - for sni := range upstream.SNI { + for sni := range upstream.SNIs { return sni, nil } } diff --git a/agent/envoyextensions/builtin/http/localratelimit/ratelimit.go b/agent/envoyextensions/builtin/http/localratelimit/ratelimit.go index 686a2390a8..e70a704aeb 100644 --- a/agent/envoyextensions/builtin/http/localratelimit/ratelimit.go +++ b/agent/envoyextensions/builtin/http/localratelimit/ratelimit.go @@ -102,10 +102,11 @@ func (p *ratelimit) CanApply(config *extensioncommon.RuntimeConfig) bool { // PatchFilter inserts a http local rate_limit filter at the head of // envoy.filters.network.http_connection_manager filters -func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, 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 // since the limit is aggregated from all downstream connections. - if !isInboundListener { + if !p.IsInbound() { return filter, false, nil } @@ -123,34 +124,34 @@ func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_l tokenBucket := envoy_type_v3.TokenBucket{} - if p.TokensPerFill != nil { + if r.TokensPerFill != nil { tokenBucket.TokensPerFill = &wrappers.UInt32Value{ - Value: uint32(*p.TokensPerFill), + Value: uint32(*r.TokensPerFill), } } - if p.MaxTokens != nil { - tokenBucket.MaxTokens = uint32(*p.MaxTokens) + if r.MaxTokens != nil { + tokenBucket.MaxTokens = uint32(*r.MaxTokens) } - if p.FillInterval != nil { - tokenBucket.FillInterval = durationpb.New(time.Duration(*p.FillInterval) * time.Second) + if r.FillInterval != nil { + tokenBucket.FillInterval = durationpb.New(time.Duration(*r.FillInterval) * time.Second) } var FilterEnabledDefault *envoy_core_v3.RuntimeFractionalPercent - if p.FilterEnabled != nil { + if r.FilterEnabled != nil { FilterEnabledDefault = &envoy_core_v3.RuntimeFractionalPercent{ DefaultValue: &envoy_type_v3.FractionalPercent{ - Numerator: *p.FilterEnabled, + Numerator: *r.FilterEnabled, Denominator: envoy_type_v3.FractionalPercent_HUNDRED, }, } } var FilterEnforcedDefault *envoy_core_v3.RuntimeFractionalPercent - if p.FilterEnforced != nil { + if r.FilterEnforced != nil { FilterEnforcedDefault = &envoy_core_v3.RuntimeFractionalPercent{ DefaultValue: &envoy_type_v3.FractionalPercent{ - Numerator: *p.FilterEnforced, + Numerator: *r.FilterEnforced, Denominator: envoy_type_v3.FractionalPercent_HUNDRED, }, } diff --git a/agent/envoyextensions/builtin/lua/lua.go b/agent/envoyextensions/builtin/lua/lua.go index 32092c5ce8..8293228a79 100644 --- a/agent/envoyextensions/builtin/lua/lua.go +++ b/agent/envoyextensions/builtin/lua/lua.go @@ -67,14 +67,16 @@ func (l *lua) CanApply(config *extensioncommon.RuntimeConfig) bool { 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") } // 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. - if !l.matchesListenerDirection(isInboundListener) { + if !l.matchesListenerDirection(p) { return filter, false, nil } diff --git a/agent/envoyextensions/builtin/property-override/property_override.go b/agent/envoyextensions/builtin/property-override/property_override.go index dcc8e5a3e5..6bdf9e7a39 100644 --- a/agent/envoyextensions/builtin/property-override/property_override.go +++ b/agent/envoyextensions/builtin/property-override/property_override.go @@ -2,7 +2,6 @@ package propertyoverride import ( "fmt" - "strings" 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" @@ -14,14 +13,10 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/envoyextensions/extensioncommon" - "github.com/hashicorp/consul/lib/maps" ) -type stringSet map[string]struct{} - type propertyOverride struct { extensioncommon.BasicExtensionAdapter - // Patches are an array of Patch operations to be applied to the target resource(s). Patches []Patch // 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 // or outbound resources. // 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. @@ -56,23 +86,13 @@ const ( ResourceTypeRoute ResourceType = "route" ) -var ResourceTypes = stringSet{ +var ResourceTypes = extensioncommon.StringSet{ string(ResourceTypeCluster): {}, string(ResourceTypeClusterLoadAssignment): {}, - string(ResourceTypeListener): {}, 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. type Op string @@ -81,10 +101,10 @@ const ( 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. -var validProxyTypes = stringSet{ +var validProxyTypes = extensioncommon.StringSet{ string(api.ServiceKindConnectProxy): struct{}{}, string(api.ServiceKindTerminatingGateway): struct{}{}, } @@ -139,27 +159,58 @@ type Patch struct { var _ extensioncommon.BasicExtension = (*propertyOverride)(nil) -func (c *stringSet) checkRequired(v, fieldName string) error { - if _, ok := (*c)[v]; !ok { - if v == "" { - return fmt.Errorf("field %s is required", fieldName) - } - return fmt.Errorf("invalid %s '%q'; supported values: %s", - fieldName, v, strings.Join(maps.SliceOfKeys(*c), ", ")) +func (f *ResourceFilter) isEmpty() bool { + if f == nil { + return true } - return nil + + if len(f.Services) > 0 { + return false + } + + if string(f.TrafficDirection) != "" { + return false + } + + if string(f.ResourceType) != "" { + return false + } + + return true } func (f *ResourceFilter) validate() error { - if f == nil || *f == (ResourceFilter{}) { + if f == nil || f.isEmpty() { 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 } - if err := TrafficDirections.checkRequired(string(f.TrafficDirection), "TrafficDirection"); err != nil { + if err := extensioncommon.TrafficDirections.CheckRequired(string(f.TrafficDirection), "TrafficDirection"); err != nil { 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 } @@ -169,7 +220,7 @@ func (p *Patch) validate(debug bool) error { return err } - if err := Ops.checkRequired(string(p.Op), "Op"); err != nil { + if err := Ops.CheckRequired(string(p.Op), "Op"); err != nil { 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) } @@ -243,57 +294,35 @@ func (p *propertyOverride) CanApply(config *extensioncommon.RuntimeConfig) bool } // 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) { - d := TrafficDirectionOutbound - if extensioncommon.IsRouteToLocalAppCluster(r) { - d = TrafficDirectionInbound - } - return patchResourceType[*envoy_route_v3.RouteConfiguration](r, p, ResourceTypeRoute, d, &defaultStructPatcher[*envoy_route_v3.RouteConfiguration]{}) +func (p *propertyOverride) PatchRoute(payload extensioncommon.RoutePayload) (*envoy_route_v3.RouteConfiguration, bool, error) { + return patchResourceType[*envoy_route_v3.RouteConfiguration](p, ResourceTypeRoute, payload, &defaultStructPatcher[*envoy_route_v3.RouteConfiguration]{}) } // 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) { - d := TrafficDirectionOutbound - if extensioncommon.IsLocalAppCluster(c) { - d = TrafficDirectionInbound - } - return patchResourceType[*envoy_cluster_v3.Cluster](c, p, ResourceTypeCluster, d, &defaultStructPatcher[*envoy_cluster_v3.Cluster]{}) +func (p *propertyOverride) PatchCluster(payload extensioncommon.ClusterPayload) (*envoy_cluster_v3.Cluster, bool, error) { + return patchResourceType[*envoy_cluster_v3.Cluster](p, ResourceTypeCluster, payload, &defaultStructPatcher[*envoy_cluster_v3.Cluster]{}) } // 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) { - d := TrafficDirectionOutbound - if extensioncommon.IsLocalAppClusterLoadAssignment(c) { - d = TrafficDirectionInbound - } - return patchResourceType[*envoy_endpoint_v3.ClusterLoadAssignment](c, p, ResourceTypeClusterLoadAssignment, d, &defaultStructPatcher[*envoy_endpoint_v3.ClusterLoadAssignment]{}) +func (p *propertyOverride) PatchClusterLoadAssignment(payload extensioncommon.ClusterLoadAssignmentPayload) (*envoy_endpoint_v3.ClusterLoadAssignment, bool, error) { + return patchResourceType[*envoy_endpoint_v3.ClusterLoadAssignment](p, ResourceTypeClusterLoadAssignment, payload, &defaultStructPatcher[*envoy_endpoint_v3.ClusterLoadAssignment]{}) } // 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) { - d := TrafficDirectionOutbound - 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 +func (p *propertyOverride) PatchListener(payload extensioncommon.ListenerPayload) (*envoy_listener_v3.Listener, bool, error) { + return patchResourceType[*envoy_listener_v3.Listener](p, ResourceTypeListener, payload, &defaultStructPatcher[*envoy_listener_v3.Listener]{}) } // 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. -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 var resultErr error + k := payload.Message + for _, patch := range p.Patches { - if patch.ResourceFilter.ResourceType != t { - continue - } - if patch.ResourceFilter.TrafficDirection != d { + if !matchesResourceFilter(patch.ResourceFilter, resourceType, payload) { continue } newK, err := patcher.applyPatch(k, patch, p.Debug) diff --git a/agent/envoyextensions/builtin/property-override/property_override_test.go b/agent/envoyextensions/builtin/property-override/property_override_test.go index 40200ca777..f613769aa3 100644 --- a/agent/envoyextensions/builtin/property-override/property_override_test.go +++ b/agent/envoyextensions/builtin/property-override/property_override_test.go @@ -2,11 +2,13 @@ package propertyoverride import ( "fmt" - routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" "strings" "testing" 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/envoyextensions/extensioncommon" "github.com/stretchr/testify/require" @@ -32,7 +34,7 @@ func TestConstructor(t *testing.T) { makeResourceFilter := func(overrides map[string]any) map[string]any { f := map[string]any{ "ResourceType": ResourceTypeRoute, - "TrafficDirection": TrafficDirectionOutbound, + "TrafficDirection": extensioncommon.TrafficDirectionOutbound, } return applyOverrides(f, overrides) } @@ -63,7 +65,7 @@ func TestConstructor(t *testing.T) { 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" if o != OpAdd { v = nil @@ -214,6 +216,19 @@ func TestConstructor(t *testing.T) { ok: false, 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 // with a "Patches" field decoded to the single "Patch" value contained in the // 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. "single value Patches decoded as map construction succeeds": { 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, }, "invalid ProxyType": { @@ -248,10 +263,10 @@ func TestConstructor(t *testing.T) { } for o := range Ops { - for d := range TrafficDirections { + for d := range extensioncommon.TrafficDirections { for t := range ResourceTypes { 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, } } - makePatchWithPath := func(t ResourceType, d TrafficDirection, p string) Patch { + makePatchWithPath := func(filter ResourceFilter, p string) Patch { return Patch{ - ResourceFilter: ResourceFilter{ - ResourceType: t, - TrafficDirection: d, - }, - Op: OpAdd, - Path: p, - Value: 1, + ResourceFilter: filter, + Op: OpAdd, + Path: p, + Value: 1, } } - makePatch := func(t ResourceType, d TrafficDirection) Patch { - return makePatchWithPath(t, d, "/foo") + makePatch := func(filter ResourceFilter) Patch { + return makePatchWithPath(filter, "/foo") } - clusterOutbound := makePatch(ResourceTypeCluster, TrafficDirectionOutbound) - clusterInbound := makePatch(ResourceTypeCluster, TrafficDirectionInbound) - routeOutbound := makePatch(ResourceTypeRoute, TrafficDirectionOutbound) - routeOutbound2 := makePatchWithPath(ResourceTypeRoute, TrafficDirectionOutbound, "/bar") - routeInbound := makePatch(ResourceTypeRoute, TrafficDirectionInbound) + svc1 := ServiceName{ + CompoundServiceName: api.CompoundServiceName{Name: "svc1"}, + } + svc2 := ServiceName{ + 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 { - d TrafficDirection - k proto.Message - p *propertyOverride - t ResourceType + resourceType ResourceType + payload extensioncommon.Payload[proto.Message] + p *propertyOverride } type testCase struct { args args @@ -329,79 +374,179 @@ func Test_patchResourceType(t *testing.T) { cases := map[string]testCase{ "outbound gets matching patch": { args: args{ - d: TrafficDirectionOutbound, - k: &clusterv3.Cluster{}, + resourceType: ResourceTypeCluster, + payload: extensioncommon.Payload[proto.Message]{ + TrafficDirection: extensioncommon.TrafficDirectionOutbound, + Message: &clusterv3.Cluster{}, + }, p: makeExtension(clusterOutbound), - t: ResourceTypeCluster, }, expectPatched: true, wantApplied: []Patch{clusterOutbound}, }, "inbound gets matching patch": { args: args{ - d: TrafficDirectionInbound, - k: &clusterv3.Cluster{}, + resourceType: ResourceTypeCluster, + payload: extensioncommon.Payload[proto.Message]{ + TrafficDirection: extensioncommon.TrafficDirectionInbound, + Message: &clusterv3.Cluster{}, + }, p: makeExtension(clusterInbound), - t: ResourceTypeCluster, }, expectPatched: true, wantApplied: []Patch{clusterInbound}, }, "multiple resources same direction only gets matching resource": { args: args{ - d: TrafficDirectionOutbound, - k: &clusterv3.Cluster{}, - p: makeExtension(clusterOutbound, routeOutbound), - t: ResourceTypeCluster, + resourceType: ResourceTypeCluster, + payload: extensioncommon.Payload[proto.Message]{ + TrafficDirection: extensioncommon.TrafficDirectionOutbound, + Message: &clusterv3.Cluster{}, + }, + p: makeExtension(clusterOutbound, listenerOutbound), }, expectPatched: true, wantApplied: []Patch{clusterOutbound}, }, "multiple directions same resource only gets matching direction": { args: args{ - d: TrafficDirectionOutbound, - k: &clusterv3.Cluster{}, + resourceType: ResourceTypeCluster, + payload: extensioncommon.Payload[proto.Message]{ + TrafficDirection: extensioncommon.TrafficDirectionOutbound, + Message: &clusterv3.Cluster{}, + }, p: makeExtension(clusterOutbound, clusterInbound), - t: ResourceTypeCluster, }, expectPatched: true, wantApplied: []Patch{clusterOutbound}, }, "multiple directions and resources only gets matching patch": { args: args{ - d: TrafficDirectionInbound, - k: &routev3.RouteConfiguration{}, - p: makeExtension(clusterOutbound, clusterInbound, routeOutbound, routeInbound), - t: ResourceTypeRoute, + resourceType: ResourceTypeRoute, + payload: extensioncommon.Payload[proto.Message]{ + TrafficDirection: extensioncommon.TrafficDirectionInbound, + Message: &routev3.RouteConfiguration{}, + }, + p: makeExtension(clusterOutbound, clusterInbound, listenerOutbound, listenerInbound, routeOutbound, routeOutbound2, routeInbound), }, expectPatched: true, wantApplied: []Patch{routeInbound}, }, "multiple directions and resources multiple matches gets all matching patches": { args: args{ - d: TrafficDirectionOutbound, - k: &routev3.RouteConfiguration{}, - p: makeExtension(clusterOutbound, clusterInbound, routeOutbound, routeInbound, routeOutbound2), - t: ResourceTypeRoute, + resourceType: ResourceTypeRoute, + payload: extensioncommon.Payload[proto.Message]{ + TrafficDirection: extensioncommon.TrafficDirectionOutbound, + Message: &routev3.RouteConfiguration{}, + }, + p: makeExtension(clusterOutbound, clusterInbound, listenerOutbound, listenerInbound, listenerOutbound2, routeOutbound, routeOutbound2, routeInbound), }, expectPatched: true, wantApplied: []Patch{routeOutbound, routeOutbound2}, }, "multiple directions and resources no matches gets no patches": { args: args{ - d: TrafficDirectionOutbound, - k: &routev3.RouteConfiguration{}, - p: makeExtension(clusterInbound, routeOutbound, routeInbound, routeOutbound2), - t: ResourceTypeCluster, + resourceType: ResourceTypeCluster, + payload: extensioncommon.Payload[proto.Message]{ + TrafficDirection: extensioncommon.TrafficDirectionOutbound, + Message: &clusterv3.Cluster{}, + }, + p: makeExtension(clusterInbound, listenerOutbound, listenerInbound, listenerOutbound2, routeInbound, routeOutbound), }, expectPatched: false, 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 { t.Run(n, func(t *testing.T) { 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.Equal(t, tc.expectPatched, patched) diff --git a/agent/envoyextensions/builtin/wasm/structs.go b/agent/envoyextensions/builtin/wasm/structs.go index 4a29035c15..012099ab62 100644 --- a/agent/envoyextensions/builtin/wasm/structs.go +++ b/agent/envoyextensions/builtin/wasm/structs.go @@ -195,7 +195,7 @@ func (p *pluginConfig) asyncDataSource(rtCfg *extensioncommon.RuntimeConfig) (*e clusterSNI := "" for service, upstream := range rtCfg.Upstreams { if service == remote.HttpURI.Service { - for sni := range upstream.SNI { + for sni := range upstream.SNIs { clusterSNI = sni break } diff --git a/agent/envoyextensions/builtin/wasm/wasm_test.go b/agent/envoyextensions/builtin/wasm/wasm_test.go index 1d431ce051..93f3a4b5e0 100644 --- a/agent/envoyextensions/builtin/wasm/wasm_test.go +++ b/agent/envoyextensions/builtin/wasm/wasm_test.go @@ -609,7 +609,7 @@ func makeTestRuntimeConfig(protocol string, enterprise bool) *extensioncommon.Ru Namespace: acl.NamespaceOrDefault(ns), Partition: acl.PartitionOrDefault(ap), }: { - SNI: map[string]struct{}{"test-file-server": {}}, + SNIs: map[string]struct{}{"test-file-server": {}}, EnvoyID: "test-file-server", }, }, diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index 569b6c7af4..a6f58a11f2 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -34,7 +34,6 @@ import ( const ( meshGatewayExportedClusterNamePrefix = "exported~" - failoverClusterNamePrefix = "failover-target~" ) // 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) if failover { - clusterName = failoverClusterNamePrefix + clusterName + clusterName = xdscommon.FailoverClusterNamePrefix + clusterName } if forMeshGateway { clusterName = meshGatewayExportedClusterNamePrefix + clusterName diff --git a/agent/xds/delta_envoy_extender_oss_test.go b/agent/xds/delta_envoy_extender_oss_test.go index e4525d91f7..ad7a67f7ee 100644 --- a/agent/xds/delta_envoy_extender_oss_test.go +++ b/agent/xds/delta_envoy_extender_oss_test.go @@ -26,6 +26,7 @@ import ( "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/xds/extensionruntime" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/envoyextensions/extensioncommon" "github.com/hashicorp/consul/envoyextensions/xdscommon" "github.com/hashicorp/consul/sdk/testutil" ) @@ -110,7 +111,7 @@ end`, { "ResourceFilter": map[string]interface{}{ "ResourceType": propertyoverride.ResourceTypeCluster, - "TrafficDirection": propertyoverride.TrafficDirectionOutbound, + "TrafficDirection": extensioncommon.TrafficDirectionOutbound, }, "Op": "add", "Path": "/outlier_detection/success_rate_minimum_hosts", @@ -125,7 +126,7 @@ end`, { "ResourceFilter": map[string]interface{}{ "ResourceType": propertyoverride.ResourceTypeCluster, - "TrafficDirection": propertyoverride.TrafficDirectionOutbound, + "TrafficDirection": extensioncommon.TrafficDirectionOutbound, }, "Op": "add", "Path": "/outlier_detection", @@ -143,7 +144,7 @@ end`, { "ResourceFilter": map[string]interface{}{ "ResourceType": propertyoverride.ResourceTypeCluster, - "TrafficDirection": propertyoverride.TrafficDirectionOutbound, + "TrafficDirection": extensioncommon.TrafficDirectionOutbound, }, "Op": "remove", "Path": "/outlier_detection", @@ -157,7 +158,7 @@ end`, { "ResourceFilter": map[string]interface{}{ "ResourceType": propertyoverride.ResourceTypeCluster, - "TrafficDirection": propertyoverride.TrafficDirectionOutbound, + "TrafficDirection": extensioncommon.TrafficDirectionOutbound, }, "Op": "add", "Path": "/upstream_connection_options/tcp_keepalive/keepalive_probes", @@ -172,7 +173,7 @@ end`, { "ResourceFilter": map[string]interface{}{ "ResourceType": propertyoverride.ResourceTypeCluster, - "TrafficDirection": propertyoverride.TrafficDirectionOutbound, + "TrafficDirection": extensioncommon.TrafficDirectionOutbound, }, "Op": "add", "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 { name string create func(t testinf.T) *proxycfg.ConfigSnapshot @@ -215,6 +379,54 @@ end`, 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", create: func(t testinf.T) *proxycfg.ConfigSnapshot { diff --git a/agent/xds/extensionruntime/runtime_config.go b/agent/xds/extensionruntime/runtime_config.go index 2c7522131e..110e3db1df 100644 --- a/agent/xds/extensionruntime/runtime_config.go +++ b/agent/xds/extensionruntime/runtime_config.go @@ -56,19 +56,27 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound // 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. - for uid, dc := range cfgSnap.ConnectProxy.DiscoveryChain { + for uid, chain := range cfgSnap.ConnectProxy.DiscoveryChain { 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] if !ok { outgoingKind = api.ServiceKindConnectProxy } upstreamMap[compoundServiceName] = &extensioncommon.UpstreamData{ - SNI: map[string]struct{}{sni: {}}, + PrimarySNI: primarySNI, + SNIs: snis, VIP: vipForService[compoundServiceName], EnvoyID: uid.EnvoyID(), OutgoingProxyKind: outgoingKind, @@ -101,10 +109,10 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound compoundServiceName := serviceNameToCompoundServiceName(svc) 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) - snis := map[string]struct{}{sni: {}} + snis := map[string]struct{}{primarySNI: {}} resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc] if hasResolver { @@ -115,7 +123,8 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound } upstreamMap[compoundServiceName] = &extensioncommon.UpstreamData{ - SNI: snis, + PrimarySNI: primarySNI, + SNIs: snis, EnvoyID: envoyID.EnvoyID(), OutgoingProxyKind: api.ServiceKindTerminatingGateway, } diff --git a/agent/xds/extensionruntime/runtime_config_oss_test.go b/agent/xds/extensionruntime/runtime_config_oss_test.go index 257fd5d737..c70a3f47f4 100644 --- a/agent/xds/extensionruntime/runtime_config_oss_test.go +++ b/agent/xds/extensionruntime/runtime_config_oss_test.go @@ -58,28 +58,32 @@ func TestGetRuntimeConfigurations_TerminatingGateway(t *testing.T) { IsSourcedFromUpstream: true, Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ 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": {}, }, EnvoyID: "api", OutgoingProxyKind: "terminating-gateway", }, 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": {}, }, EnvoyID: "cache", OutgoingProxyKind: "terminating-gateway", }, 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": {}, }, EnvoyID: "db", OutgoingProxyKind: "terminating-gateway", }, 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": {}, "canary2.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, } + 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. - 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. - 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. snapWebConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, func(ns *structs.NodeService) { ns.Proxy.EnvoyExtensions = envoyExtensions @@ -164,8 +183,10 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) { IsSourcedFromUpstream: true, Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ dbService: { - SNI: map[string]struct{}{ - "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, + 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-v2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, }, EnvoyID: "db", OutgoingProxyKind: "connect-proxy", @@ -193,8 +214,10 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) { IsSourcedFromUpstream: true, Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ dbService: { - SNI: map[string]struct{}{ - "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, + 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-v2.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, }, EnvoyID: "db", OutgoingProxyKind: "terminating-gateway", @@ -224,7 +247,8 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) { IsSourcedFromUpstream: false, Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ 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": {}, }, EnvoyID: "db", @@ -245,7 +269,8 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) { IsSourcedFromUpstream: false, Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ 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": {}, }, EnvoyID: "db", diff --git a/agent/xds/failover_policy.go b/agent/xds/failover_policy.go index ab2bb9a2b7..5edcae914d 100644 --- a/agent/xds/failover_policy.go +++ b/agent/xds/failover_policy.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/proxycfg" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/envoyextensions/xdscommon" ) type discoChainTargets struct { @@ -149,7 +150,7 @@ func (ft discoChainTargets) sequential() ([]discoChainTargetGroup, error) { var targetGroups []discoChainTargetGroup for i, t := range ft.targets { 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}, }) } diff --git a/agent/xds/testdata/builtin_extension/clusters/propertyoverride-patch-specific-upstream-service-failover.latest.golden b/agent/xds/testdata/builtin_extension/clusters/propertyoverride-patch-specific-upstream-service-failover.latest.golden new file mode 100644 index 0000000000..9e1f64a8c0 --- /dev/null +++ b/agent/xds/testdata/builtin_extension/clusters/propertyoverride-patch-specific-upstream-service-failover.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/clusters/propertyoverride-patch-specific-upstream-service-splitter.latest.golden b/agent/xds/testdata/builtin_extension/clusters/propertyoverride-patch-specific-upstream-service-splitter.latest.golden new file mode 100644 index 0000000000..2c2413ee3b --- /dev/null +++ b/agent/xds/testdata/builtin_extension/clusters/propertyoverride-patch-specific-upstream-service-splitter.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/clusters/propertyoverride-patch-specific-upstream-service.latest.golden b/agent/xds/testdata/builtin_extension/clusters/propertyoverride-patch-specific-upstream-service.latest.golden new file mode 100644 index 0000000000..2f37bfe304 --- /dev/null +++ b/agent/xds/testdata/builtin_extension/clusters/propertyoverride-patch-specific-upstream-service.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/endpoints/propertyoverride-patch-specific-upstream-service-failover.latest.golden b/agent/xds/testdata/builtin_extension/endpoints/propertyoverride-patch-specific-upstream-service-failover.latest.golden new file mode 100644 index 0000000000..ba96001dde --- /dev/null +++ b/agent/xds/testdata/builtin_extension/endpoints/propertyoverride-patch-specific-upstream-service-failover.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/endpoints/propertyoverride-patch-specific-upstream-service-splitter.latest.golden b/agent/xds/testdata/builtin_extension/endpoints/propertyoverride-patch-specific-upstream-service-splitter.latest.golden new file mode 100644 index 0000000000..98e3abe7e9 --- /dev/null +++ b/agent/xds/testdata/builtin_extension/endpoints/propertyoverride-patch-specific-upstream-service-splitter.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/endpoints/propertyoverride-patch-specific-upstream-service.latest.golden b/agent/xds/testdata/builtin_extension/endpoints/propertyoverride-patch-specific-upstream-service.latest.golden new file mode 100644 index 0000000000..49a84f09b2 --- /dev/null +++ b/agent/xds/testdata/builtin_extension/endpoints/propertyoverride-patch-specific-upstream-service.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/listeners/propertyoverride-patch-specific-upstream-service-failover.latest.golden b/agent/xds/testdata/builtin_extension/listeners/propertyoverride-patch-specific-upstream-service-failover.latest.golden new file mode 100644 index 0000000000..e8fc107d19 --- /dev/null +++ b/agent/xds/testdata/builtin_extension/listeners/propertyoverride-patch-specific-upstream-service-failover.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/listeners/propertyoverride-patch-specific-upstream-service-splitter.latest.golden b/agent/xds/testdata/builtin_extension/listeners/propertyoverride-patch-specific-upstream-service-splitter.latest.golden new file mode 100644 index 0000000000..b17de217f3 --- /dev/null +++ b/agent/xds/testdata/builtin_extension/listeners/propertyoverride-patch-specific-upstream-service-splitter.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/listeners/propertyoverride-patch-specific-upstream-service.latest.golden b/agent/xds/testdata/builtin_extension/listeners/propertyoverride-patch-specific-upstream-service.latest.golden new file mode 100644 index 0000000000..7a4514f1d7 --- /dev/null +++ b/agent/xds/testdata/builtin_extension/listeners/propertyoverride-patch-specific-upstream-service.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/routes/propertyoverride-patch-specific-upstream-service-failover.latest.golden b/agent/xds/testdata/builtin_extension/routes/propertyoverride-patch-specific-upstream-service-failover.latest.golden new file mode 100644 index 0000000000..3c51e370a4 --- /dev/null +++ b/agent/xds/testdata/builtin_extension/routes/propertyoverride-patch-specific-upstream-service-failover.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/routes/propertyoverride-patch-specific-upstream-service-splitter.latest.golden b/agent/xds/testdata/builtin_extension/routes/propertyoverride-patch-specific-upstream-service-splitter.latest.golden new file mode 100644 index 0000000000..0f6260527b --- /dev/null +++ b/agent/xds/testdata/builtin_extension/routes/propertyoverride-patch-specific-upstream-service-splitter.latest.golden @@ -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" +} \ No newline at end of file diff --git a/agent/xds/testdata/builtin_extension/routes/propertyoverride-patch-specific-upstream-service.latest.golden b/agent/xds/testdata/builtin_extension/routes/propertyoverride-patch-specific-upstream-service.latest.golden new file mode 100644 index 0000000000..095330c684 --- /dev/null +++ b/agent/xds/testdata/builtin_extension/routes/propertyoverride-patch-specific-upstream-service.latest.golden @@ -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" +} \ No newline at end of file diff --git a/envoyextensions/extensioncommon/basic_envoy_extender.go b/envoyextensions/extensioncommon/basic_envoy_extender.go index 5499d83045..53a44b0aa2 100644 --- a/envoyextensions/extensioncommon/basic_envoy_extender.go +++ b/envoyextensions/extensioncommon/basic_envoy_extender.go @@ -38,7 +38,7 @@ type BasicExtension interface { // PatchRoute patches a route to include the custom Envoy configuration // required to integrate with the built in extension template. // 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 // 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 // required to integrate with the built in extension template. // 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 // 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 // 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 // required to integrate with the built in extension template. // 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 // 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 // configuration required to integrate with the built in extension template. // 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 // 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) } 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 { 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, // since updates can come from either. 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 { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching load assignment for cluster %q: %w", nameOrSNI, err)) } else if patched { @@ -225,7 +225,7 @@ func (b *BasicEnvoyExtender) patchClusterLoadAssignments(config *RuntimeConfig, var resultErr error 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 { 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) } 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 { 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) } 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 { 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) { var resultErr error 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 } else { 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) { var resultErr error - inbound := IsInboundPublicListener(l) - 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 } else { 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 vip := config.Upstreams[config.ServiceName].VIP - inbound := IsInboundPublicListener(l) for idx, filterChain := range l.FilterChains { match := filterChainTProxyMatch(vip, filterChain) @@ -354,7 +351,7 @@ func (b *BasicEnvoyExtender) patchTProxyListenerFilterChains(config *RuntimeConf 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 } else { 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 } -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 - patchedFilters, err := b.Extension.PatchFilters(config, filterChain.Filters, isInboundListener) + inbound := IsInboundPublicListener(l) + patchedFilters, err := b.Extension.PatchFilters(config, filterChain.Filters, inbound) if err != nil { return filterChain, fmt.Errorf("error patching filters: %w", err) } 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 { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter: %w", err)) } diff --git a/envoyextensions/extensioncommon/basic_extension_adapter.go b/envoyextensions/extensioncommon/basic_extension_adapter.go index 85cc5efed8..fbf73f34c2 100644 --- a/envoyextensions/extensioncommon/basic_extension_adapter.go +++ b/envoyextensions/extensioncommon/basic_extension_adapter.go @@ -20,8 +20,8 @@ type BasicExtensionAdapter struct{} func (BasicExtensionAdapter) CanApply(_ *RuntimeConfig) bool { return false } // 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) { - return c, false, nil +func (BasicExtensionAdapter) PatchCluster(p ClusterPayload) (*envoy_cluster_v3.Cluster, bool, error) { + return p.Message, false, nil } // 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. -func (BasicExtensionAdapter) PatchClusterLoadAssignment(_ *RuntimeConfig, c *envoy_endpoint_v3.ClusterLoadAssignment) (*envoy_endpoint_v3.ClusterLoadAssignment, bool, error) { - return c, false, nil +func (BasicExtensionAdapter) PatchClusterLoadAssignment(p ClusterLoadAssignmentPayload) (*envoy_endpoint_v3.ClusterLoadAssignment, bool, error) { + return p.Message, false, nil } // 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) { - return l, false, nil +func (BasicExtensionAdapter) PatchListener(p ListenerPayload) (*envoy_listener_v3.Listener, bool, error) { + return p.Message, false, nil } // 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. -func (BasicExtensionAdapter) PatchFilter(_ *RuntimeConfig, f *envoy_listener_v3.Filter, _ bool) (*envoy_listener_v3.Filter, bool, error) { - return f, false, nil +func (BasicExtensionAdapter) PatchFilter(p FilterPayload) (*envoy_listener_v3.Filter, bool, error) { + return p.Message, false, nil } // 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. -func (BasicExtensionAdapter) PatchRoute(_ *RuntimeConfig, r *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) { - return r, false, nil +func (BasicExtensionAdapter) PatchRoute(p RoutePayload) (*envoy_route_v3.RouteConfiguration, bool, error) { + return p.Message, false, nil } // PatchRoutes provides a default implementation of the PatchRoutes interface that does nothing. diff --git a/envoyextensions/extensioncommon/resources.go b/envoyextensions/extensioncommon/resources.go index 8067568ff2..22c6c3ffa9 100644 --- a/envoyextensions/extensioncommon/resources.go +++ b/envoyextensions/extensioncommon/resources.go @@ -17,9 +17,11 @@ import ( 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_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/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. @@ -70,6 +72,206 @@ func MakeFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, erro }, 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 // returns the empty string. func GetListenerEnvoyID(l *envoy_listener_v3.Listener) string { diff --git a/envoyextensions/extensioncommon/runtime_config.go b/envoyextensions/extensioncommon/runtime_config.go index 2572d3f381..9317271e1e 100644 --- a/envoyextensions/extensioncommon/runtime_config.go +++ b/envoyextensions/extensioncommon/runtime_config.go @@ -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 // is used to choose which Envoy resources to patch. type UpstreamData struct { - // SNI is the SNI header used to reach an upstream service. - SNI map[string]struct{} + // This is the SNI for the upstream service without accounting for any discovery chain magic. + 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 or // when using a // non-default namespace or partition. @@ -68,7 +71,7 @@ type RuntimeConfig struct { // Only used when IsSourcedFromUpstream is true. func (c RuntimeConfig) MatchesUpstreamServiceSNI(sni string) bool { u := c.Upstreams[c.ServiceName] - _, match := u.SNI[sni] + _, match := u.SNIs[sni] return match } diff --git a/envoyextensions/extensioncommon/runtime_config_test.go b/envoyextensions/extensioncommon/runtime_config_test.go index a6cca3b64b..fa2ac8df5f 100644 --- a/envoyextensions/extensioncommon/runtime_config_test.go +++ b/envoyextensions/extensioncommon/runtime_config_test.go @@ -20,7 +20,7 @@ func makeTestRuntimeConfig() RuntimeConfig { sn: { EnvoyID: "eid", OutgoingProxyKind: api.ServiceKindTerminatingGateway, - SNI: map[string]struct{}{ + SNIs: map[string]struct{}{ "sni1": {}, "sni2": {}, }, diff --git a/envoyextensions/extensioncommon/upstream_envoy_extender.go b/envoyextensions/extensioncommon/upstream_envoy_extender.go index 96a0969d44..d3ca2faf6a 100644 --- a/envoyextensions/extensioncommon/upstream_envoy_extender.go +++ b/envoyextensions/extensioncommon/upstream_envoy_extender.go @@ -74,7 +74,7 @@ func (ext *UpstreamEnvoyExtender) Extend(resources *xdscommon.IndexedResources, continue } - newCluster, patched, err := ext.Extension.PatchCluster(config, resource) + newCluster, patched, err := ext.Extension.PatchCluster(config.GetClusterPayload(resource)) if err != nil { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster: %w", err)) continue @@ -101,7 +101,7 @@ func (ext *UpstreamEnvoyExtender) Extend(resources *xdscommon.IndexedResources, continue } - newRoute, patched, err := ext.Extension.PatchRoute(config, resource) + newRoute, patched, err := ext.Extension.PatchRoute(config.GetRoutePayload(resource)) if err != nil { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching route: %w", err)) continue @@ -146,7 +146,7 @@ func (ext *UpstreamEnvoyExtender) patchTerminatingGatewayListener(config *Runtim var filters []*envoy_listener_v3.Filter 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 { 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 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 { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err)) filters = append(filters, filter) @@ -225,7 +225,7 @@ func (ext *UpstreamEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l * } 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 { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err)) filters = append(filters, filter) diff --git a/envoyextensions/xdscommon/xdscommon.go b/envoyextensions/xdscommon/xdscommon.go index b91342f221..e4d293f8c9 100644 --- a/envoyextensions/xdscommon/xdscommon.go +++ b/envoyextensions/xdscommon/xdscommon.go @@ -52,6 +52,8 @@ const ( // SecretType is the TypeURL for Secret discovery responses. SecretType = apiTypePrefix + "envoy.extensions.transport_sockets.tls.v3.Secret" + + FailoverClusterNamePrefix = "failover-target~" ) type IndexedResources struct { diff --git a/troubleshoot/proxy/validateupstream.go b/troubleshoot/proxy/validateupstream.go index d54731a8d2..64be9d02e1 100644 --- a/troubleshoot/proxy/validateupstream.go +++ b/troubleshoot/proxy/validateupstream.go @@ -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 // 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. - SNI: snis, + SNIs: snis, EnvoyID: envoyID, }, }, diff --git a/troubleshoot/validate/validate.go b/troubleshoot/validate/validate.go index 2177eb304b..f7c6299f99 100644 --- a/troubleshoot/validate/validate.go +++ b/troubleshoot/validate/validate.go @@ -92,10 +92,10 @@ func MakeValidate(ext extensioncommon.RuntimeConfig) (extensioncommon.BasicExten upstream, ok := ext.Upstreams[ext.ServiceName] if ok { 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") } - snis = upstream.SNI + snis = upstream.SNIs } if mainEnvoyID == "" && vip == "" { return nil, fmt.Errorf("envoyID or virtual IP is required") @@ -306,84 +306,87 @@ func (p *Validate) CanApply(config *extensioncommon.RuntimeConfig) bool { 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 // the envoyID configured. - if route.Name != p.envoyID { + if route.Name != p.Upstream.EnvoyID { return route, false, nil } - p.route = true + v.route = true for sni := range extensioncommon.RouteClusterNames(route) { - if _, ok := p.resources[sni]; ok { + if _, ok := v.resources[sni]; ok { continue } - p.resources[sni] = &resource{required: true} + v.resources[sni] = &resource{required: true} } return route, false, nil } -func (p *Validate) PatchCluster(config *extensioncommon.RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) { - v, ok := p.resources[c.Name] +func (v *Validate) PatchCluster(p extensioncommon.ClusterPayload) (*envoy_cluster_v3.Cluster, bool, error) { + c := p.Message + val, ok := v.resources[c.Name] if !ok { - v = &resource{} - p.resources[c.Name] = v + val = &resource{} + 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. aggregateCluster, ok := isAggregateCluster(c) if ok { // 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 { - r, ok := p.resources[clusterName] + r, ok := v.resources[clusterName] if !ok { r = &resource{} - p.resources[clusterName] = r + v.resources[clusterName] = r } - if v.aggregateClusterChildren == nil { - v.aggregateClusterChildren = []string{} + if val.aggregateClusterChildren == nil { + val.aggregateClusterChildren = []string{} } // 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. r.parentCluster = c.Name // 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 } if c.EdsClusterConfig != nil { - v.usesEDS = true + val.usesEDS = true } else { la := c.LoadAssignment if la == 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 } -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. - p.listener = true + v.listener = true 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 // else we need to do with the filter. 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 // routes are matched by envoyID and in the transparent proxy case, we only have the VIP set in the // RuntimeConfig. - p.envoyID = httpConfig.GetRds().RouteConfigName + v.envoyID = httpConfig.GetRds().RouteConfigName emptyServiceKey := api.CompoundServiceName{} - upstream, ok := config.Upstreams[emptyServiceKey] + upstream, ok := p.RuntimeConfig.Upstreams[emptyServiceKey] if ok { - upstream.EnvoyID = p.envoyID + upstream.EnvoyID = v.envoyID } 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. for sni := range extensioncommon.FilterClusterNames(filter) { // 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 } else { - p.resources[sni] = &resource{required: true} + v.resources[sni] = &resource{required: true} } } diff --git a/troubleshoot/validate/validate_test.go b/troubleshoot/validate/validate_test.go index 6747d688e1..9d779c48f4 100644 --- a/troubleshoot/validate/validate_test.go +++ b/troubleshoot/validate/validate_test.go @@ -349,7 +349,7 @@ func TestMakeValidate(t *testing.T) { }, Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ svc: { - SNI: tc.snis, + SNIs: tc.snis, }, }, }