diff --git a/agent/proxycfg/testing_terminating_gateway.go b/agent/proxycfg/testing_terminating_gateway.go index 617cc2b53f..d2945a35f4 100644 --- a/agent/proxycfg/testing_terminating_gateway.go +++ b/agent/proxycfg/testing_terminating_gateway.go @@ -952,7 +952,7 @@ func TestConfigSnapshotTerminatingGatewayWithLambdaService(t testing.T, extraUpd ProxyConfig: map[string]interface{}{"protocol": "http"}, EnvoyExtensions: []structs.EnvoyExtension{ { - Name: "builtin/aws/lambda", + Name: structs.BuiltinAWSLambdaExtension, Arguments: map[string]interface{}{ "ARN": "lambda-arn", "PayloadPassthrough": true, diff --git a/agent/proxycfg/testing_upstreams.go b/agent/proxycfg/testing_upstreams.go index f35053cb43..ed2e65d8d6 100644 --- a/agent/proxycfg/testing_upstreams.go +++ b/agent/proxycfg/testing_upstreams.go @@ -25,6 +25,12 @@ func setupTestVariationConfigEntriesAndSnapshot( dbChain := setupTestVariationDiscoveryChain(t, variation, additionalEntries...) + nodes := TestUpstreamNodes(t, "db") + if variation == "register-to-terminating-gateway" { + for _, node := range nodes { + node.Service.Kind = structs.ServiceKindTerminatingGateway + } + } events := []UpdateEvent{ { CorrelationID: "discovery-chain:" + dbUID.String(), @@ -35,7 +41,7 @@ func setupTestVariationConfigEntriesAndSnapshot( { CorrelationID: "upstream-target:" + dbChain.ID() + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ - Nodes: TestUpstreamNodes(t, "db"), + Nodes: nodes, }, }, } @@ -207,6 +213,7 @@ func setupTestVariationConfigEntriesAndSnapshot( case "grpc-router": case "chain-and-router": case "lb-resolver": + case "register-to-terminating-gateway": default: t.Fatalf("unexpected variation: %q", variation) return nil @@ -229,6 +236,7 @@ func setupTestVariationDiscoveryChain( switch variation { case "default": // no config entries + case "register-to-terminating-gateway": case "simple-with-overrides": compileSetup = func(req *discoverychain.CompileRequest) { req.OverrideMeshGateway.Mode = structs.MeshGatewayModeLocal diff --git a/agent/structs/config_entry.go b/agent/structs/config_entry.go index 9139ae6367..1276f37001 100644 --- a/agent/structs/config_entry.go +++ b/agent/structs/config_entry.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/hashicorp/consul/api" "github.com/miekg/dns" "github.com/hashicorp/go-multierror" @@ -56,6 +57,10 @@ var AllConfigEntryKinds = []string{ ExportedServices, } +const ( + BuiltinAWSLambdaExtension string = "builtin/aws/lambda" +) + // ConfigEntry is the interface for centralized configuration stored in Raft. // Currently only service-defaults and proxy-defaults are supported. type ConfigEntry interface { @@ -293,10 +298,23 @@ type EnvoyExtension struct { Required bool Arguments map[string]interface{} } +type EnvoyExtensions []EnvoyExtension + +func (es EnvoyExtensions) ToAPI() []api.EnvoyExtension { + extensions := make([]api.EnvoyExtension, len(es)) + for i, e := range es { + extensions[i] = api.EnvoyExtension{ + Name: e.Name, + Required: e.Required, + Arguments: e.Arguments, + } + } + return extensions +} func builtInExtension(name string) bool { extensions := map[string]struct{}{ - "builtin/aws/lambda": {}, + BuiltinAWSLambdaExtension: {}, } _, ok := extensions[name] diff --git a/agent/structs/config_entry_test.go b/agent/structs/config_entry_test.go index f1dde1ef43..33cc9eb6f3 100644 --- a/agent/structs/config_entry_test.go +++ b/agent/structs/config_entry_test.go @@ -2732,7 +2732,7 @@ func TestServiceConfigEntry(t *testing.T) { Protocol: "http", EnvoyExtensions: []EnvoyExtension{ { - Name: "builtin/aws/lambda", + Name: BuiltinAWSLambdaExtension, }, }, }, diff --git a/agent/xds/delta.go b/agent/xds/delta.go index b90c10e0f3..2671eabb9d 100644 --- a/agent/xds/delta.go +++ b/agent/xds/delta.go @@ -251,9 +251,17 @@ func (s *Server) processDelta(stream ADSDeltaStream, reqCh <-chan *envoy_discove s.ResourceMapMutateFn(newResourceMap) } - newResourceMap, err = serverlessplugin.MutateIndexedResources(newResourceMap, xdscommon.MakePluginConfiguration(cfgSnap)) - if err != nil { - return status.Errorf(codes.Unavailable, "failed to patch xDS resources in the serverless plugin: %v", err) + cfgs := xdscommon.GetExtensionConfigurations(cfgSnap) + for _, extensions := range cfgs { + for _, ext := range extensions { + switch ext.EnvoyExtension.Name { + case structs.BuiltinAWSLambdaExtension: + newResourceMap, err = serverlessplugin.Extend(newResourceMap, ext) + if err != nil && ext.EnvoyExtension.Required { + return status.Errorf(codes.Unavailable, "failed to patch xDS resources in the serverless plugin: %v", err) + } + } + } } if err := populateChildIndexMap(newResourceMap); err != nil { diff --git a/agent/xds/serverless_plugin_oss_test.go b/agent/xds/serverless_plugin_oss_test.go index 96919553c9..e97d0ea10d 100644 --- a/agent/xds/serverless_plugin_oss_test.go +++ b/agent/xds/serverless_plugin_oss_test.go @@ -9,6 +9,7 @@ import ( "testing" envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" "github.com/golang/protobuf/proto" @@ -45,7 +46,7 @@ func TestServerlessPluginFromSnapshot(t *testing.T) { Protocol: "http", EnvoyExtensions: []structs.EnvoyExtension{ { - Name: "builtin/aws/lambda", + Name: structs.BuiltinAWSLambdaExtension, Arguments: map[string]interface{}{ "ARN": "lambda-arn", "PayloadPassthrough": payloadPassthrough, @@ -67,6 +68,13 @@ func TestServerlessPluginFromSnapshot(t *testing.T) { return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeServiceDefaults(false)) }, }, + // Make sure that if the upstream type is different from ExtensionConfiguration.Kind is, that the resources are not patched. + { + name: "lambda-connect-proxy-with-terminating-gateway-upstream", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", nil, nil, makeServiceDefaults(false)) + }, + }, { name: "lambda-connect-proxy-opposite-meta", create: func(t testinf.T) *proxycfg.ConfigSnapshot { @@ -107,8 +115,17 @@ func TestServerlessPluginFromSnapshot(t *testing.T) { require.NoError(t, err) indexedResources := indexResources(g.Logger, res) - newResourceMap, err := serverlessplugin.MutateIndexedResources(indexedResources, xdscommon.MakePluginConfiguration(snap)) - require.NoError(t, err) + var newResourceMap *xdscommon.IndexedResources + cfgs := xdscommon.GetExtensionConfigurations(snap) + for _, extensions := range cfgs { + for _, ext := range extensions { + switch ext.EnvoyExtension.Name { + case structs.BuiltinAWSLambdaExtension: + newResourceMap, err = serverlessplugin.Extend(indexedResources, ext) + require.NoError(t, err) + } + } + } entities := []struct { name string @@ -142,6 +159,15 @@ func TestServerlessPluginFromSnapshot(t *testing.T) { } }, }, + { + name: "endpoints", + key: xdscommon.EndpointType, + sorter: func(msgs []proto.Message) func(int, int) bool { + return func(i, j int) bool { + return msgs[i].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName < msgs[j].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName + } + }, + }, } for _, entity := range entities { diff --git a/agent/xds/serverlessplugin/lambda_patcher.go b/agent/xds/serverlessplugin/lambda_patcher.go index a3bc48cfba..743bfabbb4 100644 --- a/agent/xds/serverlessplugin/lambda_patcher.go +++ b/agent/xds/serverlessplugin/lambda_patcher.go @@ -14,9 +14,9 @@ import ( 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" pstruct "github.com/golang/protobuf/ptypes/struct" + "github.com/hashicorp/consul/agent/structs" "github.com/mitchellh/mapstructure" - "github.com/hashicorp/consul/agent/xds/xdscommon" "github.com/hashicorp/consul/api" ) @@ -30,22 +30,15 @@ type lambdaPatcher struct { var _ patcher = (*lambdaPatcher)(nil) -func makeLambdaPatcher(serviceConfig xdscommon.ServiceConfig) (patcher, bool) { +func makeLambdaPatcher(ext api.EnvoyExtension, upstreamKind api.ServiceKind) (patcher, bool) { var patcher lambdaPatcher - // TODO this is a hack. We should iterate through the extensions outside of here - if len(serviceConfig.EnvoyExtensions) == 0 { + if ext.Name != structs.BuiltinAWSLambdaExtension { return nil, false } - // TODO this is a hack. We should iterate through the extensions outside of here - extension := serviceConfig.EnvoyExtensions[0] - if extension.Name != "builtin/aws/lambda" { - return nil, false - } - - // TODO this fails when types aren't encode properly. We need to check this earlier in the Validate RPC. - err := mapstructure.Decode(extension.Arguments, &patcher) + // TODO this blows up if types aren't encode properly. We need to check this earlier in the Validate RPC. + err := mapstructure.Decode(ext.Arguments, &patcher) if err != nil { return nil, false } @@ -58,7 +51,7 @@ func makeLambdaPatcher(serviceConfig xdscommon.ServiceConfig) (patcher, bool) { return nil, false } - patcher.Kind = serviceConfig.Kind + patcher.Kind = upstreamKind return patcher, true } diff --git a/agent/xds/serverlessplugin/lambda_patcher_test.go b/agent/xds/serverlessplugin/lambda_patcher_test.go index 5ecbe87ad5..ff61ebbb91 100644 --- a/agent/xds/serverlessplugin/lambda_patcher_test.go +++ b/agent/xds/serverlessplugin/lambda_patcher_test.go @@ -3,9 +3,9 @@ package serverlessplugin import ( "testing" + "github.com/hashicorp/consul/agent/structs" "github.com/stretchr/testify/require" - "github.com/hashicorp/consul/agent/xds/xdscommon" "github.com/hashicorp/consul/api" ) @@ -50,21 +50,16 @@ func TestMakeLambdaPatcher(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - config := xdscommon.ServiceConfig{ - Kind: kind, - EnvoyExtensions: []api.EnvoyExtension{ - { - Name: "builtin/aws/lambda", - Arguments: map[string]interface{}{ - "ARN": tc.arn, - "Region": tc.region, - "PayloadPassthrough": tc.payloadPassthrough, - }, - }, + ext := api.EnvoyExtension{ + Name: structs.BuiltinAWSLambdaExtension, + Arguments: map[string]interface{}{ + "ARN": tc.arn, + "Region": tc.region, + "PayloadPassthrough": tc.payloadPassthrough, }, } - patcher, ok := makeLambdaPatcher(config) + patcher, ok := makeLambdaPatcher(ext, kind) require.Equal(t, tc.ok, ok) diff --git a/agent/xds/serverlessplugin/patcher.go b/agent/xds/serverlessplugin/patcher.go index 32398a4cc8..c68533ecb8 100644 --- a/agent/xds/serverlessplugin/patcher.go +++ b/agent/xds/serverlessplugin/patcher.go @@ -32,51 +32,9 @@ type patcher interface { type patchers map[api.CompoundServiceName]patcher -// getPatcherBySNI gets the patcher for the associated SNI. -func getPatcherBySNI(config xdscommon.PluginConfiguration, sni string) patcher { - serviceName, ok := config.SNIToServiceName[sni] - - if !ok { - return nil - } - - serviceConfig, ok := config.ServiceConfigs[serviceName] - if !ok { - return nil - } - - p := makePatcher(serviceConfig) - if p == nil || !p.CanPatch(config.Kind) { - return nil - } - - return p -} - -// getPatcherByEnvoyID gets the patcher for the associated envoy id. -func getPatcherByEnvoyID(config xdscommon.PluginConfiguration, envoyID string) patcher { - serviceName, ok := config.EnvoyIDToServiceName[envoyID] - - if !ok { - return nil - } - - serviceConfig, ok := config.ServiceConfigs[serviceName] - if !ok { - return nil - } - - p := makePatcher(serviceConfig) - if p == nil || !p.CanPatch(config.Kind) { - return nil - } - - return p -} - -func makePatcher(serviceConfig xdscommon.ServiceConfig) patcher { +func makePatcher(config xdscommon.ExtensionConfiguration) patcher { for _, constructor := range patchConstructors { - patcher, ok := constructor(serviceConfig) + patcher, ok := constructor(config.EnvoyExtension, config.OutgoingProxyKind()) if ok { return patcher } @@ -88,7 +46,7 @@ func makePatcher(serviceConfig xdscommon.ServiceConfig) patcher { // patchConstructor is used to construct patchers based on // xdscommon.ServiceConfig. This function contains all of the logic around // turning Meta data into the patcher. -type patchConstructor func(xdscommon.ServiceConfig) (patcher, bool) +type patchConstructor func(extension api.EnvoyExtension, upstreamKind api.ServiceKind) (patcher, bool) // patchConstructors contains all patchers that getPatchers tries to create. var patchConstructors = []patchConstructor{makeLambdaPatcher} diff --git a/agent/xds/serverlessplugin/patcher_test.go b/agent/xds/serverlessplugin/patcher_test.go deleted file mode 100644 index fb0da760a1..0000000000 --- a/agent/xds/serverlessplugin/patcher_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package serverlessplugin - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/xds/xdscommon" - "github.com/hashicorp/consul/api" -) - -func TestGetPatcherBySNI(t *testing.T) { - cases := []struct { - name string - sni string - kind api.ServiceKind - expected patcher - config *xdscommon.PluginConfiguration - }{ - { - name: "no sni match", - sni: "not-matching", - }, - { - name: "no patcher", - config: &xdscommon.PluginConfiguration{}, - sni: "lambda-sni", - }, - { - name: "no kind match", - kind: api.ServiceKindIngressGateway, - sni: "lambda-sni", - }, - { - name: "full match", - sni: "lambda-sni", - kind: api.ServiceKindTerminatingGateway, - expected: lambdaPatcher{ - ARN: "arn", - Region: "region", - PayloadPassthrough: false, - Kind: api.ServiceKindTerminatingGateway, - }, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - config := sampleConfig() - config.Kind = tc.kind - if tc.config != nil { - config = *tc.config - } - patcher := getPatcherBySNI(config, tc.sni) - - if tc.expected == nil { - require.Empty(t, patcher) - } else { - require.Equal(t, tc.expected, patcher) - } - }) - } -} - -var ( - lambdaService = api.CompoundServiceName{Name: "lambda"} - disabledLambdaService = api.CompoundServiceName{Name: "disabled-lambda"} - invalidLambdaService = api.CompoundServiceName{Name: "invalid-lambda"} -) - -func sampleConfig() xdscommon.PluginConfiguration { - return xdscommon.PluginConfiguration{ - Kind: api.ServiceKindTerminatingGateway, - ServiceConfigs: map[api.CompoundServiceName]xdscommon.ServiceConfig{ - lambdaService: { - Kind: api.ServiceKindTerminatingGateway, - EnvoyExtensions: []api.EnvoyExtension{ - { - Name: "builtin/aws/lambda", - Arguments: map[string]interface{}{ - "ARN": "arn", - "Region": "region", - }, - }, - }, - }, - disabledLambdaService: { - Kind: api.ServiceKindTerminatingGateway, - // No extension. - }, - invalidLambdaService: { - Kind: api.ServiceKindTerminatingGateway, - EnvoyExtensions: []api.EnvoyExtension{ - { - Name: "builtin/aws/lambda", - Arguments: map[string]interface{}{}, // ARN, etc missing - }, - }, - }, - }, - SNIToServiceName: map[string]api.CompoundServiceName{ - "lambda-sni": lambdaService, - }, - } -} diff --git a/agent/xds/serverlessplugin/serverlessplugin.go b/agent/xds/serverlessplugin/serverlessplugin.go index cb19649354..76cfe6ba64 100644 --- a/agent/xds/serverlessplugin/serverlessplugin.go +++ b/agent/xds/serverlessplugin/serverlessplugin.go @@ -14,19 +14,32 @@ import ( "github.com/hashicorp/consul/api" ) -// MutateIndexedResources updates indexed xDS structures to include patches for +// Extend updates indexed xDS structures to include patches for // serverless integrations. It is responsible for constructing all of the // patchers and forwarding xDS structs onto the appropriate patcher. If any // portion of this function fails, it will record the error and continue. The // behavior is appropriate since the unpatched xDS structures this receives are // typically invalid. -func MutateIndexedResources(resources *xdscommon.IndexedResources, config xdscommon.PluginConfiguration) (*xdscommon.IndexedResources, error) { +func Extend(resources *xdscommon.IndexedResources, config xdscommon.ExtensionConfiguration) (*xdscommon.IndexedResources, error) { var resultErr error switch config.Kind { case api.ServiceKindTerminatingGateway, api.ServiceKindConnectProxy: default: - return resources, resultErr + return resources, nil + } + + if !config.IsUpstream() { + return resources, nil + } + + patcher := makePatcher(config) + if patcher == nil { + return resources, nil + } + + if !patcher.CanPatch(config.Kind) { + return resources, nil } for _, indexType := range []string{ @@ -37,8 +50,7 @@ func MutateIndexedResources(resources *xdscommon.IndexedResources, config xdscom for nameOrSNI, msg := range resources.Index[indexType] { switch resource := msg.(type) { case *envoy_cluster_v3.Cluster: - patcher := getPatcherBySNI(config, nameOrSNI) - if patcher == nil { + if !config.MatchesUpstreamServiceSNI(nameOrSNI) { continue } @@ -52,7 +64,7 @@ func MutateIndexedResources(resources *xdscommon.IndexedResources, config xdscom } case *envoy_listener_v3.Listener: - newListener, patched, err := patchListener(config, resource) + newListener, patched, err := patchListener(config, resource, patcher) if err != nil { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener: %w", err)) continue @@ -62,8 +74,7 @@ func MutateIndexedResources(resources *xdscommon.IndexedResources, config xdscom } case *envoy_route_v3.RouteConfiguration: - patcher := getPatcherBySNI(config, nameOrSNI) - if patcher == nil { + if !config.MatchesUpstreamServiceSNI(nameOrSNI) { continue } @@ -85,17 +96,17 @@ func MutateIndexedResources(resources *xdscommon.IndexedResources, config xdscom return resources, resultErr } -func patchListener(config xdscommon.PluginConfiguration, l *envoy_listener_v3.Listener) (proto.Message, bool, error) { +func patchListener(config xdscommon.ExtensionConfiguration, l *envoy_listener_v3.Listener, p patcher) (proto.Message, bool, error) { switch config.Kind { case api.ServiceKindTerminatingGateway: - return patchTerminatingGatewayListener(config, l) + return patchTerminatingGatewayListener(config, l, p) case api.ServiceKindConnectProxy: - return patchConnectProxyListener(config, l) + return patchConnectProxyListener(config, l, p) } return l, false, nil } -func patchTerminatingGatewayListener(config xdscommon.PluginConfiguration, l *envoy_listener_v3.Listener) (proto.Message, bool, error) { +func patchTerminatingGatewayListener(config xdscommon.ExtensionConfiguration, l *envoy_listener_v3.Listener, p patcher) (proto.Message, bool, error) { var resultErr error patched := false for _, filterChain := range l.FilterChains { @@ -105,16 +116,14 @@ func patchTerminatingGatewayListener(config xdscommon.PluginConfiguration, l *en continue } - patcher := getPatcherBySNI(config, sni) - - if patcher == nil { + if !config.MatchesUpstreamServiceSNI(sni) { continue } var filters []*envoy_listener_v3.Filter for _, filter := range filterChain.Filters { - newFilter, ok, err := patcher.PatchFilter(filter) + newFilter, ok, err := p.PatchFilter(filter) if err != nil { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err)) @@ -131,7 +140,7 @@ func patchTerminatingGatewayListener(config xdscommon.PluginConfiguration, l *en return l, patched, resultErr } -func patchConnectProxyListener(config xdscommon.PluginConfiguration, l *envoy_listener_v3.Listener) (proto.Message, bool, error) { +func patchConnectProxyListener(config xdscommon.ExtensionConfiguration, l *envoy_listener_v3.Listener, p patcher) (proto.Message, bool, error) { var resultErr error envoyID := "" @@ -139,8 +148,7 @@ func patchConnectProxyListener(config xdscommon.PluginConfiguration, l *envoy_li envoyID = l.Name[:i] } - patcher := getPatcherByEnvoyID(config, envoyID) - if patcher == nil { + if envoyID != config.EnvoyID() { return l, false, nil } @@ -150,7 +158,7 @@ func patchConnectProxyListener(config xdscommon.PluginConfiguration, l *envoy_li var filters []*envoy_listener_v3.Filter for _, filter := range filterChain.Filters { - newFilter, ok, err := patcher.PatchFilter(filter) + newFilter, ok, err := p.PatchFilter(filter) if err != nil { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err)) filters = append(filters, filter) diff --git a/agent/xds/testdata/serverless_plugin/clusters/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden b/agent/xds/testdata/serverless_plugin/clusters/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden new file mode 100644 index 0000000000..2e3c9ea20e --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/clusters/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden @@ -0,0 +1,145 @@ +{ + "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", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "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": "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/serverless_plugin/endpoints/lambda-connect-proxy-opposite-meta.latest.golden b/agent/xds/testdata/serverless_plugin/endpoints/lambda-connect-proxy-opposite-meta.latest.golden new file mode 100644 index 0000000000..6e4d37bc32 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/endpoints/lambda-connect-proxy-opposite-meta.latest.golden @@ -0,0 +1,75 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.20.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/serverless_plugin/endpoints/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden b/agent/xds/testdata/serverless_plugin/endpoints/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden new file mode 100644 index 0000000000..6e4d37bc32 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/endpoints/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden @@ -0,0 +1,75 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.20.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/serverless_plugin/endpoints/lambda-connect-proxy.latest.golden b/agent/xds/testdata/serverless_plugin/endpoints/lambda-connect-proxy.latest.golden new file mode 100644 index 0000000000..6e4d37bc32 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/endpoints/lambda-connect-proxy.latest.golden @@ -0,0 +1,75 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.20.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/serverless_plugin/endpoints/lambda-terminating-gateway-with-service-resolvers.latest.golden b/agent/xds/testdata/serverless_plugin/endpoints/lambda-terminating-gateway-with-service-resolvers.latest.golden new file mode 100644 index 0000000000..ecb4f9edd1 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/endpoints/lambda-terminating-gateway-with-service-resolvers.latest.golden @@ -0,0 +1,109 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "canary1.web.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": "canary2.web.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": "web.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 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/serverless_plugin/endpoints/lambda-terminating-gateway.latest.golden b/agent/xds/testdata/serverless_plugin/endpoints/lambda-terminating-gateway.latest.golden new file mode 100644 index 0000000000..f33244f4e7 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/endpoints/lambda-terminating-gateway.latest.golden @@ -0,0 +1,41 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "web.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 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/serverless_plugin/listeners/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden b/agent/xds/testdata/serverless_plugin/listeners/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden new file mode 100644 index 0000000000..717877fcd7 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/listeners/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden @@ -0,0 +1,152 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "upstream.db.default.default.dc1", + "routeConfig": { + "name": "db", + "virtualHosts": [ + { + "name": "db.default.default.dc1", + "domains": [ + "*" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ] + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.prepared_query_geo-cache", + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": { + + }, + "statPrefix": "connect_authz" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "public_listener", + "cluster": "local_app" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": true + } + } + } + ], + "trafficDirection": "INBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/serverless_plugin/routes/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden b/agent/xds/testdata/serverless_plugin/routes/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden new file mode 100644 index 0000000000..9c050cbe6b --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/routes/lambda-connect-proxy-with-terminating-gateway-upstream.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/xdscommon/xdscommon.go b/agent/xds/xdscommon/xdscommon.go index b2e82cdb2b..0e6cce480b 100644 --- a/agent/xds/xdscommon/xdscommon.go +++ b/agent/xds/xdscommon/xdscommon.go @@ -60,35 +60,65 @@ type ServiceConfig struct { EnvoyExtensions []api.EnvoyExtension } -// PluginConfiguration is passed into Envoy plugins. It should depend on the -// API client rather than the structs package because the API client is meant -// to be public. -type PluginConfiguration struct { - // ServiceConfigs is a mapping from service names to the data Envoy plugins - // need to override the default Envoy configurations. - ServiceConfigs map[api.CompoundServiceName]ServiceConfig +// ExtensionConfiguration is the configuration for an extension attached to a service on the local proxy. Currently, it +// is only created for the local proxy's upstream service if the upstream service has an extension configured. In the +// future it will also include information about the service local to the local proxy as well. It should depend on the +// API client rather than the structs package because the API client is meant to be public. +type ExtensionConfiguration struct { + // EnvoyExtension is the extension that will patch Envoy resources. + EnvoyExtension api.EnvoyExtension - // SNIToServiceName is a mapping from SNIs to service names. This allows - // Envoy plugins to easily convert from an SNI Envoy resource name to the - // associated service's CompoundServiceName - SNIToServiceName map[string]api.CompoundServiceName + // ServiceName is the name of the service the EnvoyExtension is being applied to. It could be the local service or + // an upstream of the local service. + ServiceName api.CompoundServiceName - // EnvoyIDToServiceName is a mapping from EnvoyIDs to service names. This allows - // Envoy plugins to easily convert from an EnvoyID Envoy resource name to the - // associated service's CompoundServiceName - EnvoyIDToServiceName map[string]api.CompoundServiceName + // Upstreams will only be configured on the ExtensionConfiguration if the EnvoyExtension is being applied to an + // upstream. If there are no Upstreams, then EnvoyExtension is being applied to the local service's resources. + Upstreams map[api.CompoundServiceName]UpstreamData - // Kind is mode the local Envoy proxy is running in. For now, only + // Kind is mode the local Envoy proxy is running in. For now, only connect proxy and // terminating gateways are supported. Kind api.ServiceKind } -// MakePluginConfiguration generates the configuration that will be sent to -// Envoy plugins. -func MakePluginConfiguration(cfgSnap *proxycfg.ConfigSnapshot) PluginConfiguration { - serviceConfigs := make(map[api.CompoundServiceName]ServiceConfig) - sniMappings := make(map[string]api.CompoundServiceName) - envoyIDMappings := make(map[string]api.CompoundServiceName) +// 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{} + // EnvoyID is the envoy ID of an upstream service, structured or // when using a + // non-default namespace or partition. + EnvoyID string + // OutgoingProxyKind is the type of proxy of the upstream service. However, if the upstream is "typical" this will + // be set to "connect-proxy" instead. + OutgoingProxyKind api.ServiceKind +} + +func (ec ExtensionConfiguration) IsUpstream() bool { + _, ok := ec.Upstreams[ec.ServiceName] + return ok +} + +func (ec ExtensionConfiguration) MatchesUpstreamServiceSNI(sni string) bool { + u := ec.Upstreams[ec.ServiceName] + _, match := u.SNI[sni] + return match +} + +func (ec ExtensionConfiguration) EnvoyID() string { + u := ec.Upstreams[ec.ServiceName] + return u.EnvoyID +} + +func (ec ExtensionConfiguration) OutgoingProxyKind() api.ServiceKind { + u := ec.Upstreams[ec.ServiceName] + return u.OutgoingProxyKind +} + +func GetExtensionConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.CompoundServiceName][]ExtensionConfiguration { + extensionsMap := make(map[api.CompoundServiceName][]api.EnvoyExtension) + upstreamMap := make(map[api.CompoundServiceName]UpstreamData) + var kind api.ServiceKind trustDomain := "" if cfgSnap.Roots != nil { @@ -97,70 +127,91 @@ func MakePluginConfiguration(cfgSnap *proxycfg.ConfigSnapshot) PluginConfigurati switch cfgSnap.Kind { case structs.ServiceKindConnectProxy: - connectProxies := make(map[proxycfg.UpstreamID]struct{}) + kind = api.ServiceKindConnectProxy + outgoingKindByService := make(map[api.CompoundServiceName]api.ServiceKind) for uid, upstreamData := range cfgSnap.ConnectProxy.WatchedUpstreamEndpoints { + sn := upstreamIDToCompoundServiceName(uid) + for _, serviceNodes := range upstreamData { - // Lambdas and likely other integrations won't be attached to nodes. - // After agentless, we may need to reconsider this. - if len(serviceNodes) == 0 { - connectProxies[uid] = struct{}{} - } for _, serviceNode := range serviceNodes { - if serviceNode.Service.Kind == structs.ServiceKindTypical || serviceNode.Service.Kind == structs.ServiceKindConnectProxy { - connectProxies[uid] = struct{}{} + if serviceNode.Service == nil { + continue } + // Store the upstream's kind, and for ServiceKindTypical we don't do anything because we'll default + // any unset upstreams to ServiceKindConnectProxy below. + switch serviceNode.Service.Kind { + case structs.ServiceKindTypical: + default: + outgoingKindByService[sn] = api.ServiceKind(serviceNode.Service.Kind) + } + // We only need the kind from one instance, so break once we find it. + break } } } // 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 { - if _, ok := connectProxies[uid]; !ok { - continue - } - - serviceConfigs[upstreamIDToCompoundServiceName(uid)] = ServiceConfig{ - Kind: api.ServiceKindConnectProxy, - EnvoyExtensions: convertEnvoyExtensions(dc.EnvoyExtensions), - } - compoundServiceName := upstreamIDToCompoundServiceName(uid) + extensionsMap[compoundServiceName] = convertEnvoyExtensions(dc.EnvoyExtensions) + meta := uid.EnterpriseMeta sni := connect.ServiceSNI(uid.Name, "", meta.NamespaceOrDefault(), meta.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain) - sniMappings[sni] = compoundServiceName - envoyIDMappings[uid.EnvoyID()] = compoundServiceName - } - case structs.ServiceKindTerminatingGateway: - for svc, c := range cfgSnap.TerminatingGateway.ServiceConfigs { - compoundServiceName := serviceNameToCompoundServiceName(svc) - serviceConfigs[compoundServiceName] = ServiceConfig{ - EnvoyExtensions: convertEnvoyExtensions(c.EnvoyExtensions), - Kind: api.ServiceKindTerminatingGateway, + outgoingKind, ok := outgoingKindByService[compoundServiceName] + if !ok { + outgoingKind = api.ServiceKindConnectProxy } - sni := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain) - sniMappings[sni] = compoundServiceName + upstreamMap[compoundServiceName] = UpstreamData{ + SNI: map[string]struct{}{sni: {}}, + EnvoyID: uid.EnvoyID(), + OutgoingProxyKind: outgoingKind, + } + } + case structs.ServiceKindTerminatingGateway: + kind = api.ServiceKindTerminatingGateway + for svc, c := range cfgSnap.TerminatingGateway.ServiceConfigs { + compoundServiceName := serviceNameToCompoundServiceName(svc) + extensionsMap[compoundServiceName] = convertEnvoyExtensions(c.EnvoyExtensions) + sni := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain) envoyID := proxycfg.NewUpstreamIDFromServiceName(svc) - envoyIDMappings[envoyID.EnvoyID()] = compoundServiceName + + snis := map[string]struct{}{sni: {}} resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc] if hasResolver { for subsetName := range resolver.Subsets { sni := connect.ServiceSNI(svc.Name, subsetName, svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain) - sniMappings[sni] = compoundServiceName + snis[sni] = struct{}{} } } + + upstreamMap[compoundServiceName] = UpstreamData{ + SNI: snis, + EnvoyID: envoyID.EnvoyID(), + OutgoingProxyKind: api.ServiceKindTerminatingGateway, + } + } } - return PluginConfiguration{ - ServiceConfigs: serviceConfigs, - SNIToServiceName: sniMappings, - EnvoyIDToServiceName: envoyIDMappings, - Kind: api.ServiceKind(cfgSnap.Kind), + extensionConfigurationsMap := make(map[api.CompoundServiceName][]ExtensionConfiguration) + for svc, exts := range extensionsMap { + extensionConfigurationsMap[svc] = []ExtensionConfiguration{} + for _, ext := range exts { + extCfg := ExtensionConfiguration{ + EnvoyExtension: ext, + Kind: kind, + ServiceName: svc, + Upstreams: upstreamMap, + } + extensionConfigurationsMap[svc] = append(extensionConfigurationsMap[svc], extCfg) + } } + + return extensionConfigurationsMap } func serviceNameToCompoundServiceName(svc structs.ServiceName) api.CompoundServiceName { @@ -179,16 +230,6 @@ func upstreamIDToCompoundServiceName(uid proxycfg.UpstreamID) api.CompoundServic } } -func convertEnvoyExtensions(structExtensions []structs.EnvoyExtension) []api.EnvoyExtension { - var extensions []api.EnvoyExtension - - for _, e := range structExtensions { - extensions = append(extensions, api.EnvoyExtension{ - Name: e.Name, - Required: e.Required, - Arguments: e.Arguments, - }) - } - - return extensions +func convertEnvoyExtensions(structExtensions structs.EnvoyExtensions) []api.EnvoyExtension { + return structExtensions.ToAPI() } diff --git a/agent/xds/xdscommon/xdscommon_oss_test.go b/agent/xds/xdscommon/xdscommon_oss_test.go index d3d2afeaf1..01d0d56b39 100644 --- a/agent/xds/xdscommon/xdscommon_oss_test.go +++ b/agent/xds/xdscommon/xdscommon_oss_test.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/consul/api" ) -func TestMakePluginConfiguration_TerminatingGateway(t *testing.T) { +func TestGetExtensionConfigurations_TerminatingGateway(t *testing.T) { snap := proxycfg.TestConfigSnapshotTerminatingGatewayWithLambdaServiceAndServiceResolvers(t) webService := api.CompoundServiceName{ @@ -37,52 +37,62 @@ func TestMakePluginConfiguration_TerminatingGateway(t *testing.T) { Partition: "default", } - expected := PluginConfiguration{ - Kind: api.ServiceKindTerminatingGateway, - ServiceConfigs: map[api.CompoundServiceName]ServiceConfig{ - webService: { - Kind: api.ServiceKindTerminatingGateway, - EnvoyExtensions: []api.EnvoyExtension{ - { - Name: "builtin/aws/lambda", - Arguments: map[string]interface{}{ - "ARN": "lambda-arn", - "PayloadPassthrough": true, - "Region": "us-east-1", - }, + expected := map[api.CompoundServiceName][]ExtensionConfiguration{ + apiService: {}, + cacheService: {}, + dbService: {}, + webService: { + { + EnvoyExtension: api.EnvoyExtension{ + Name: structs.BuiltinAWSLambdaExtension, + Arguments: map[string]interface{}{ + "ARN": "lambda-arn", + "PayloadPassthrough": true, + "Region": "us-east-1", + }, + }, + ServiceName: webService, + Upstreams: map[api.CompoundServiceName]UpstreamData{ + apiService: { + SNI: map[string]struct{}{ + "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, + }, + EnvoyID: "api", + OutgoingProxyKind: "terminating-gateway", + }, + cacheService: { + SNI: map[string]struct{}{ + "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, + }, + EnvoyID: "cache", + OutgoingProxyKind: "terminating-gateway", + }, + dbService: { + SNI: map[string]struct{}{ + "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, + }, + EnvoyID: "db", + OutgoingProxyKind: "terminating-gateway", + }, + webService: { + SNI: 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": {}, + }, + EnvoyID: "web", + OutgoingProxyKind: "terminating-gateway", }, }, - }, - apiService: { Kind: api.ServiceKindTerminatingGateway, }, - cacheService: { - Kind: api.ServiceKindTerminatingGateway, - }, - dbService: { - Kind: api.ServiceKindTerminatingGateway, - }, - }, - SNIToServiceName: map[string]api.CompoundServiceName{ - "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": apiService, - "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": cacheService, - "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": dbService, - "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": webService, - "canary1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": webService, - "canary2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": webService, - }, - EnvoyIDToServiceName: map[string]api.CompoundServiceName{ - "web": webService, - "db": dbService, - "cache": cacheService, - "api": apiService, }, } - require.Equal(t, expected, MakePluginConfiguration(snap)) + require.Equal(t, expected, GetExtensionConfigurations(snap)) } -func TestMakePluginConfiguration_ConnectProxy(t *testing.T) { +func TestGetExtensionConfigurations_ConnectProxy(t *testing.T) { dbService := api.CompoundServiceName{ Name: "db", Partition: "default", @@ -90,7 +100,7 @@ func TestMakePluginConfiguration_ConnectProxy(t *testing.T) { } envoyExtensions := []structs.EnvoyExtension{ { - Name: "builtin/aws/lambda", + Name: structs.BuiltinAWSLambdaExtension, Arguments: map[string]interface{}{ "ARN": "lambda-arn", "PayloadPassthrough": true, @@ -106,22 +116,75 @@ func TestMakePluginConfiguration_ConnectProxy(t *testing.T) { EnvoyExtensions: envoyExtensions, } - snap := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, serviceDefaults) - expected := PluginConfiguration{ - Kind: api.ServiceKindConnectProxy, - ServiceConfigs: map[api.CompoundServiceName]ServiceConfig{ - dbService: { - Kind: api.ServiceKindConnectProxy, - EnvoyExtensions: convertEnvoyExtensions(envoyExtensions), + snapConnect := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, serviceDefaults) + snapTermGw := proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", nil, nil, serviceDefaults) + + type testCase struct { + snapshot *proxycfg.ConfigSnapshot + expected map[api.CompoundServiceName][]ExtensionConfiguration + } + cases := map[string]testCase{ + "connect proxy upstream": { + snapshot: snapConnect, + expected: map[api.CompoundServiceName][]ExtensionConfiguration{ + dbService: { + { + EnvoyExtension: api.EnvoyExtension{ + Name: structs.BuiltinAWSLambdaExtension, + Arguments: map[string]interface{}{ + "ARN": "lambda-arn", + "PayloadPassthrough": true, + "Region": "us-east-1", + }, + }, + ServiceName: dbService, + Upstreams: map[api.CompoundServiceName]UpstreamData{ + dbService: { + SNI: map[string]struct{}{ + "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, + }, + EnvoyID: "db", + OutgoingProxyKind: "connect-proxy", + }, + }, + Kind: api.ServiceKindConnectProxy, + }, + }, }, }, - SNIToServiceName: map[string]api.CompoundServiceName{ - "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": dbService, - }, - EnvoyIDToServiceName: map[string]api.CompoundServiceName{ - "db": dbService, + "terminating gateway upstream": { + snapshot: snapTermGw, + expected: map[api.CompoundServiceName][]ExtensionConfiguration{ + dbService: { + { + EnvoyExtension: api.EnvoyExtension{ + Name: structs.BuiltinAWSLambdaExtension, + Arguments: map[string]interface{}{ + "ARN": "lambda-arn", + "PayloadPassthrough": true, + "Region": "us-east-1", + }, + }, + ServiceName: dbService, + Upstreams: map[api.CompoundServiceName]UpstreamData{ + dbService: { + SNI: map[string]struct{}{ + "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {}, + }, + EnvoyID: "db", + OutgoingProxyKind: "terminating-gateway", + }, + }, + Kind: api.ServiceKindConnectProxy, + }, + }, + }, }, } - require.Equal(t, expected, MakePluginConfiguration(snap)) + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.expected, GetExtensionConfigurations(tc.snapshot)) + }) + } }