diff --git a/.changelog/12956.txt b/.changelog/12956.txt new file mode 100644 index 000000000..86595eb2c --- /dev/null +++ b/.changelog/12956.txt @@ -0,0 +1,3 @@ +```release-note:feature +xds: Add the ability to invoke AWS Lambdas through sidecar proxies. +``` diff --git a/agent/xds/serverless_plugin_oss_test.go b/agent/xds/serverless_plugin_oss_test.go index 0f6fdd382..334905d4a 100644 --- a/agent/xds/serverless_plugin_oss_test.go +++ b/agent/xds/serverless_plugin_oss_test.go @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/hashicorp/consul/agent/proxycfg" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/xds/proxysupport" "github.com/hashicorp/consul/agent/xds/serverlessplugin" "github.com/hashicorp/consul/agent/xds/xdscommon" @@ -23,10 +24,28 @@ import ( ) func TestServerlessPluginFromSnapshot(t *testing.T) { + serviceDefaults := &structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "db", + 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", + }, + } + tests := []struct { name string create func(t testinf.T) *proxycfg.ConfigSnapshot }{ + { + name: "lambda-connect-proxy", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, serviceDefaults) + }, + }, { name: "lambda-terminating-gateway", create: func(t testinf.T) *proxycfg.ConfigSnapshot { diff --git a/agent/xds/serverlessplugin/patcher.go b/agent/xds/serverlessplugin/patcher.go index 8db1c95a1..32398a4cc 100644 --- a/agent/xds/serverlessplugin/patcher.go +++ b/agent/xds/serverlessplugin/patcher.go @@ -32,22 +32,8 @@ type patcher interface { 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 { +func getPatcherBySNI(config xdscommon.PluginConfiguration, sni string) patcher { serviceName, ok := config.SNIToServiceName[sni] if !ok { @@ -60,7 +46,28 @@ func getPatcherBySNI(config xdscommon.PluginConfiguration, kind api.ServiceKind, } p := makePatcher(serviceConfig) - if p == nil || !p.CanPatch(kind) { + 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 } diff --git a/agent/xds/serverlessplugin/patcher_test.go b/agent/xds/serverlessplugin/patcher_test.go index 456cc0cd4..8e068392f 100644 --- a/agent/xds/serverlessplugin/patcher_test.go +++ b/agent/xds/serverlessplugin/patcher_test.go @@ -47,10 +47,11 @@ func TestGetPatcherBySNI(t *testing.T) { 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.kind, tc.sni) + patcher := getPatcherBySNI(config, tc.sni) if tc.expected == nil { require.Empty(t, patcher) @@ -69,6 +70,7 @@ var ( func sampleConfig() xdscommon.PluginConfiguration { return xdscommon.PluginConfiguration{ + Kind: api.ServiceKindTerminatingGateway, ServiceConfigs: map[api.CompoundServiceName]xdscommon.ServiceConfig{ lambdaService: { Kind: api.ServiceKindTerminatingGateway, diff --git a/agent/xds/serverlessplugin/serverlessplugin.go b/agent/xds/serverlessplugin/serverlessplugin.go index 1d7387000..cb1964935 100644 --- a/agent/xds/serverlessplugin/serverlessplugin.go +++ b/agent/xds/serverlessplugin/serverlessplugin.go @@ -2,6 +2,7 @@ package serverlessplugin import ( "fmt" + "strings" envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" @@ -22,9 +23,9 @@ import ( func MutateIndexedResources(resources *xdscommon.IndexedResources, config xdscommon.PluginConfiguration) (*xdscommon.IndexedResources, error) { var resultErr error - // The serverless plugin only supports terminating gateays for now, but will - // likely add connect proxies soon. - if config.Kind != api.ServiceKindTerminatingGateway { + switch config.Kind { + case api.ServiceKindTerminatingGateway, api.ServiceKindConnectProxy: + default: return resources, resultErr } @@ -36,7 +37,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, config.Kind, nameOrSNI) + patcher := getPatcherBySNI(config, nameOrSNI) if patcher == nil { continue } @@ -51,7 +52,7 @@ func MutateIndexedResources(resources *xdscommon.IndexedResources, config xdscom } case *envoy_listener_v3.Listener: - newListener, patched, err := patchTerminatingGatewayListener(resource, config) + newListener, patched, err := patchListener(config, resource) if err != nil { resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener: %w", err)) continue @@ -61,7 +62,7 @@ func MutateIndexedResources(resources *xdscommon.IndexedResources, config xdscom } case *envoy_route_v3.RouteConfiguration: - patcher := getPatcherBySNI(config, config.Kind, nameOrSNI) + patcher := getPatcherBySNI(config, nameOrSNI) if patcher == nil { continue } @@ -84,7 +85,17 @@ func MutateIndexedResources(resources *xdscommon.IndexedResources, config xdscom return resources, resultErr } -func patchTerminatingGatewayListener(l *envoy_listener_v3.Listener, config xdscommon.PluginConfiguration) (proto.Message, bool, error) { +func patchListener(config xdscommon.PluginConfiguration, l *envoy_listener_v3.Listener) (proto.Message, bool, error) { + switch config.Kind { + case api.ServiceKindTerminatingGateway: + return patchTerminatingGatewayListener(config, l) + case api.ServiceKindConnectProxy: + return patchConnectProxyListener(config, l) + } + return l, false, nil +} + +func patchTerminatingGatewayListener(config xdscommon.PluginConfiguration, l *envoy_listener_v3.Listener) (proto.Message, bool, error) { var resultErr error patched := false for _, filterChain := range l.FilterChains { @@ -94,7 +105,7 @@ func patchTerminatingGatewayListener(l *envoy_listener_v3.Listener, config xdsco continue } - patcher := getPatcherBySNI(config, config.Kind, sni) + patcher := getPatcherBySNI(config, sni) if patcher == nil { continue @@ -120,6 +131,42 @@ func patchTerminatingGatewayListener(l *envoy_listener_v3.Listener, config xdsco return l, patched, resultErr } +func patchConnectProxyListener(config xdscommon.PluginConfiguration, l *envoy_listener_v3.Listener) (proto.Message, bool, error) { + var resultErr error + + envoyID := "" + if i := strings.IndexByte(l.Name, ':'); i != -1 { + envoyID = l.Name[:i] + } + + patcher := getPatcherByEnvoyID(config, envoyID) + if patcher == nil { + return l, false, nil + } + + var patched bool + + for _, filterChain := range l.FilterChains { + var filters []*envoy_listener_v3.Filter + + for _, filter := range filterChain.Filters { + newFilter, ok, err := 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 diff --git a/agent/xds/testdata/listeners/http-listener-with-timeouts.latest.golden b/agent/xds/testdata/listeners/http-listener-with-timeouts.latest.golden index 679b07105..de384eef5 100644 --- a/agent/xds/testdata/listeners/http-listener-with-timeouts.latest.golden +++ b/agent/xds/testdata/listeners/http-listener-with-timeouts.latest.golden @@ -67,14 +67,6 @@ "name": "envoy.filters.network.http_connection_manager", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "cert": true, - "chain": true, - "dns": true, - "subject": true, - "uri": true - }, "statPrefix": "public_listener", "routeConfig": { "name": "public_listener", @@ -119,6 +111,14 @@ "randomSampling": { } + }, + "forwardClientCertDetails": "APPEND_FORWARD", + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true } } } diff --git a/agent/xds/testdata/listeners/http-public-listener.latest.golden b/agent/xds/testdata/listeners/http-public-listener.latest.golden index 66db47bb2..45f052872 100644 --- a/agent/xds/testdata/listeners/http-public-listener.latest.golden +++ b/agent/xds/testdata/listeners/http-public-listener.latest.golden @@ -67,14 +67,6 @@ "name": "envoy.filters.network.http_connection_manager", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "cert": true, - "chain": true, - "dns": true, - "subject": true, - "uri": true - }, "statPrefix": "public_listener", "routeConfig": { "name": "public_listener", @@ -118,6 +110,14 @@ "randomSampling": { } + }, + "forwardClientCertDetails": "APPEND_FORWARD", + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true } } } diff --git a/agent/xds/testdata/listeners/terminating-gateway-service-subsets.latest.golden b/agent/xds/testdata/listeners/terminating-gateway-service-subsets.latest.golden index fea8b4775..003860ddc 100644 --- a/agent/xds/testdata/listeners/terminating-gateway-service-subsets.latest.golden +++ b/agent/xds/testdata/listeners/terminating-gateway-service-subsets.latest.golden @@ -184,14 +184,6 @@ "name": "envoy.filters.network.http_connection_manager", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "cert": true, - "chain": true, - "dns": true, - "subject": true, - "uri": true - }, "statPrefix": "upstream.web.default.default.dc1", "rds": { "configSource": { @@ -223,6 +215,14 @@ "randomSampling": { } + }, + "forwardClientCertDetails": "APPEND_FORWARD", + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true } } } @@ -266,14 +266,6 @@ "name": "envoy.filters.network.http_connection_manager", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "cert": true, - "chain": true, - "dns": true, - "subject": true, - "uri": true - }, "statPrefix": "upstream.web.default.default.dc1", "rds": { "configSource": { @@ -305,6 +297,14 @@ "randomSampling": { } + }, + "forwardClientCertDetails": "APPEND_FORWARD", + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true } } } @@ -348,14 +348,6 @@ "name": "envoy.filters.network.http_connection_manager", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "cert": true, - "chain": true, - "dns": true, - "subject": true, - "uri": true - }, "statPrefix": "upstream.web.default.default.dc1", "rds": { "configSource": { @@ -387,6 +379,14 @@ "randomSampling": { } + }, + "forwardClientCertDetails": "APPEND_FORWARD", + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true } } } diff --git a/agent/xds/testdata/serverless_plugin/clusters/lambda-connect-proxy.latest.golden b/agent/xds/testdata/serverless_plugin/clusters/lambda-connect-proxy.latest.golden new file mode 100644 index 000000000..88a3c1034 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/clusters/lambda-connect-proxy.latest.golden @@ -0,0 +1,127 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@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": "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 + } + } + } + }, + { + "@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/listeners/lambda-connect-proxy.latest.golden b/agent/xds/testdata/serverless_plugin/listeners/lambda-connect-proxy.latest.golden new file mode 100644 index 000000000..1e522685d --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/listeners/lambda-connect-proxy.latest.golden @@ -0,0 +1,160 @@ +{ + "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.aws_lambda", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.aws_lambda.v3.Config", + "arn": "lambda-arn" + } + }, + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + }, + "stripAnyHostPort": true + } + } + ] + } + ], + "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/listeners/lambda-terminating-gateway-with-service-resolvers.latest.golden b/agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway-with-service-resolvers.latest.golden index 158ea619c..8bfabc7fe 100644 --- a/agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway-with-service-resolvers.latest.golden +++ b/agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway-with-service-resolvers.latest.golden @@ -130,14 +130,6 @@ "name": "envoy.filters.network.http_connection_manager", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "cert": true, - "chain": true, - "dns": true, - "subject": true, - "uri": true - }, "statPrefix": "upstream.web.default.default.dc1", "rds": { "configSource": { @@ -177,6 +169,14 @@ } }, + "forwardClientCertDetails": "APPEND_FORWARD", + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true + }, "stripAnyHostPort": true } } @@ -220,14 +220,6 @@ "name": "envoy.filters.network.http_connection_manager", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "cert": true, - "chain": true, - "dns": true, - "subject": true, - "uri": true - }, "statPrefix": "upstream.web.default.default.dc1", "rds": { "configSource": { @@ -267,6 +259,14 @@ } }, + "forwardClientCertDetails": "APPEND_FORWARD", + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true + }, "stripAnyHostPort": true } } @@ -364,14 +364,6 @@ "name": "envoy.filters.network.http_connection_manager", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "cert": true, - "chain": true, - "dns": true, - "subject": true, - "uri": true - }, "statPrefix": "upstream.web.default.default.dc1", "rds": { "configSource": { @@ -411,6 +403,14 @@ } }, + "forwardClientCertDetails": "APPEND_FORWARD", + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true + }, "stripAnyHostPort": true } } diff --git a/agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway.latest.golden b/agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway.latest.golden index f30e7e875..2ec6bda8f 100644 --- a/agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway.latest.golden +++ b/agent/xds/testdata/serverless_plugin/listeners/lambda-terminating-gateway.latest.golden @@ -184,14 +184,6 @@ "name": "envoy.filters.network.http_connection_manager", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", - "forwardClientCertDetails": "APPEND_FORWARD", - "setCurrentClientCertDetails": { - "cert": true, - "chain": true, - "dns": true, - "subject": true, - "uri": true - }, "statPrefix": "upstream.web.default.default.dc1", "rds": { "configSource": { @@ -231,6 +223,14 @@ } }, + "forwardClientCertDetails": "APPEND_FORWARD", + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true + }, "stripAnyHostPort": true } } diff --git a/agent/xds/testdata/serverless_plugin/routes/lambda-connect-proxy.latest.golden b/agent/xds/testdata/serverless_plugin/routes/lambda-connect-proxy.latest.golden new file mode 100644 index 000000000..9c050cbe6 --- /dev/null +++ b/agent/xds/testdata/serverless_plugin/routes/lambda-connect-proxy.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 0cccba7d8..251b94e63 100644 --- a/agent/xds/xdscommon/xdscommon.go +++ b/agent/xds/xdscommon/xdscommon.go @@ -96,6 +96,39 @@ func MakePluginConfiguration(cfgSnap *proxycfg.ConfigSnapshot) PluginConfigurati } switch cfgSnap.Kind { + case structs.ServiceKindConnectProxy: + connectProxies := make(map[proxycfg.UpstreamID]struct{}) + for uid, upstreamData := range cfgSnap.ConnectProxy.WatchedUpstreamEndpoints { + 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{}{} + } + } + } + } + + for uid, dc := range cfgSnap.ConnectProxy.DiscoveryChain { + if _, ok := connectProxies[uid]; !ok { + continue + } + + serviceConfigs[upstreamIDToCompoundServiceName(uid)] = ServiceConfig{ + Meta: dc.ServiceMeta, + Kind: api.ServiceKindConnectProxy, + } + + compoundServiceName := upstreamIDToCompoundServiceName(uid) + 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) @@ -135,3 +168,11 @@ func serviceNameToCompoundServiceName(svc structs.ServiceName) api.CompoundServi Namespace: svc.NamespaceOrDefault(), } } + +func upstreamIDToCompoundServiceName(uid proxycfg.UpstreamID) api.CompoundServiceName { + return api.CompoundServiceName{ + Name: uid.Name, + Partition: uid.PartitionOrDefault(), + Namespace: uid.NamespaceOrDefault(), + } +} diff --git a/agent/xds/xdscommon/xdscommon_oss_test.go b/agent/xds/xdscommon/xdscommon_oss_test.go index a1182f201..7bc61c313 100644 --- a/agent/xds/xdscommon/xdscommon_oss_test.go +++ b/agent/xds/xdscommon/xdscommon_oss_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/hashicorp/consul/agent/proxycfg" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" ) @@ -76,3 +77,42 @@ func TestMakePluginConfiguration_TerminatingGateway(t *testing.T) { require.Equal(t, expected, MakePluginConfiguration(snap)) } + +func TestMakePluginConfiguration_ConnectProxy(t *testing.T) { + dbService := api.CompoundServiceName{ + Name: "db", + Partition: "default", + Namespace: "default", + } + lambdaMeta := 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", + } + serviceDefaults := &structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "db", + Protocol: "http", + Meta: lambdaMeta, + } + + snap := proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, serviceDefaults) + expected := PluginConfiguration{ + Kind: api.ServiceKindConnectProxy, + ServiceConfigs: map[api.CompoundServiceName]ServiceConfig{ + dbService: { + Kind: api.ServiceKindConnectProxy, + Meta: lambdaMeta, + }, + }, + SNIToServiceName: map[string]api.CompoundServiceName{ + "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": dbService, + }, + EnvoyIDToServiceName: map[string]api.CompoundServiceName{ + "db": dbService, + }, + } + + require.Equal(t, expected, MakePluginConfiguration(snap)) +}