From af2e4a7cb227b0b71635e3dd746e1ffc35e213fe Mon Sep 17 00:00:00 2001 From: "R.B. Boyer" Date: Wed, 14 Aug 2019 15:04:39 -0500 Subject: [PATCH 1/4] 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, }, From a3cc49e6e18a8eb9cdc185d143d541386401dcd7 Mon Sep 17 00:00:00 2001 From: "R.B. Boyer" Date: Wed, 14 Aug 2019 16:40:35 -0500 Subject: [PATCH 2/4] treat external services differently --- agent/consul/discoverychain/compile.go | 27 +++++++-- agent/consul/discoverychain/compile_test.go | 65 ++++++++++++++++++--- agent/structs/discovery_chain.go | 3 +- api/discovery_chain.go | 1 + 4 files changed, 84 insertions(+), 12 deletions(-) diff --git a/agent/consul/discoverychain/compile.go b/agent/consul/discoverychain/compile.go index 1fc4b741afd1..95efc1753031 100644 --- a/agent/consul/discoverychain/compile.go +++ b/agent/consul/discoverychain/compile.go @@ -813,19 +813,38 @@ 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 + target.External = true + } + + // If using external SNI the service is fundamentally external. + if target.External { + 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 usingExternalSNI { - // Bypass mesh gateways if external SNI is configured. + } else if target.External { + // Bypass mesh gateways if it is an external service. target.MeshGateway.Mode = structs.MeshGatewayModeDefault } else { diff --git a/agent/consul/discoverychain/compile_test.go b/agent/consul/discoverychain/compile_test.go index f5e343140205..f808943f5e1e 100644 --- a/agent/consul/discoverychain/compile_test.go +++ b/agent/consul/discoverychain/compile_test.go @@ -55,13 +55,15 @@ 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(), // overrides "resolver with protocol from override": testcase_ResolverProtocolOverride(), @@ -1461,12 +1463,61 @@ func testcase_DefaultResolver_ExternalSNI() compileTestCase { 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_MultiDatacenterCanary() compileTestCase { entries := newEntries() setServiceProtocol(entries, "main", "http") diff --git a/agent/structs/discovery_chain.go b/agent/structs/discovery_chain.go index ef2d4c19292e..b56f1a6d951d 100644 --- a/agent/structs/discovery_chain.go +++ b/agent/structs/discovery_chain.go @@ -163,7 +163,8 @@ type DiscoveryTarget struct { // 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"` + SNI string `json:",omitempty"` + External bool `json:",omitempty"` } func NewDiscoveryTarget(service, serviceSubset, namespace, datacenter string) *DiscoveryTarget { diff --git a/api/discovery_chain.go b/api/discovery_chain.go index 8de29d7ea7d6..976d179d87d4 100644 --- a/api/discovery_chain.go +++ b/api/discovery_chain.go @@ -188,4 +188,5 @@ type DiscoveryTarget struct { MeshGateway MeshGatewayConfig Subset ServiceResolverSubset SNI string + External bool } From 1af4dbd166759369528c59b1e68490b5a4af34e4 Mon Sep 17 00:00:00 2001 From: "R.B. Boyer" Date: Wed, 14 Aug 2019 16:46:44 -0500 Subject: [PATCH 3/4] also disallow redirects for external services --- agent/consul/discoverychain/compile.go | 8 +++++++ agent/consul/discoverychain/compile_test.go | 24 +++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/agent/consul/discoverychain/compile.go b/agent/consul/discoverychain/compile.go index 95efc1753031..0e94a87e5668 100644 --- a/agent/consul/discoverychain/compile.go +++ b/agent/consul/discoverychain/compile.go @@ -821,6 +821,14 @@ RESOLVE_AGAIN: // 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( diff --git a/agent/consul/discoverychain/compile_test.go b/agent/consul/discoverychain/compile_test.go index f808943f5e1e..ee68c405c16b 100644 --- a/agent/consul/discoverychain/compile_test.go +++ b/agent/consul/discoverychain/compile_test.go @@ -64,6 +64,7 @@ func TestCompile(t *testing.T) { "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(), @@ -1518,6 +1519,29 @@ func testcase_Resolver_ExternalSNI_SubsetsNotAllowed() compileTestCase { } } +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") From 763255b77caac5344e5017e96c1662fab09860f6 Mon Sep 17 00:00:00 2001 From: "R.B. Boyer" Date: Mon, 19 Aug 2019 10:49:38 -0500 Subject: [PATCH 4/4] Apply suggestions from code review Co-Authored-By: Paul Banks --- agent/structs/discovery_chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/structs/discovery_chain.go b/agent/structs/discovery_chain.go index b56f1a6d951d..4534105fd7c9 100644 --- a/agent/structs/discovery_chain.go +++ b/agent/structs/discovery_chain.go @@ -162,7 +162,7 @@ type DiscoveryTarget struct { 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. + // endpoints. If not configured then the Connect default should be used. SNI string `json:",omitempty"` External bool `json:",omitempty"` }