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, },