diff --git a/agent/proxycfg/testing_terminating_gateway.go b/agent/proxycfg/testing_terminating_gateway.go index fda6591736..f2052a8107 100644 --- a/agent/proxycfg/testing_terminating_gateway.go +++ b/agent/proxycfg/testing_terminating_gateway.go @@ -328,10 +328,11 @@ func TestConfigSnapshotTerminatingGatewayDestinations(t testing.T, populateDesti roots, _ := TestCerts(t) var ( - externalIPTCP = structs.NewServiceName("external-IP-TCP", nil) - externalHostnameTCP = structs.NewServiceName("external-hostname-TCP", nil) - externalIPHTTP = structs.NewServiceName("external-IP-HTTP", nil) - externalHostnameHTTP = structs.NewServiceName("external-hostname-HTTP", nil) + externalIPTCP = structs.NewServiceName("external-IP-TCP", nil) + externalHostnameTCP = structs.NewServiceName("external-hostname-TCP", nil) + externalIPHTTP = structs.NewServiceName("external-IP-HTTP", nil) + externalHostnameHTTP = structs.NewServiceName("external-hostname-HTTP", nil) + externalHostnameWithSNI = structs.NewServiceName("external-hostname-with-SNI", nil) ) baseEvents := []UpdateEvent{ @@ -367,6 +368,12 @@ func TestConfigSnapshotTerminatingGatewayDestinations(t testing.T, populateDesti Service: externalHostnameHTTP, ServiceKind: structs.GatewayServiceKindDestination, }, + &structs.GatewayService{ + Service: externalHostnameWithSNI, + ServiceKind: structs.GatewayServiceKindDestination, + CAFile: "cert.pem", + SNI: "api.test.com", + }, ) baseEvents = testSpliceEvents(baseEvents, []UpdateEvent{ @@ -393,6 +400,10 @@ func TestConfigSnapshotTerminatingGatewayDestinations(t testing.T, populateDesti CorrelationID: serviceIntentionsIDPrefix + externalHostnameHTTP.String(), Result: structs.Intentions{}, }, + { + CorrelationID: serviceIntentionsIDPrefix + externalHostnameWithSNI.String(), + Result: structs.Intentions{}, + }, // ======== { CorrelationID: serviceLeafIDPrefix + externalIPTCP.String(), @@ -422,6 +433,13 @@ func TestConfigSnapshotTerminatingGatewayDestinations(t testing.T, populateDesti PrivateKeyPEM: "placeholder.key", }, }, + { + CorrelationID: serviceLeafIDPrefix + externalHostnameWithSNI.String(), + Result: &structs.IssuedCert{ + CertPEM: "placeholder.crt", + PrivateKeyPEM: "placeholder.key", + }, + }, // ======== { CorrelationID: serviceConfigIDPrefix + externalIPTCP.String(), @@ -474,6 +492,17 @@ func TestConfigSnapshotTerminatingGatewayDestinations(t testing.T, populateDesti }, }, }, + { + CorrelationID: serviceConfigIDPrefix + externalHostnameWithSNI.String(), + Result: &structs.ServiceConfigResponse{ + Mode: structs.ProxyModeTransparent, + ProxyConfig: map[string]interface{}{"protocol": "tcp"}, + Destination: structs.DestinationConfig{ + Addresses: []string{"api.test.com"}, + Port: 80, + }, + }, + }, }) } diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index c90877ec2f..9f64c5e342 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -559,6 +559,9 @@ func (s *ResourceGenerator) makeDestinationClusters(cfgSnap *proxycfg.ConfigSnap } else { cluster = s.makeTerminatingHostnameCluster(cfgSnap, opts) } + if err := s.injectGatewayDestinationAddons(cfgSnap, cluster, svcName); err != nil { + return nil, err + } clusters = append(clusters, cluster) } } @@ -602,6 +605,32 @@ func (s *ResourceGenerator) injectGatewayServiceAddons(cfgSnap *proxycfg.ConfigS return nil } +func (s *ResourceGenerator) injectGatewayDestinationAddons(cfgSnap *proxycfg.ConfigSnapshot, c *envoy_cluster_v3.Cluster, svc structs.ServiceName) error { + switch cfgSnap.Kind { + case structs.ServiceKindTerminatingGateway: + // Context used for TLS origination to the cluster + if mapping, ok := cfgSnap.TerminatingGateway.DestinationServices[svc]; ok && mapping.CAFile != "" { + tlsContext := &envoy_tls_v3.UpstreamTlsContext{ + CommonTlsContext: makeCommonTLSContextFromFiles(mapping.CAFile, mapping.CertFile, mapping.KeyFile), + } + if mapping.SNI != "" { + tlsContext.Sni = mapping.SNI + if err := injectSANMatcher(tlsContext.CommonTlsContext, mapping.SNI); err != nil { + return fmt.Errorf("failed to inject SNI matcher into TLS context: %v", err) + } + } + + transportSocket, err := makeUpstreamTLSTransportSocket(tlsContext) + if err != nil { + return err + } + c.TransportSocket = transportSocket + } + + } + return nil +} + func (s *ResourceGenerator) clustersFromSnapshotIngressGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { var clusters []proto.Message createdClusters := make(map[proxycfg.UpstreamID]bool) diff --git a/agent/xds/testdata/clusters/transparent-proxy-terminating-gateway-destinations-only.latest.golden b/agent/xds/testdata/clusters/transparent-proxy-terminating-gateway-destinations-only.latest.golden index 87569ad32c..0b298c9906 100644 --- a/agent/xds/testdata/clusters/transparent-proxy-terminating-gateway-destinations-only.latest.golden +++ b/agent/xds/testdata/clusters/transparent-proxy-terminating-gateway-destinations-only.latest.golden @@ -142,6 +142,57 @@ } }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "destination.api-test-com.external-hostname-with-SNI.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "LOGICAL_DNS", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "destination.api-test-com.external-hostname-with-SNI.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "api.test.com", + "portValue": 80 + } + } + } + } + ] + } + ] + }, + "dnsRefreshRate": "10s", + "outlierDetection": { + + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "validationContext": { + "trustedCa": { + "filename": "cert.pem" + }, + "matchSubjectAltNames": [ + { + "exact": "api.test.com" + } + ] + } + }, + "sni": "api.test.com" + } + } + }, { "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", "name": "destination.httpbin-org.external-hostname-HTTP.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", diff --git a/agent/xds/testdata/listeners/transparent-proxy-terminating-gateway-destinations-only.latest.golden b/agent/xds/testdata/listeners/transparent-proxy-terminating-gateway-destinations-only.latest.golden index fc39c5da90..a56501250e 100644 --- a/agent/xds/testdata/listeners/transparent-proxy-terminating-gateway-destinations-only.latest.golden +++ b/agent/xds/testdata/listeners/transparent-proxy-terminating-gateway-destinations-only.latest.golden @@ -309,6 +309,60 @@ } } }, + { + "filterChainMatch": { + "serverNames": [ + "destination.api-test-com.external-hostname-with-SNI.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": { + + }, + "statPrefix": "connect_authz" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.external-hostname-with-SNI.default.default.dc1", + "cluster": "destination.api-test-com.external-hostname-with-SNI.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "placeholder.crt\n" + }, + "privateKey": { + "inlineString": "placeholder.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 + } + } + }, { "filterChainMatch": { "serverNames": [