From ae79cdab1bae3846c398105185c246b13b5f78fc Mon Sep 17 00:00:00 2001 From: "R.B. Boyer" Date: Mon, 19 Aug 2019 12:19:44 -0500 Subject: [PATCH] connect: introduce ExternalSNI field on service-defaults (#6324) Compiling this will set an optional SNI field on each DiscoveryTarget. When set this value should be used for TLS connections to the instances of the target. If not set the default should be used. Setting ExternalSNI will disable mesh gateway use for that target. It also disables several service-resolver features that do not make sense for an external service. --- agent/config/runtime_test.go | 18 ++- agent/consul/discoverychain/compile.go | 39 ++++++ agent/consul/discoverychain/compile_test.go | 121 +++++++++++++++++- agent/proxycfg/testing.go | 18 +++ agent/structs/config_entry.go | 3 + agent/structs/config_entry_test.go | 9 +- agent/structs/discovery_chain.go | 5 + agent/xds/clusters.go | 7 +- agent/xds/clusters_test.go | 5 + agent/xds/endpoints_test.go | 5 + agent/xds/listeners_test.go | 5 + agent/xds/routes_test.go | 5 + ...nnect-proxy-with-chain-external-sni.golden | 116 +++++++++++++++++ ...nnect-proxy-with-chain-external-sni.golden | 41 ++++++ ...nnect-proxy-with-chain-external-sni.golden | 116 +++++++++++++++++ ...nnect-proxy-with-chain-external-sni.golden | 30 +++++ api/config_entry.go | 1 + api/config_entry_test.go | 8 +- api/discovery_chain.go | 2 + command/config/write/config_write_test.go | 11 +- 20 files changed, 542 insertions(+), 23 deletions(-) create mode 100644 agent/xds/testdata/clusters/connect-proxy-with-chain-external-sni.golden create mode 100644 agent/xds/testdata/endpoints/connect-proxy-with-chain-external-sni.golden create mode 100644 agent/xds/testdata/listeners/connect-proxy-with-chain-external-sni.golden create mode 100644 agent/xds/testdata/routes/connect-proxy-with-chain-external-sni.golden diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index c2c6b32d6b04..0093e9fa5b37 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -2783,6 +2783,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { "kind": "service-defaults", "name": "web", "protocol": "http", + "external_sni": "abc-123", "mesh_gateway": { "mode": "remote" } @@ -2796,6 +2797,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { kind = "service-defaults" name = "web" protocol = "http" + external_sni = "abc-123" mesh_gateway { mode = "remote" } @@ -2805,9 +2807,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.DataDir = dataDir rt.ConfigEntryBootstrap = []structs.ConfigEntry{ &structs.ServiceConfigEntry{ - Kind: structs.ServiceDefaults, - Name: "web", - Protocol: "http", + Kind: structs.ServiceDefaults, + Name: "web", + Protocol: "http", + ExternalSNI: "abc-123", MeshGateway: structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, }, @@ -2825,6 +2828,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { "Kind": "service-defaults", "Name": "web", "Protocol": "http", + "ExternalSNI": "abc-123", "MeshGateway": { "Mode": "remote" } @@ -2838,6 +2842,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { Kind = "service-defaults" Name = "web" Protocol = "http" + ExternalSNI = "abc-123" MeshGateway { Mode = "remote" } @@ -2847,9 +2852,10 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.DataDir = dataDir rt.ConfigEntryBootstrap = []structs.ConfigEntry{ &structs.ServiceConfigEntry{ - Kind: structs.ServiceDefaults, - Name: "web", - Protocol: "http", + Kind: structs.ServiceDefaults, + Name: "web", + Protocol: "http", + ExternalSNI: "abc-123", MeshGateway: structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, }, diff --git a/agent/consul/discoverychain/compile.go b/agent/consul/discoverychain/compile.go index 3f7ba6dc8fc0..0e94a87e5668 100644 --- a/agent/consul/discoverychain/compile.go +++ b/agent/consul/discoverychain/compile.go @@ -813,9 +813,48 @@ RESOLVE_AGAIN: target.Subset = resolver.Subsets[target.ServiceSubset] + if serviceDefault := c.entries.GetService(target.Service); serviceDefault != nil && serviceDefault.ExternalSNI != "" { + // Explicitly set the SNI value. + target.SNI = serviceDefault.ExternalSNI + target.External = true + } + + // If using external SNI the service is fundamentally external. + if target.External { + if resolver.Redirect != nil { + return nil, &structs.ConfigEntryGraphError{ + Message: fmt.Sprintf( + "service %q has an external SNI set; cannot define redirects for external services", + target.Service, + ), + } + } + if len(resolver.Subsets) > 0 { + return nil, &structs.ConfigEntryGraphError{ + Message: fmt.Sprintf( + "service %q has an external SNI set; cannot define subsets for external services", + target.Service, + ), + } + } + if len(resolver.Failover) > 0 { + return nil, &structs.ConfigEntryGraphError{ + Message: fmt.Sprintf( + "service %q has an external SNI set; cannot define failover for external services", + target.Service, + ), + } + } + } + // TODO (mesh-gateway)- maybe allow using a gateway within a datacenter at some point if target.Datacenter == c.useInDatacenter { target.MeshGateway.Mode = structs.MeshGatewayModeDefault + + } else if target.External { + // Bypass mesh gateways if it is an external service. + target.MeshGateway.Mode = structs.MeshGatewayModeDefault + } else { // Default mesh gateway settings if serviceDefault := c.entries.GetService(target.Service); serviceDefault != nil { diff --git a/agent/consul/discoverychain/compile_test.go b/agent/consul/discoverychain/compile_test.go index 1afcbfa64d6a..ee68c405c16b 100644 --- a/agent/consul/discoverychain/compile_test.go +++ b/agent/consul/discoverychain/compile_test.go @@ -46,6 +46,7 @@ func TestCompile(t *testing.T) { "datacenter failover with mesh gateways": testcase_DatacenterFailover_WithMeshGateways(), "noop split to resolver with default subset": testcase_NoopSplit_WithDefaultSubset(), "resolver with default subset": testcase_Resolve_WithDefaultSubset(), + "default resolver with external sni": testcase_DefaultResolver_ExternalSNI(), "resolver with no entries and inferring defaults": testcase_DefaultResolver(), "default resolver with proxy defaults": testcase_DefaultResolver_WithProxyDefaults(), "service redirect to service with default resolver is not a default chain": testcase_RedirectToDefaultResolverIsNotDefaultChain(), @@ -54,13 +55,16 @@ func TestCompile(t *testing.T) { "multi dc canary": testcase_MultiDatacenterCanary(), // various errors - "splitter requires valid protocol": testcase_SplitterRequiresValidProtocol(), - "router requires valid protocol": testcase_RouterRequiresValidProtocol(), - "split to unsplittable protocol": testcase_SplitToUnsplittableProtocol(), - "route to unroutable protocol": testcase_RouteToUnroutableProtocol(), - "failover crosses protocols": testcase_FailoverCrossesProtocols(), - "redirect crosses protocols": testcase_RedirectCrossesProtocols(), - "redirect to missing subset": testcase_RedirectToMissingSubset(), + "splitter requires valid protocol": testcase_SplitterRequiresValidProtocol(), + "router requires valid protocol": testcase_RouterRequiresValidProtocol(), + "split to unsplittable protocol": testcase_SplitToUnsplittableProtocol(), + "route to unroutable protocol": testcase_RouteToUnroutableProtocol(), + "failover crosses protocols": testcase_FailoverCrossesProtocols(), + "redirect crosses protocols": testcase_RedirectCrossesProtocols(), + "redirect to missing subset": testcase_RedirectToMissingSubset(), + "resolver with failover and external sni": testcase_Resolver_ExternalSNI_FailoverNotAllowed(), + "resolver with subsets and external sni": testcase_Resolver_ExternalSNI_SubsetsNotAllowed(), + "resolver with redirect and external sni": testcase_Resolver_ExternalSNI_RedirectNotAllowed(), // overrides "resolver with protocol from override": testcase_ResolverProtocolOverride(), @@ -1435,6 +1439,109 @@ func testcase_Resolve_WithDefaultSubset() compileTestCase { return compileTestCase{entries: entries, expect: expect} } +func testcase_DefaultResolver_ExternalSNI() compileTestCase { + entries := newEntries() + entries.AddServices(&structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "main", + ExternalSNI: "main.some.other.service.mesh", + }) + + expect := &structs.CompiledDiscoveryChain{ + Protocol: "tcp", + StartNode: "resolver:main.default.dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main.default.dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main.default.dc1", + Resolver: &structs.DiscoveryResolver{ + Default: true, + ConnectTimeout: 5 * time.Second, + Target: "main.default.dc1", + }, + }, + }, + Targets: map[string]*structs.DiscoveryTarget{ + "main.default.dc1": newTarget("main", "", "default", "dc1", func(t *structs.DiscoveryTarget) { + t.SNI = "main.some.other.service.mesh" + t.External = true + }), + }, + } + return compileTestCase{entries: entries, expect: expect, expectIsDefault: true} +} + +func testcase_Resolver_ExternalSNI_FailoverNotAllowed() compileTestCase { + entries := newEntries() + entries.AddServices(&structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "main", + ExternalSNI: "main.some.other.service.mesh", + }) + entries.AddResolvers(&structs.ServiceResolverConfigEntry{ + Kind: "service-resolver", + Name: "main", + ConnectTimeout: 33 * time.Second, + Failover: map[string]structs.ServiceResolverFailover{ + "*": {Service: "backup"}, + }, + }) + + return compileTestCase{ + entries: entries, + expectErr: `service "main" has an external SNI set; cannot define failover for external services`, + expectGraphErr: true, + } +} + +func testcase_Resolver_ExternalSNI_SubsetsNotAllowed() compileTestCase { + entries := newEntries() + entries.AddServices(&structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "main", + ExternalSNI: "main.some.other.service.mesh", + }) + entries.AddResolvers(&structs.ServiceResolverConfigEntry{ + Kind: "service-resolver", + Name: "main", + ConnectTimeout: 33 * time.Second, + Subsets: map[string]structs.ServiceResolverSubset{ + "canary": { + Filter: "Service.Meta.version == canary", + }, + }, + }) + + return compileTestCase{ + entries: entries, + expectErr: `service "main" has an external SNI set; cannot define subsets for external services`, + expectGraphErr: true, + } +} + +func testcase_Resolver_ExternalSNI_RedirectNotAllowed() compileTestCase { + entries := newEntries() + entries.AddServices(&structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "main", + ExternalSNI: "main.some.other.service.mesh", + }) + entries.AddResolvers(&structs.ServiceResolverConfigEntry{ + Kind: "service-resolver", + Name: "main", + ConnectTimeout: 33 * time.Second, + Redirect: &structs.ServiceResolverRedirect{ + Datacenter: "dc2", + }, + }) + + return compileTestCase{ + entries: entries, + expectErr: `service "main" has an external SNI set; cannot define redirects for external services`, + expectGraphErr: true, + } +} + func testcase_MultiDatacenterCanary() compileTestCase { entries := newEntries() setServiceProtocol(entries, "main", "http") diff --git a/agent/proxycfg/testing.go b/agent/proxycfg/testing.go index 7e97f4e3ff0f..b383ffe0601f 100644 --- a/agent/proxycfg/testing.go +++ b/agent/proxycfg/testing.go @@ -592,6 +592,10 @@ func TestConfigSnapshotDiscoveryChain(t testing.T) *ConfigSnapshot { return testConfigSnapshotDiscoveryChain(t, "simple") } +func TestConfigSnapshotDiscoveryChainExternalSNI(t testing.T) *ConfigSnapshot { + return testConfigSnapshotDiscoveryChain(t, "external-sni") +} + func TestConfigSnapshotDiscoveryChainWithOverrides(t testing.T) *ConfigSnapshot { return testConfigSnapshotDiscoveryChain(t, "simple-with-overrides") } @@ -664,6 +668,19 @@ func testConfigSnapshotDiscoveryChain(t testing.T, variation string, additionalE ConnectTimeout: 33 * time.Second, }, ) + case "external-sni": + entries = append(entries, + &structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "db", + ExternalSNI: "db.some.other.service.mesh", + }, + &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "db", + ConnectTimeout: 33 * time.Second, + }, + ) case "failover": entries = append(entries, &structs.ServiceResolverConfigEntry{ @@ -858,6 +875,7 @@ func testConfigSnapshotDiscoveryChain(t testing.T, variation string, additionalE switch variation { case "simple-with-overrides": case "simple": + case "external-sni": case "failover": snap.ConnectProxy.WatchedUpstreamEndpoints["db"]["fail.default.dc1"] = TestUpstreamNodesAlternate(t) diff --git a/agent/structs/config_entry.go b/agent/structs/config_entry.go index 293a5efa712f..13d16ff1bcff 100644 --- a/agent/structs/config_entry.go +++ b/agent/structs/config_entry.go @@ -52,6 +52,8 @@ type ServiceConfigEntry struct { Protocol string MeshGateway MeshGatewayConfig `json:",omitempty"` + ExternalSNI string `json:",omitempty"` + // TODO(banks): enable this once we have upstreams supported too. Enabling // sidecars actually makes no sense and adds complications when you don't // allow upstreams to be specified centrally too. @@ -302,6 +304,7 @@ func ConfigEntryDecodeRulesForKind(kind string) (skipWhenPatching []string, tran case ServiceDefaults: return nil, map[string]string{ "mesh_gateway": "meshgateway", + "external_sni": "externalsni", }, nil case ServiceRouter: return []string{ diff --git a/agent/structs/config_entry_test.go b/agent/structs/config_entry_test.go index 0c76f0928fe0..fc2a3368d72b 100644 --- a/agent/structs/config_entry_test.go +++ b/agent/structs/config_entry_test.go @@ -93,6 +93,7 @@ func TestDecodeConfigEntry(t *testing.T) { kind = "service-defaults" name = "main" protocol = "http" + external_sni = "abc-123" mesh_gateway { mode = "remote" } @@ -101,14 +102,16 @@ func TestDecodeConfigEntry(t *testing.T) { Kind = "service-defaults" Name = "main" Protocol = "http" + ExternalSNI = "abc-123" MeshGateway { Mode = "remote" } `, expect: &ServiceConfigEntry{ - Kind: "service-defaults", - Name: "main", - Protocol: "http", + Kind: "service-defaults", + Name: "main", + Protocol: "http", + ExternalSNI: "abc-123", MeshGateway: MeshGatewayConfig{ Mode: MeshGatewayModeRemote, }, diff --git a/agent/structs/discovery_chain.go b/agent/structs/discovery_chain.go index a99ff805a8c1..4534105fd7c9 100644 --- a/agent/structs/discovery_chain.go +++ b/agent/structs/discovery_chain.go @@ -160,6 +160,11 @@ type DiscoveryTarget struct { MeshGateway MeshGatewayConfig `json:",omitempty"` Subset ServiceResolverSubset `json:",omitempty"` + + // SNI if set is the sni field to use when addressing this set of + // endpoints. If not configured then the Connect default should be used. + SNI string `json:",omitempty"` + External bool `json:",omitempty"` } func NewDiscoveryTarget(service, serviceSubset, namespace, datacenter string) *DiscoveryTarget { diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index 2725fa97bf95..343e7e19b244 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -321,10 +321,15 @@ func (s *Server) makeUpstreamClustersForDiscoveryChain( c.Http2ProtocolOptions = &envoycore.Http2ProtocolOptions{} } + finalSNI := sni + if target.SNI != "" { + finalSNI = target.SNI + } + // Enable TLS upstream with the configured client certificate. c.TlsContext = &envoyauth.UpstreamTlsContext{ CommonTlsContext: makeCommonTLSContext(cfgSnap), - Sni: sni, + Sni: finalSNI, } out = append(out, c) diff --git a/agent/xds/clusters_test.go b/agent/xds/clusters_test.go index e71a138dc1dc..4bb9896f0c43 100644 --- a/agent/xds/clusters_test.go +++ b/agent/xds/clusters_test.go @@ -105,6 +105,11 @@ func TestClustersFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotDiscoveryChain, setup: nil, }, + { + name: "connect-proxy-with-chain-external-sni", + create: proxycfg.TestConfigSnapshotDiscoveryChainExternalSNI, + setup: nil, + }, { name: "connect-proxy-with-chain-and-overrides", create: proxycfg.TestConfigSnapshotDiscoveryChainWithOverrides, diff --git a/agent/xds/endpoints_test.go b/agent/xds/endpoints_test.go index bfe1eafd3a81..0dff69675fec 100644 --- a/agent/xds/endpoints_test.go +++ b/agent/xds/endpoints_test.go @@ -243,6 +243,11 @@ func Test_endpointsFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotDiscoveryChain, setup: nil, }, + { + name: "connect-proxy-with-chain-external-sni", + create: proxycfg.TestConfigSnapshotDiscoveryChainExternalSNI, + setup: nil, + }, { name: "connect-proxy-with-chain-and-overrides", create: proxycfg.TestConfigSnapshotDiscoveryChainWithOverrides, diff --git a/agent/xds/listeners_test.go b/agent/xds/listeners_test.go index dddbd06058e5..5d48fae0fab9 100644 --- a/agent/xds/listeners_test.go +++ b/agent/xds/listeners_test.go @@ -184,6 +184,11 @@ func TestListenersFromSnapshot(t *testing.T) { }, setup: nil, }, + { + name: "connect-proxy-with-chain-external-sni", + create: proxycfg.TestConfigSnapshotDiscoveryChainExternalSNI, + setup: nil, + }, { name: "connect-proxy-with-chain-and-overrides", create: proxycfg.TestConfigSnapshotDiscoveryChainWithOverrides, diff --git a/agent/xds/routes_test.go b/agent/xds/routes_test.go index e9bb5dc3c84c..728903728cdc 100644 --- a/agent/xds/routes_test.go +++ b/agent/xds/routes_test.go @@ -50,6 +50,11 @@ func TestRoutesFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotDiscoveryChain, setup: nil, }, + { + name: "connect-proxy-with-chain-external-sni", + create: proxycfg.TestConfigSnapshotDiscoveryChainExternalSNI, + setup: nil, + }, { name: "connect-proxy-with-chain-and-overrides", create: proxycfg.TestConfigSnapshotDiscoveryChainWithOverrides, diff --git a/agent/xds/testdata/clusters/connect-proxy-with-chain-external-sni.golden b/agent/xds/testdata/clusters/connect-proxy-with-chain-external-sni.golden new file mode 100644 index 000000000000..28f0f1fc2b78 --- /dev/null +++ b/agent/xds/testdata/clusters/connect-proxy-with-chain-external-sni.golden @@ -0,0 +1,116 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.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": { + + } + } + }, + "connectTimeout": "33s", + "tlsContext": { + "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" + } + } + }, + "sni": "db.some.other.service.mesh" + }, + "outlierDetection": { + + }, + "commonLbConfig": { + "healthyPanicThreshold": { + + } + } + }, + { + "@type": "type.googleapis.com/envoy.api.v2.Cluster", + "name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + } + } + }, + "connectTimeout": "5s", + "tlsContext": { + "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" + } + } + }, + "sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + }, + "outlierDetection": { + + } + }, + { + "@type": "type.googleapis.com/envoy.api.v2.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.api.v2.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-chain-external-sni.golden b/agent/xds/testdata/endpoints/connect-proxy-with-chain-external-sni.golden new file mode 100644 index 000000000000..2acef4c0aa08 --- /dev/null +++ b/agent/xds/testdata/endpoints/connect-proxy-with-chain-external-sni.golden @@ -0,0 +1,41 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.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 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/connect-proxy-with-chain-external-sni.golden b/agent/xds/testdata/listeners/connect-proxy-with-chain-external-sni.golden new file mode 100644 index 000000000000..e83b237dfb42 --- /dev/null +++ b/agent/xds/testdata/listeners/connect-proxy-with-chain-external-sni.golden @@ -0,0 +1,116 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "db:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.tcp_proxy", + "config": { + "cluster": "", + "stat_prefix": "upstream_db_tcp" + } + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.tcp_proxy", + "config": { + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul", + "stat_prefix": "upstream_prepared_query_geo-cache_tcp" + } + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 + } + }, + "filterChains": [ + { + "tlsContext": { + "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.ext_authz", + "config": { + "grpc_service": { + "envoy_grpc": { + "cluster_name": "local_agent" + }, + "initial_metadata": [ + { + "key": "x-consul-token", + "value": "my-token" + } + ] + }, + "stat_prefix": "connect_authz" + } + }, + { + "name": "envoy.tcp_proxy", + "config": { + "cluster": "local_app", + "stat_prefix": "public_listener_tcp" + } + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/connect-proxy-with-chain-external-sni.golden b/agent/xds/testdata/routes/connect-proxy-with-chain-external-sni.golden new file mode 100644 index 000000000000..a0b6cb832b4f --- /dev/null +++ b/agent/xds/testdata/routes/connect-proxy-with-chain-external-sni.golden @@ -0,0 +1,30 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "name": "db", + "virtualHosts": [ + { + "name": "db", + "domains": [ + "*" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/api/config_entry.go b/api/config_entry.go index 63a67b28f951..4348171a9c61 100644 --- a/api/config_entry.go +++ b/api/config_entry.go @@ -61,6 +61,7 @@ type ServiceConfigEntry struct { Name string Protocol string MeshGateway MeshGatewayConfig + ExternalSNI string CreateIndex uint64 ModifyIndex uint64 } diff --git a/api/config_entry_test.go b/api/config_entry_test.go index 21a6bcf3ed1e..13a6d6ee1df9 100644 --- a/api/config_entry_test.go +++ b/api/config_entry_test.go @@ -235,15 +235,17 @@ func TestDecodeConfigEntry(t *testing.T) { "Kind": "service-defaults", "Name": "main", "Protocol": "http", + "ExternalSNI": "abc-123", "MeshGateway": { "Mode": "remote" } } `, expect: &ServiceConfigEntry{ - Kind: "service-defaults", - Name: "main", - Protocol: "http", + Kind: "service-defaults", + Name: "main", + Protocol: "http", + ExternalSNI: "abc-123", MeshGateway: MeshGatewayConfig{ Mode: MeshGatewayModeRemote, }, diff --git a/api/discovery_chain.go b/api/discovery_chain.go index a226f912c294..976d179d87d4 100644 --- a/api/discovery_chain.go +++ b/api/discovery_chain.go @@ -187,4 +187,6 @@ type DiscoveryTarget struct { MeshGateway MeshGatewayConfig Subset ServiceResolverSubset + SNI string + External bool } diff --git a/command/config/write/config_write_test.go b/command/config/write/config_write_test.go index 59d162f4e900..22ac7c7d550a 100644 --- a/command/config/write/config_write_test.go +++ b/command/config/write/config_write_test.go @@ -251,6 +251,7 @@ func TestParseConfigEntry(t *testing.T) { kind = "service-defaults" name = "main" protocol = "http" + external_sni = "abc-123" mesh_gateway { mode = "remote" } @@ -259,6 +260,7 @@ func TestParseConfigEntry(t *testing.T) { Kind = "service-defaults" Name = "main" Protocol = "http" + ExternalSNI = "abc-123" MeshGateway { Mode = "remote" } @@ -268,6 +270,7 @@ func TestParseConfigEntry(t *testing.T) { "kind": "service-defaults", "name": "main", "protocol": "http", + "external_sni": "abc-123", "mesh_gateway": { "mode": "remote" } @@ -278,15 +281,17 @@ func TestParseConfigEntry(t *testing.T) { "Kind": "service-defaults", "Name": "main", "Protocol": "http", + "ExternalSNI": "abc-123", "MeshGateway": { "Mode": "remote" } } `, expect: &api.ServiceConfigEntry{ - Kind: "service-defaults", - Name: "main", - Protocol: "http", + Kind: "service-defaults", + Name: "main", + Protocol: "http", + ExternalSNI: "abc-123", MeshGateway: api.MeshGatewayConfig{ Mode: api.MeshGatewayModeRemote, },