From c7204528c5a7f07c864a8a0ea6fd59301c408a90 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 31 Mar 2022 16:24:46 -0400 Subject: [PATCH] Implement Lambda Patching in the Serverless Plugin --- agent/proxycfg/testing_terminating_gateway.go | 10 +- agent/xds/serverless_plugin_oss_test.go | 107 +++++++ agent/xds/serverlessplugin/copied.go | 59 ++++ agent/xds/serverlessplugin/lambda_patcher.go | 170 +++++++++++ .../serverlessplugin/lambda_patcher_test.go | 83 ++++++ agent/xds/serverlessplugin/patcher.go | 81 ++++++ agent/xds/serverlessplugin/patcher_test.go | 100 +++++++ .../xds/serverlessplugin/serverlessplugin.go | 115 +++++++- ...da-terminating-gateway.envoy-1-20-x.golden | 169 +++++++++++ ...da-terminating-gateway.envoy-1-20-x.golden | 272 ++++++++++++++++++ agent/xds/xdscommon/xdscommon.go | 3 +- agent/xds/xdscommon/xdscommon_oss_test.go | 9 +- 12 files changed, 1172 insertions(+), 6 deletions(-) create mode 100644 agent/xds/serverless_plugin_oss_test.go create mode 100644 agent/xds/serverlessplugin/copied.go create mode 100644 agent/xds/serverlessplugin/lambda_patcher.go create mode 100644 agent/xds/serverlessplugin/lambda_patcher_test.go create mode 100644 agent/xds/serverlessplugin/patcher.go create mode 100644 agent/xds/serverlessplugin/patcher_test.go create mode 100644 agent/xds/testdata/serverless_plugin/clusters/lambda-terminating-gateway.envoy-1-20-x.golden create mode 100644 agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway.envoy-1-20-x.golden diff --git a/agent/proxycfg/testing_terminating_gateway.go b/agent/proxycfg/testing_terminating_gateway.go index 5b9889c85..e66ef3399 100644 --- a/agent/proxycfg/testing_terminating_gateway.go +++ b/agent/proxycfg/testing_terminating_gateway.go @@ -642,13 +642,19 @@ func TestConfigSnapshotTerminatingGatewayIgnoreExtraResolvers(t testing.T) *Conf }) } -func TestConfigSnapshotTerminatingGatewayWithServiceDefaultsMeta(t testing.T) *ConfigSnapshot { +func TestConfigSnapshotTerminatingGatewayWithLambdaService(t testing.T) *ConfigSnapshot { web := structs.NewServiceName("web", nil) return TestConfigSnapshotTerminatingGateway(t, true, nil, []agentcache.UpdateEvent{ { CorrelationID: serviceConfigIDPrefix + web.String(), Result: &structs.ServiceConfigResponse{ - Meta: map[string]string{"a": "b"}, + ProxyConfig: map[string]interface{}{"protocol": "http"}, + Meta: map[string]string{ + "serverless.consul.hashicorp.com/v1alpha1/lambda/enabled": "true", + "serverless.consul.hashicorp.com/v1alpha1/lambda/arn": "lambda-arn", + "serverless.consul.hashicorp.com/v1alpha1/lambda/payload-passthrough": "true", + "serverless.consul.hashicorp.com/v1alpha1/lambda/region": "us-east-1", + }, }, }, }) diff --git a/agent/xds/serverless_plugin_oss_test.go b/agent/xds/serverless_plugin_oss_test.go new file mode 100644 index 000000000..20c9a4d2d --- /dev/null +++ b/agent/xds/serverless_plugin_oss_test.go @@ -0,0 +1,107 @@ +//go:build !consulent +// +build !consulent + +package xds + +import ( + "path/filepath" + "sort" + "testing" + + envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + "github.com/golang/protobuf/proto" + testinf "github.com/mitchellh/go-testing-interface" + "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul/agent/proxycfg" + "github.com/hashicorp/consul/agent/xds/proxysupport" + "github.com/hashicorp/consul/agent/xds/serverlessplugin" + "github.com/hashicorp/consul/agent/xds/xdscommon" + "github.com/hashicorp/consul/sdk/testutil" +) + +func TestServerlessPluginFromSnapshot(t *testing.T) { + tests := []struct { + name string + create func(t testinf.T) *proxycfg.ConfigSnapshot + }{ + { + name: "lambda-terminating-gateway", + create: proxycfg.TestConfigSnapshotTerminatingGatewayWithLambdaService, + }, + } + + latestEnvoyVersion := proxysupport.EnvoyVersions[0] + for _, envoyVersion := range proxysupport.EnvoyVersions { + sf, err := determineSupportedProxyFeaturesFromString(envoyVersion) + require.NoError(t, err) + t.Run("envoy-"+envoyVersion, func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Sanity check default with no overrides first + snap := tt.create(t) + + // We need to replace the TLS certs with deterministic ones to make golden + // files workable. Note we don't update these otherwise they'd change + // golden files for every test case and so not be any use! + setupTLSRootsAndLeaf(t, snap) + + g := newResourceGenerator(testutil.Logger(t), nil, nil, false) + g.ProxyFeatures = sf + + res, err := g.allResourcesFromSnapshot(snap) + require.NoError(t, err) + + indexedResources := indexResources(g.Logger, res) + newResourceMap, err := serverlessplugin.MutateIndexedResources(indexedResources, xdscommon.MakePluginConfiguration(snap)) + require.NoError(t, err) + + entities := []struct { + name string + key string + sorter func([]proto.Message) func(int, int) bool + }{ + { + name: "clusters", + key: xdscommon.ClusterType, + sorter: func(msgs []proto.Message) func(int, int) bool { + return func(i, j int) bool { + return msgs[i].(*envoy_cluster_v3.Cluster).Name < msgs[j].(*envoy_cluster_v3.Cluster).Name + } + }, + }, + { + name: "listeners", + key: xdscommon.ListenerType, + sorter: func(msgs []proto.Message) func(int, int) bool { + return func(i, j int) bool { + return msgs[i].(*envoy_listener_v3.Listener).Name < msgs[j].(*envoy_listener_v3.Listener).Name + } + }, + }, + } + + for _, entity := range entities { + var msgs []proto.Message + for _, e := range newResourceMap.Index[entity.key] { + msgs = append(msgs, e) + } + + sort.Slice(msgs, entity.sorter(msgs)) + r, err := createResponse(entity.key, "00000001", "00000001", msgs) + require.NoError(t, err) + + t.Run(entity.name, func(t *testing.T) { + gotJSON := protoToJSON(t, r) + + require.JSONEq(t, goldenEnvoy(t, + filepath.Join("serverless_plugin", entity.name, tt.name), + envoyVersion, latestEnvoyVersion, gotJSON), gotJSON) + }) + } + }) + } + }) + } +} diff --git a/agent/xds/serverlessplugin/copied.go b/agent/xds/serverlessplugin/copied.go new file mode 100644 index 000000000..3e99038aa --- /dev/null +++ b/agent/xds/serverlessplugin/copied.go @@ -0,0 +1,59 @@ +package serverlessplugin + +import ( + envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + + "github.com/golang/protobuf/ptypes" + + "github.com/golang/protobuf/proto" +) + +// This is copied from xds and not put into the shared package because I'm not +// convinced it should be shared. + +func makeUpstreamTLSTransportSocket(tlsContext *envoy_tls_v3.UpstreamTlsContext) (*envoy_core_v3.TransportSocket, error) { + if tlsContext == nil { + return nil, nil + } + return makeTransportSocket("tls", tlsContext) +} + +func makeTransportSocket(name string, config proto.Message) (*envoy_core_v3.TransportSocket, error) { + any, err := ptypes.MarshalAny(config) + if err != nil { + return nil, err + } + return &envoy_core_v3.TransportSocket{ + Name: name, + ConfigType: &envoy_core_v3.TransportSocket_TypedConfig{ + TypedConfig: any, + }, + }, nil +} + +func makeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFilter, error) { + any, err := ptypes.MarshalAny(cfg) + if err != nil { + return nil, err + } + + return &envoy_http_v3.HttpFilter{ + Name: name, + ConfigType: &envoy_http_v3.HttpFilter_TypedConfig{TypedConfig: any}, + }, nil +} + +func makeFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, error) { + any, err := ptypes.MarshalAny(cfg) + if err != nil { + return nil, err + } + + return &envoy_listener_v3.Filter{ + Name: name, + ConfigType: &envoy_listener_v3.Filter_TypedConfig{TypedConfig: any}, + }, nil +} diff --git a/agent/xds/serverlessplugin/lambda_patcher.go b/agent/xds/serverlessplugin/lambda_patcher.go new file mode 100644 index 000000000..5121a36df --- /dev/null +++ b/agent/xds/serverlessplugin/lambda_patcher.go @@ -0,0 +1,170 @@ +package serverlessplugin + +import ( + "errors" + "fmt" + + envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_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_lambda_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/aws_lambda/v3" + envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" + pstruct "github.com/golang/protobuf/ptypes/struct" + + "github.com/hashicorp/consul/agent/xds/xdscommon" + "github.com/hashicorp/consul/api" +) + +const ( + lambdaPrefix string = "serverless.consul.hashicorp.com/v1alpha1" + lambdaEnabledTag string = lambdaPrefix + "/lambda/enabled" + lambdaArnTag string = lambdaPrefix + "/lambda/arn" + lambdaPayloadPassthroughTag string = lambdaPrefix + "/lambda/payload-passhthrough" + lambdaRegionTag string = lambdaPrefix + "/lambda/region" + lambdaInvocationMode string = lambdaPrefix + "/lambda/invocation-mode" +) + +type lambdaPatcher struct { + arn string + payloadPassthrough bool + region string + kind api.ServiceKind + invocationMode envoy_lambda_v3.Config_InvocationMode +} + +var _ patcher = (*lambdaPatcher)(nil) + +func makeLambdaPatcher(serviceConfig xdscommon.ServiceConfig) (patcher, bool) { + var patcher lambdaPatcher + if !isStringTrue(serviceConfig.Meta[lambdaEnabledTag]) { + return patcher, true + } + + arn := serviceConfig.Meta[lambdaArnTag] + if arn == "" { + return patcher, false + } + + region := serviceConfig.Meta[lambdaRegionTag] + if region == "" { + return patcher, false + } + + payloadPassthrough := isStringTrue(serviceConfig.Meta[lambdaPayloadPassthroughTag]) + + invocationModeStr := serviceConfig.Meta[lambdaInvocationMode] + invocationMode := envoy_lambda_v3.Config_SYNCHRONOUS + if invocationModeStr == "asynchronous" { + invocationMode = envoy_lambda_v3.Config_ASYNCHRONOUS + } + + return lambdaPatcher{ + arn: arn, + payloadPassthrough: payloadPassthrough, + region: region, + kind: serviceConfig.Kind, + invocationMode: invocationMode, + }, true +} + +func isStringTrue(v string) bool { + return v == "true" +} + +func (p lambdaPatcher) CanPatch(kind api.ServiceKind) bool { + return kind == p.kind +} + +func (p lambdaPatcher) PatchCluster(c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) { + transportSocket, err := makeUpstreamTLSTransportSocket(&envoy_tls_v3.UpstreamTlsContext{ + Sni: "*.amazonaws.com", + }) + + if err != nil { + return c, false, fmt.Errorf("failed to make transport socket: %w", err) + } + + cluster := &envoy_cluster_v3.Cluster{ + Name: c.Name, + ConnectTimeout: c.ConnectTimeout, + ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{Type: envoy_cluster_v3.Cluster_LOGICAL_DNS}, + DnsLookupFamily: envoy_cluster_v3.Cluster_V4_ONLY, + LbPolicy: envoy_cluster_v3.Cluster_ROUND_ROBIN, + Metadata: &envoy_core_v3.Metadata{ + FilterMetadata: map[string]*pstruct.Struct{ + "com.amazonaws.lambda": { + Fields: map[string]*pstruct.Value{ + "egress_gateway": {Kind: &pstruct.Value_BoolValue{BoolValue: true}}, + }, + }, + }, + }, + LoadAssignment: &envoy_endpoint_v3.ClusterLoadAssignment{ + ClusterName: c.Name, + Endpoints: []*envoy_endpoint_v3.LocalityLbEndpoints{ + { + LbEndpoints: []*envoy_endpoint_v3.LbEndpoint{ + { + HostIdentifier: &envoy_endpoint_v3.LbEndpoint_Endpoint{ + Endpoint: &envoy_endpoint_v3.Endpoint{ + Address: &envoy_core_v3.Address{ + Address: &envoy_core_v3.Address_SocketAddress{ + SocketAddress: &envoy_core_v3.SocketAddress{ + Address: fmt.Sprintf("lambda.%s.amazonaws.com", p.region), + PortSpecifier: &envoy_core_v3.SocketAddress_PortValue{ + PortValue: 443, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + TransportSocket: transportSocket, + } + return cluster, true, nil +} + +func (p lambdaPatcher) PatchFilter(filter *envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error) { + if filter.Name != "envoy.filters.network.http_connection_manager" { + return filter, false, nil + } + if typedConfig := filter.GetTypedConfig(); typedConfig == nil { + return filter, false, errors.New("error getting typed config for http filter") + } + + config := envoy_resource_v3.GetHTTPConnectionManager(filter) + if config == nil { + return filter, false, errors.New("error unmarshalling filter") + } + httpFilter, err := makeEnvoyHTTPFilter( + "envoy.filters.http.aws_lambda", + &envoy_lambda_v3.Config{ + Arn: p.arn, + PayloadPassthrough: p.payloadPassthrough, + InvocationMode: p.invocationMode, + }, + ) + if err != nil { + return filter, false, err + } + + config.HttpFilters = []*envoy_http_v3.HttpFilter{ + httpFilter, + {Name: "envoy.filters.http.router"}, + } + config.StripMatchingHostPort = true + newFilter, err := makeFilter("envoy.filters.network.http_connection_manager", config) + if err != nil { + return filter, false, errors.New("error making new filter") + } + + return newFilter, true, nil +} diff --git a/agent/xds/serverlessplugin/lambda_patcher_test.go b/agent/xds/serverlessplugin/lambda_patcher_test.go new file mode 100644 index 000000000..09ac0cc36 --- /dev/null +++ b/agent/xds/serverlessplugin/lambda_patcher_test.go @@ -0,0 +1,83 @@ +package serverlessplugin + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul/agent/xds/xdscommon" + "github.com/hashicorp/consul/api" +) + +func TestMakeLambdaPatcher(t *testing.T) { + kind := api.ServiceKindTerminatingGateway + cases := []struct { + name string + enabled bool + arn string + payloadPassthrough bool + region string + expected lambdaPatcher + ok bool + }{ + { + name: "no meta", + ok: true, + }, + { + name: "lambda disabled", + enabled: false, + ok: true, + }, + { + name: "missing arn", + enabled: true, + region: "blah", + ok: false, + }, + { + name: "missing region", + enabled: true, + region: "arn", + ok: false, + }, + { + name: "including payload passthrough", + enabled: true, + arn: "arn", + region: "blah", + payloadPassthrough: true, + expected: lambdaPatcher{ + arn: "arn", + payloadPassthrough: true, + region: "blah", + kind: kind, + }, + ok: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + config := xdscommon.ServiceConfig{ + Kind: kind, + Meta: map[string]string{ + lambdaEnabledTag: strconv.FormatBool(tc.enabled), + lambdaArnTag: tc.arn, + lambdaRegionTag: tc.region, + }, + } + + if tc.payloadPassthrough { + config.Meta[lambdaPayloadPassthroughTag] = strconv.FormatBool(tc.payloadPassthrough) + } + + patcher, ok := makeLambdaPatcher(config) + + require.Equal(t, tc.ok, ok) + + require.Equal(t, tc.expected, patcher) + }) + } +} diff --git a/agent/xds/serverlessplugin/patcher.go b/agent/xds/serverlessplugin/patcher.go new file mode 100644 index 000000000..58847bcb6 --- /dev/null +++ b/agent/xds/serverlessplugin/patcher.go @@ -0,0 +1,81 @@ +package serverlessplugin + +import ( + envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + + "github.com/hashicorp/consul/agent/xds/xdscommon" + "github.com/hashicorp/consul/api" +) + +// patcher is the interface that each serverless integration must implement. It +// is responsible for modifying the xDS structures based on only the state of +// the patcher. +type patcher interface { + // CanPatch determines if the patcher can mutate resources for the given api.ServiceKind + CanPatch(api.ServiceKind) bool + + // PatchCluster patches a cluster to include the custom Envoy configuration + // required to integrate with the serverless integration. + PatchCluster(*envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) + + // PatchFilter patches an Envoy filter to include the custom Envoy + // configuration required to integrate with the serverless integration. + PatchFilter(*envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error) +} + +type patchers map[api.CompoundServiceName]patcher + +func getPatcher(patchers patchers, kind api.ServiceKind, name api.CompoundServiceName) patcher { + patcher, ok := patchers[name] + + if !ok { + return nil + } + + if !patcher.CanPatch(kind) { + return nil + } + + return patcher +} + +// getPatcherBySNI gets the patcher for the associated SNI. +func getPatcherBySNI(config xdscommon.PluginConfiguration, kind api.ServiceKind, 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(kind) { + return nil + } + + return p +} + +func makePatcher(serviceConfig xdscommon.ServiceConfig) patcher { + for _, constructor := range patchConstructors { + patcher, ok := constructor(serviceConfig) + if ok { + return patcher + } + } + + return nil +} + +// 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) + +// 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 new file mode 100644 index 000000000..456cc0cd4 --- /dev/null +++ b/agent/xds/serverlessplugin/patcher_test.go @@ -0,0 +1,100 @@ +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() + if tc.config != nil { + config = *tc.config + } + patcher := getPatcherBySNI(config, tc.kind, 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{ + ServiceConfigs: map[api.CompoundServiceName]xdscommon.ServiceConfig{ + lambdaService: { + Kind: api.ServiceKindTerminatingGateway, + Meta: map[string]string{ + lambdaEnabledTag: "true", + lambdaArnTag: "arn", + lambdaRegionTag: "region", + }, + }, + disabledLambdaService: { + Kind: api.ServiceKindTerminatingGateway, + Meta: map[string]string{ + lambdaEnabledTag: "false", + lambdaArnTag: "arn", + lambdaRegionTag: "region", + }, + }, + invalidLambdaService: { + Kind: api.ServiceKindTerminatingGateway, + Meta: map[string]string{ + lambdaEnabledTag: "true", + }, + }, + }, + SNIToServiceName: map[string]api.CompoundServiceName{ + "lambda-sni": lambdaService, + }, + } +} diff --git a/agent/xds/serverlessplugin/serverlessplugin.go b/agent/xds/serverlessplugin/serverlessplugin.go index 8ad6c6273..8ab38e374 100644 --- a/agent/xds/serverlessplugin/serverlessplugin.go +++ b/agent/xds/serverlessplugin/serverlessplugin.go @@ -1,9 +1,122 @@ package serverlessplugin import ( + "fmt" + + envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + "github.com/golang/protobuf/proto" + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/consul/agent/xds/xdscommon" + "github.com/hashicorp/consul/api" ) +// MutateIndexedResources 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) { - return resources, nil + var resultErr error + + // The serverless plugin only supports terminating gateays for now, but will + // likely add connect proxies soon. + if config.Kind != api.ServiceKindTerminatingGateway { + return resources, resultErr + } + + for _, indexType := range []string{ + xdscommon.ClusterType, + xdscommon.ListenerType, + } { + for nameOrSNI, msg := range resources.Index[indexType] { + switch resource := msg.(type) { + case *envoy_cluster_v3.Cluster: + patcher := getPatcherBySNI(config, config.Kind, nameOrSNI) + if patcher == nil { + continue + } + + newCluster, patched, err := patcher.PatchCluster(resource) + if err != nil { + resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster: %w", err)) + continue + } + if patched { + resources.Index[xdscommon.ClusterType][nameOrSNI] = newCluster + } + + case *envoy_listener_v3.Listener: + newListener, patched, err := patchTerminatingGatewayListener(resource, config) + if err != nil { + resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener: %w", err)) + continue + } + if patched { + resources.Index[xdscommon.ListenerType][nameOrSNI] = newListener + } + + default: + resultErr = multierror.Append(resultErr, fmt.Errorf("unsupported type was skipped: %T", resource)) + } + } + } + + return resources, resultErr +} + +func patchTerminatingGatewayListener(l *envoy_listener_v3.Listener, config xdscommon.PluginConfiguration) (proto.Message, bool, error) { + var resultErr error + patched := false + for _, filterChain := range l.FilterChains { + sni := getSNI(filterChain) + + if sni == "" { + continue + } + + patcher := getPatcherBySNI(config, config.Kind, sni) + + if patcher == nil { + continue + } + + var filters []*envoy_listener_v3.Filter + + for _, filter := range filterChain.Filters { + newFilter, ok, err := patcher.PatchFilter(filter) + + if err != nil { + resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err)) + filters = append(filters, filter) + } + if ok { + filters = append(filters, newFilter) + patched = true + } + } + filterChain.Filters = filters + } + + return l, patched, resultErr +} + +func getSNI(chain *envoy_listener_v3.FilterChain) string { + var sni string + + if chain == nil { + return sni + } + + if chain.FilterChainMatch == nil { + return sni + } + + if len(chain.FilterChainMatch.ServerNames) == 0 { + return sni + } + + return chain.FilterChainMatch.ServerNames[0] } diff --git a/agent/xds/testdata/serverless_plugin/clusters/lambda-terminating-gateway.envoy-1-20-x.golden b/agent/xds/testdata/serverless_plugin/clusters/lambda-terminating-gateway.envoy-1-20-x.golden new file mode 100644 index 000000000..aa3deff05 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/clusters/lambda-terminating-gateway.envoy-1-20-x.golden @@ -0,0 +1,169 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "LOGICAL_DNS", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "api.altdomain", + "portValue": 8081 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + "dnsRefreshRate": "10s", + "dnsLookupFamily": "V4_ONLY", + "outlierDetection": { + + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "filename": "api.cert.pem" + }, + "privateKey": { + "filename": "api.key.pem" + } + } + ], + "validationContext": { + "trustedCa": { + "filename": "ca.cert.pem" + } + } + } + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "LOGICAL_DNS", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "cache.mydomain", + "portValue": 8081 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + "dnsRefreshRate": "10s", + "dnsLookupFamily": "V4_ONLY", + "outlierDetection": { + + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "LOGICAL_DNS", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "db.mydomain", + "portValue": 8081 + } + } + }, + "healthStatus": "UNHEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + "dnsRefreshRate": "10s", + "dnsLookupFamily": "V4_ONLY", + "outlierDetection": { + + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "LOGICAL_DNS", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "lambda.us-east-1.amazonaws.com", + "portValue": 443 + } + } + } + } + ] + } + ] + }, + "dnsLookupFamily": "V4_ONLY", + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "sni": "*.amazonaws.com" + } + }, + "metadata": { + "filterMetadata": { + "com.amazonaws.lambda": { + "egress_gateway": true + } + } + } + } + ], + "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/listeners/lambda-terminating-gateway.envoy-1-20-x.golden b/agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway.envoy-1-20-x.golden new file mode 100644 index 000000000..e0b77f6f4 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway.envoy-1-20-x.golden @@ -0,0 +1,272 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "default:1.2.3.4:8443", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8443 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "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": "upstream.api.default.default.dc1", + "cluster": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICnTCCAkKgAwIBAgIRAJrvEdaRAkSltrotd/l/j2cwCgYIKoZIzj0EAwIwgbgx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj\nbzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw\nFQYDVQQKEw5IYXNoaUNvcnAgSW5jLjE/MD0GA1UEAxM2Q29uc3VsIEFnZW50IENB\nIDk2NjM4NzM1MDkzNTU5NTIwNDk3MTQwOTU3MDY1MTc0OTg3NDMxMB4XDTIwMDQx\nNDIyMzE1MloXDTIxMDQxNDIyMzE1MlowHDEaMBgGA1UEAxMRc2VydmVyLmRjMS5j\nb25zdWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ4v0FoIYI0OWmxE2MR6w5l\n0pWGhc02RpsOPj/6RS1fmXMMu7JzPzwCmkGcR16RlwwhNFKCZsWpvAjVRHf/pTp+\no4HHMIHEMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB\nBQUHAwIwDAYDVR0TAQH/BAIwADApBgNVHQ4EIgQgk7kABFitAy3PluyNtmzYiC7H\njSN8W/K/OXNJQAQAscMwKwYDVR0jBCQwIoAgNKbPPepvRHXSAPTc+a/BXBzFX1qJ\ny+Zi7qtjlFX7qtUwLQYDVR0RBCYwJIIRc2VydmVyLmRjMS5jb25zdWyCCWxvY2Fs\naG9zdIcEfwAAATAKBggqhkjOPQQDAgNJADBGAiEAhP4HmN5BWysWTbQWClXaWUah\nLpBGFrvc/2cCQuyEZKsCIQD6JyYCYMArtWwZ4G499zktxrFlqfX14bqyONrxtA5I\nDw==\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIE3KbKXHdsa0vvC1fysQaGdoJRgjRALIolI4XJanie+coAoGCCqGSM49\nAwEHoUQDQgAEOL9BaCGCNDlpsRNjEesOZdKVhoXNNkabDj4/+kUtX5lzDLuycz88\nAppBnEdekZcMITRSgmbFqbwI1UR3/6U6fg==\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 + } + } + }, + { + "filterChainMatch": { + "serverNames": [ + "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "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": "upstream.cache.default.default.dc1", + "cluster": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICmjCCAkGgAwIBAgIQe1ZmC0rzRwer6jaH1YIUIjAKBggqhkjOPQQDAjCBuDEL\nMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2Nv\nMRowGAYDVQQJExExMDEgU2Vjb25kIFN0cmVldDEOMAwGA1UEERMFOTQxMDUxFzAV\nBgNVBAoTDkhhc2hpQ29ycCBJbmMuMT8wPQYDVQQDEzZDb25zdWwgQWdlbnQgQ0Eg\nODE5ODAwNjg0MDM0MTM3ODkyNDYxNTA1MDk0NDU3OTU1MTQxNjEwHhcNMjAwNjE5\nMTU1MjAzWhcNMjEwNjE5MTU1MjAzWjAcMRowGAYDVQQDExFzZXJ2ZXIuZGMxLmNv\nbnN1bDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH2aWaaa3fpQLBayheHiKlrH\n+z53m0frfGknKjOhOPVYDVHV8x0OE01negswVQbKHAtxPf1M8Zy+WbI9rK7Ua1mj\ngccwgcQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF\nBQcDAjAMBgNVHRMBAf8EAjAAMCkGA1UdDgQiBCDf9CPBSUwwZvpeW73oJLTmgQE2\ntW1NKpL5t1uq9WFcqDArBgNVHSMEJDAigCCPPd/NxgZB0tq2M8pdVpPj3Cr79iTv\ni4/T1ysodfMb7zAtBgNVHREEJjAkghFzZXJ2ZXIuZGMxLmNvbnN1bIIJbG9jYWxo\nb3N0hwR/AAABMAoGCCqGSM49BAMCA0cAMEQCIFCjFZAoXq0s2ied2eIBv0i1KoW5\nIhCylnKFt6iHkyDeAiBBCByTcjHRgEQmqyPojQKoO584EFiczTub9aWdnf9tEw==\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEINsen3S8xzxMrKcRZIvxXzhKDn43Tw9ttqWEFU9TqS5hoAoGCCqGSM49\nAwEHoUQDQgAEfZpZpprd+lAsFrKF4eIqWsf7PnebR+t8aScqM6E49VgNUdXzHQ4T\nTWd6CzBVBsocC3E9/UzxnL5Zsj2srtRrWQ==\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 + } + } + }, + { + "filterChainMatch": { + "serverNames": [ + "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "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": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICnTCCAkOgAwIBAgIRAKF+qDJbaOULNL1TIatrsBowCgYIKoZIzj0EAwIwgbkx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj\nbzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw\nFQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB\nIDE4Nzg3MDAwNjUzMDcxOTYzNTk1ODkwNTE1ODY1NjEzMDA2MTU0NDAeFw0yMDA2\nMTkxNTMxMzRaFw0yMTA2MTkxNTMxMzRaMBwxGjAYBgNVBAMTEXNlcnZlci5kYzEu\nY29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdQ8Igci5f7ZvvCVsxXt9\ntLfvczD+60XHg0OC0+Aka7ZjQfbEjQwZbz/82EwPoS7Dqo3LTK4IuelOimoNNxuk\nkaOBxzCBxDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMAwGA1UdEwEB/wQCMAAwKQYDVR0OBCIEILzTLkfJcdWQnTMKUcai/YJq\n0RqH1pjCqtY7SOU4gGOTMCsGA1UdIwQkMCKAIMa2vNcTEC5AGfHIYARJ/4sodX0o\nLzCj3lpw7BcEzPTcMC0GA1UdEQQmMCSCEXNlcnZlci5kYzEuY29uc3Vsgglsb2Nh\nbGhvc3SHBH8AAAEwCgYIKoZIzj0EAwIDSAAwRQIgBZ/Z4GSLEc98WvT/qjTVCNTG\n1WNaAaesVbkRx+J0yl8CIQDAVoqY9ByA5vKHjnQrxWlc/JUtJz8wudg7e/OCRriP\nSg==\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIN1v14FaNxgY4MgjDOOWthen8dgwB0lNMs9/j2TfrnxzoAoGCCqGSM49\nAwEHoUQDQgAEdQ8Igci5f7ZvvCVsxXt9tLfvczD+60XHg0OC0+Aka7ZjQfbEjQwZ\nbz/82EwPoS7Dqo3LTK4IuelOimoNNxukkQ==\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 + } + } + }, + { + "filterChainMatch": { + "serverNames": [ + "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "upstream.web.default.default.dc1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.aws_lambda", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.aws_lambda.v3.Config", + "arn": "lambda-arn" + } + }, + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + }, + "stripMatchingHostPort": 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" + } + } + }, + "requireClientCertificate": true + } + } + }, + { + "filters": [ + { + "name": "envoy.filters.network.sni_cluster" + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "terminating_gateway.default", + "cluster": "" + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "trafficDirection": "INBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/xdscommon/xdscommon.go b/agent/xds/xdscommon/xdscommon.go index de856c355..b1b7da121 100644 --- a/agent/xds/xdscommon/xdscommon.go +++ b/agent/xds/xdscommon/xdscommon.go @@ -78,7 +78,8 @@ type PluginConfiguration struct { // associated service's CompoundServiceName EnvoyIDToServiceName map[string]api.CompoundServiceName - // Kind is mode the local Envoy proxy is running in + // Kind is mode the local Envoy proxy is running in. For now, only + // terminating gateways are supported. Kind api.ServiceKind } diff --git a/agent/xds/xdscommon/xdscommon_oss_test.go b/agent/xds/xdscommon/xdscommon_oss_test.go index d11c9b015..c92be3ba5 100644 --- a/agent/xds/xdscommon/xdscommon_oss_test.go +++ b/agent/xds/xdscommon/xdscommon_oss_test.go @@ -13,7 +13,7 @@ import ( ) func TestMakePluginConfiguration_TerminatingGateway(t *testing.T) { - snap := proxycfg.TestConfigSnapshotTerminatingGatewayWithServiceDefaultsMeta(t) + snap := proxycfg.TestConfigSnapshotTerminatingGatewayWithLambdaService(t) webService := api.CompoundServiceName{ Name: "web", @@ -41,7 +41,12 @@ func TestMakePluginConfiguration_TerminatingGateway(t *testing.T) { ServiceConfigs: map[api.CompoundServiceName]ServiceConfig{ webService: { Kind: api.ServiceKindTerminatingGateway, - Meta: map[string]string{"a": "b"}, + Meta: map[string]string{ + "serverless.consul.hashicorp.com/v1alpha1/lambda/enabled": "true", + "serverless.consul.hashicorp.com/v1alpha1/lambda/arn": "lambda-arn", + "serverless.consul.hashicorp.com/v1alpha1/lambda/payload-passthrough": "true", + "serverless.consul.hashicorp.com/v1alpha1/lambda/region": "us-east-1", + }, }, apiService: { Kind: api.ServiceKindTerminatingGateway,