From c69055c5cdd1952ee3765a6b80ac28ed7ab82d55 Mon Sep 17 00:00:00 2001 From: "R.B. Boyer" Date: Wed, 14 Aug 2019 15:04:39 -0500 Subject: [PATCH] connect: introduce ExternalSNI field on service-defaults 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. --- agent/config/runtime_test.go | 18 ++- agent/consul/discoverychain/compile.go | 12 ++ agent/consul/discoverychain/compile_test.go | 32 +++++ agent/proxycfg/testing.go | 18 +++ agent/structs/config_entry.go | 3 + agent/structs/config_entry_test.go | 9 +- agent/structs/discovery_chain.go | 4 + 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 | 1 + command/config/write/config_write_test.go | 11 +- 20 files changed, 431 insertions(+), 16 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..1fc4b741afd1 100644 --- a/agent/consul/discoverychain/compile.go +++ b/agent/consul/discoverychain/compile.go @@ -813,9 +813,21 @@ RESOLVE_AGAIN: target.Subset = resolver.Subsets[target.ServiceSubset] + usingExternalSNI := false + if serviceDefault := c.entries.GetService(target.Service); serviceDefault != nil && serviceDefault.ExternalSNI != "" { + // Explicitly set the SNI value. + target.SNI = serviceDefault.ExternalSNI + usingExternalSNI = true + } + // 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 usingExternalSNI { + // Bypass mesh gateways if external SNI is configured. + 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..f5e343140205 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(), @@ -1435,6 +1436,37 @@ 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" + }), + }, + } + return compileTestCase{entries: entries, expect: expect, expectIsDefault: 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 aa5458cf3125..95931a3e10c3 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. @@ -306,6 +308,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..ef2d4c19292e 100644 --- a/agent/structs/discovery_chain.go +++ b/agent/structs/discovery_chain.go @@ -160,6 +160,10 @@ 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 default should be used. + SNI string `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..8de29d7ea7d6 100644 --- a/api/discovery_chain.go +++ b/api/discovery_chain.go @@ -187,4 +187,5 @@ type DiscoveryTarget struct { MeshGateway MeshGatewayConfig Subset ServiceResolverSubset + SNI string } 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, },