From 219c78e586381f2f2a04ce5e2259befcb1d0aebc Mon Sep 17 00:00:00 2001 From: freddygv Date: Mon, 13 Apr 2020 10:33:01 -0600 Subject: [PATCH] Add xds cluster/listener/endpoint management --- agent/proxycfg/testing.go | 61 ++++ agent/xds/clusters.go | 47 ++- agent/xds/clusters_test.go | 10 + agent/xds/endpoints.go | 19 +- agent/xds/endpoints_test.go | 10 + agent/xds/listeners.go | 137 +++++++-- agent/xds/listeners_test.go | 31 ++ agent/xds/server.go | 7 +- .../terminating-gateway-no-services.golden | 7 + .../clusters/terminating-gateway.golden | 39 +++ .../terminating-gateway-no-services.golden | 7 + .../endpoints/terminating-gateway.golden | 75 +++++ ...gateway-custom-and-tagged-addresses.golden | 277 ++++++++++++++++++ .../terminating-gateway-no-services.golden | 22 ++ .../listeners/terminating-gateway.golden | 142 +++++++++ 15 files changed, 844 insertions(+), 47 deletions(-) create mode 100644 agent/xds/testdata/clusters/terminating-gateway-no-services.golden create mode 100644 agent/xds/testdata/clusters/terminating-gateway.golden create mode 100644 agent/xds/testdata/endpoints/terminating-gateway-no-services.golden create mode 100644 agent/xds/testdata/endpoints/terminating-gateway.golden create mode 100644 agent/xds/testdata/listeners/terminating-gateway-custom-and-tagged-addresses.golden create mode 100644 agent/xds/testdata/listeners/terminating-gateway-no-services.golden create mode 100644 agent/xds/testdata/listeners/terminating-gateway.golden diff --git a/agent/proxycfg/testing.go b/agent/proxycfg/testing.go index 5cd20586b5..bc73c9f6b2 100644 --- a/agent/proxycfg/testing.go +++ b/agent/proxycfg/testing.go @@ -1446,6 +1446,67 @@ func TestConfigSnapshotExposeConfig(t testing.T) *ConfigSnapshot { } } +func TestConfigSnapshotTerminatingGateway(t testing.T) *ConfigSnapshot { + return testConfigSnapshotTerminatingGateway(t, true) +} + +func TestConfigSnapshotTerminatingGatewayNoServices(t testing.T) *ConfigSnapshot { + return testConfigSnapshotTerminatingGateway(t, false) +} + +func testConfigSnapshotTerminatingGateway(t testing.T, populateServices bool) *ConfigSnapshot { + roots, _ := TestCerts(t) + + snap := &ConfigSnapshot{ + Kind: structs.ServiceKindTerminatingGateway, + Service: "terminating-gateway", + ProxyID: structs.NewServiceID("terminating-gateway", nil), + Address: "1.2.3.4", + TaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressWAN: structs.ServiceAddress{ + Address: "198.18.0.1", + Port: 443, + }, + }, + Port: 8443, + Roots: roots, + Datacenter: "dc1", + } + if populateServices { + web := structs.NewServiceID("web", nil) + webNodes := TestUpstreamNodes(t) + + api := structs.NewServiceID("api", nil) + apiNodes := TestUpstreamNodes(t) + for i := 0; i < len(apiNodes); i++ { + apiNodes[i].Service.Service = "api" + apiNodes[i].Service.Port = 8081 + } + + // Hard-coding these certs since TestLeafForCA returns a new leaf each time and will not match the golden file + webLeaf := &structs.IssuedCert{ + CertPEM: `-----BEGIN CERTIFICATE-----\nMIICKDCCAc+gAwIBAgIIT/zLIOrnlRQwCgYIKoZIzj0EAwIwFTETMBEGA1UEAxMK\nVGVzdCBDQSA4NTAeFw0yMDA0MTEwMDMxMjJaFw0zMDA0MTEwMDMxMjJaMCoxKDAm\nBgNVBAMTH3dlYi5zdmMuZGVmYXVsdC4xMTExMTExMS5jb25zdWwwWTATBgcqhkjO\nPQIBBggqhkjOPQMBBwNCAAR3uNYYt8amLQMfae6GpMyFaMBBkGL2ZPANmCy7nsL7\n5kczishLb0GG1/PoNBQJW5A1Wl7uI/SE77KTThRxk3Wco4HzMIHwMA4GA1UdDwEB\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\nBAIwADApBgNVHQ4EIgQgYbec+6bte/VdH3M63TFVmDU0jH5461iZTsiJ1x+18iow\nKwYDVR0jBCQwIoAgqo5xDZXH+SCNmEyBYOSyc8RlSBX3sJJrSLCtuZp5WxgwWQYD\nVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1\nNTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqGSM49\nBAMCA0cAMEQCIC/xdLblMMnwXGqBn9XkdGOnUEtJW0LU+tKyen5PxRO7AiBjTefh\n5uZU8QVs2FQTQHN0Omr4ngToHBHNwKl1Flvyqw==\n-----END CERTIFICATE-----\n`, + PrivateKeyPEM: `-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIP6roctlkGz+7od7hFwaldpvbnkXmnjvpPBxyKU4NcM9oAoGCCqGSM49\nAwEHoUQDQgAEd7jWGLfGpi0DH2nuhqTMhWjAQZBi9mTwDZgsu57C++ZHM4rIS29B\nhtfz6DQUCVuQNVpe7iP0hO+yk04UcZN1nA==\n-----END EC PRIVATE KEY-----\n`, + } + apiLeaf := &structs.IssuedCert{ + CertPEM: `-----BEGIN CERTIFICATE-----\nMIICKjCCAc+gAwIBAgIICeaPMbQdJsswCgYIKoZIzj0EAwIwFTETMBEGA1UEAxMK\nVGVzdCBDQSA4NTAeFw0yMDA0MTEwMDE3NTZaFw0zMDA0MTEwMDE3NTZaMCoxKDAm\nBgNVBAMTH2FwaS5zdmMuZGVmYXVsdC4xMTExMTExMS5jb25zdWwwWTATBgcqhkjO\nPQIBBggqhkjOPQMBBwNCAASvkFCbA1rP8NxyKAOGoLmVjwSB+dO/ncs5KqourUPw\nbZfQEETsTaTdO5aWgkJilagD7Z1RZltWk+MGhPleo8/bo4HzMIHwMA4GA1UdDwEB\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\nBAIwADApBgNVHQ4EIgQgyNkAhsJy93ueCk9Bjo9DdiZB+eJq7zs4qr9tmrT5zBAw\nKwYDVR0jBCQwIoAgJDQM9fdkMlYIa/hmjVbXie/3qNMaAS8R9dKPQ2XE05gwWQYD\nVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1\nNTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvYXBpMAoGCCqGSM49\nBAMCA0kAMEYCIQDN/60bmqjeFQpHw52r63Lftuuexl6AHVDI+o7MPYzfKwIhANMJ\n0s1qRbOUItdIC8y0Ph2woXcj2yXluiPzFT3Ij94k\n-----END CERTIFICATE-----\n`, + PrivateKeyPEM: `-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIBdbcIKnjbzUBLHVQANB2P6bQf6SNOtEd6san+82wY21oAoGCCqGSM49\nAwEHoUQDQgAEr5BQmwNaz/DccigDhqC5lY8EgfnTv53LOSqqLq1D8G2X0BBE7E2k\n3TuWloJCYpWoA+2dUWZbVpPjBoT5XqPP2w==\n-----END EC PRIVATE KEY-----\n`, + } + + snap.TerminatingGateway = configSnapshotTerminatingGateway{ + ServiceGroups: map[structs.ServiceID]structs.CheckServiceNodes{ + web: webNodes, + api: apiNodes, + }, + ServiceLeaves: map[structs.ServiceID]*structs.IssuedCert{ + web: webLeaf, + api: apiLeaf, + }, + } + } + return snap +} + func TestConfigSnapshotGRPCExposeHTTP1(t testing.T) *ConfigSnapshot { return &ConfigSnapshot{ Kind: structs.ServiceKindConnectProxy, diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index 58041496cd..b9cc587239 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -30,6 +30,8 @@ func (s *Server) clustersFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot, _ string switch cfgSnap.Kind { case structs.ServiceKindConnectProxy: return s.clustersFromSnapshotConnectProxy(cfgSnap) + case structs.ServiceKindTerminatingGateway: + return s.clustersFromSnapshotTerminatingGateway(cfgSnap) case structs.ServiceKindMeshGateway: return s.clustersFromSnapshotMeshGateway(cfgSnap) case structs.ServiceKindIngressGateway: @@ -117,6 +119,25 @@ func makeExposeClusterName(destinationPort int) string { return fmt.Sprintf("exposed_cluster_%d", destinationPort) } +// clustersFromSnapshotTerminatingGateway returns the xDS API representation of the "clusters" +// for a terminating gateway. This will include 1 cluster per service. +func (s *Server) clustersFromSnapshotTerminatingGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { + clusters := make([]proto.Message, 0, len(cfgSnap.TerminatingGateway.ServiceGroups)) + + // Generate the per-service clusters + for svc, _ := range cfgSnap.TerminatingGateway.ServiceGroups { + clusterName := connect.ServiceSNI(svc.ID, "", svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) + + cluster, err := s.makeGatewayCluster(clusterName, cfgSnap) + if err != nil { + return nil, fmt.Errorf("failed to make cluster %q for terminating-gateway: %v", cluster, err) + } + clusters = append(clusters, cluster) + } + + return clusters, nil +} + // clustersFromSnapshotMeshGateway returns the xDS API representation of the "clusters" // for a mesh gateway. This will include 1 cluster per remote datacenter as well as // 1 cluster for each service subset. @@ -133,7 +154,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho } clusterName := connect.DatacenterSNI(dc, cfgSnap.Roots.TrustDomain) - cluster, err := s.makeMeshGatewayCluster(clusterName, cfgSnap) + cluster, err := s.makeGatewayCluster(clusterName, cfgSnap) if err != nil { return nil, err } @@ -145,7 +166,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho for _, dc := range datacenters { clusterName := cfgSnap.ServerSNIFn(dc, "") - cluster, err := s.makeMeshGatewayCluster(clusterName, cfgSnap) + cluster, err := s.makeGatewayCluster(clusterName, cfgSnap) if err != nil { return nil, err } @@ -156,7 +177,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho for _, srv := range cfgSnap.MeshGateway.ConsulServers { clusterName := cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node) - cluster, err := s.makeMeshGatewayCluster(clusterName, cfgSnap) + cluster, err := s.makeGatewayCluster(clusterName, cfgSnap) if err != nil { return nil, err } @@ -173,9 +194,9 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho var cluster *envoy.Cluster var err error if hasResolver { - cluster, err = s.makeMeshGatewayClusterWithConnectTimeout(clusterName, cfgSnap, resolver.ConnectTimeout) + cluster, err = s.makeGatewayClusterWithConnectTimeout(clusterName, cfgSnap, resolver.ConnectTimeout) } else { - cluster, err = s.makeMeshGatewayCluster(clusterName, cfgSnap) + cluster, err = s.makeGatewayCluster(clusterName, cfgSnap) } if err != nil { return nil, err @@ -188,7 +209,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho for subsetName := range resolver.Subsets { clusterName := connect.ServiceSNI(svc.ID, subsetName, svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) - cluster, err := s.makeMeshGatewayClusterWithConnectTimeout(clusterName, cfgSnap, resolver.ConnectTimeout) + cluster, err := s.makeGatewayClusterWithConnectTimeout(clusterName, cfgSnap, resolver.ConnectTimeout) if err != nil { return nil, err } @@ -325,7 +346,7 @@ func (s *Server) makeUpstreamClusterForPreparedQuery(upstream structs.Upstream, // Enable TLS upstream with the configured client certificate. c.TlsContext = &envoyauth.UpstreamTlsContext{ - CommonTlsContext: makeCommonTLSContext(cfgSnap), + CommonTlsContext: makeCommonTLSContext(cfgSnap, cfgSnap.Leaf()), Sni: sni, } @@ -436,7 +457,7 @@ func (s *Server) makeUpstreamClustersForDiscoveryChain( // Enable TLS upstream with the configured client certificate. c.TlsContext = &envoyauth.UpstreamTlsContext{ - CommonTlsContext: makeCommonTLSContext(cfgSnap), + CommonTlsContext: makeCommonTLSContext(cfgSnap, cfgSnap.Leaf()), Sni: sni, } @@ -504,20 +525,20 @@ func makeClusterFromUserConfig(configJSON string) (*envoy.Cluster, error) { return &c, err } -func (s *Server) makeMeshGatewayCluster(clusterName string, cfgSnap *proxycfg.ConfigSnapshot) (*envoy.Cluster, error) { - return s.makeMeshGatewayClusterWithConnectTimeout(clusterName, cfgSnap, 0) +func (s *Server) makeGatewayCluster(clusterName string, cfgSnap *proxycfg.ConfigSnapshot) (*envoy.Cluster, error) { + return s.makeGatewayClusterWithConnectTimeout(clusterName, cfgSnap, 0) } -// makeMeshGatewayClusterWithConnectTimeout initializes a mesh gateway cluster +// makeGatewayClusterWithConnectTimeout initializes a mesh gateway cluster // with the specified connect timeout. If the timeout is 0, the connect timeout // defaults to use the mesh gateway timeout. -func (s *Server) makeMeshGatewayClusterWithConnectTimeout(clusterName string, cfgSnap *proxycfg.ConfigSnapshot, +func (s *Server) makeGatewayClusterWithConnectTimeout(clusterName string, cfgSnap *proxycfg.ConfigSnapshot, connectTimeout time.Duration) (*envoy.Cluster, error) { cfg, err := ParseGatewayConfig(cfgSnap.Proxy.Config) if err != nil { // Don't hard fail on a config typo, just warn. The parse func returns // default config if there is an error so it's safe to continue. - s.Logger.Warn("failed to parse mesh gateway config", "error", err) + s.Logger.Warn("failed to parse gateway config", "error", err) } if connectTimeout <= 0 { diff --git a/agent/xds/clusters_test.go b/agent/xds/clusters_test.go index 62d2a68b67..223fbe7880 100644 --- a/agent/xds/clusters_test.go +++ b/agent/xds/clusters_test.go @@ -418,6 +418,16 @@ func TestClustersFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotIngress_SplitterWithResolverRedirectMultiDC, setup: nil, }, + { + name: "terminating-gateway", + create: proxycfg.TestConfigSnapshotTerminatingGateway, + setup: nil, + }, + { + name: "terminating-gateway-no-services", + create: proxycfg.TestConfigSnapshotTerminatingGatewayNoServices, + setup: nil, + }, } for _, tt := range tests { diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index 7b8eb91205..1fef77c43f 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -3,7 +3,6 @@ package xds import ( "errors" "fmt" - envoy "github.com/envoyproxy/go-control-plane/envoy/api/v2" envoycore "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" envoyendpoint "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint" @@ -30,6 +29,8 @@ func (s *Server) endpointsFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot, _ strin switch cfgSnap.Kind { case structs.ServiceKindConnectProxy: return s.endpointsFromSnapshotConnectProxy(cfgSnap) + case structs.ServiceKindTerminatingGateway: + return s.endpointsFromSnapshotTerminatingGateway(cfgSnap) case structs.ServiceKindMeshGateway: return s.endpointsFromSnapshotMeshGateway(cfgSnap) case structs.ServiceKindIngressGateway: @@ -106,6 +107,22 @@ func (s *Server) filterSubsetEndpoints(subset *structs.ServiceResolverSubset, en return endpoints, nil } +func (s *Server) endpointsFromSnapshotTerminatingGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { + resources := make([]proto.Message, 0, len(cfgSnap.TerminatingGateway.ServiceGroups)) + + // generate the endpoints for the linked service groups + for svc, endpoints := range cfgSnap.TerminatingGateway.ServiceGroups { + clusterName := connect.ServiceSNI(svc.ID, "", svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) + + group := []loadAssignmentEndpointGroup{{Endpoints: endpoints, OnlyPassing: false}} + + la := makeLoadAssignment(clusterName, group, cfgSnap.Datacenter) + resources = append(resources, la) + } + + return resources, nil +} + func (s *Server) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { datacenters := cfgSnap.MeshGateway.Datacenters() resources := make([]proto.Message, 0, len(datacenters)+len(cfgSnap.MeshGateway.ServiceGroups)) diff --git a/agent/xds/endpoints_test.go b/agent/xds/endpoints_test.go index b95b10c64f..e39b93a51a 100644 --- a/agent/xds/endpoints_test.go +++ b/agent/xds/endpoints_test.go @@ -456,6 +456,16 @@ func Test_endpointsFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotIngress_SplitterWithResolverRedirectMultiDC, setup: nil, }, + { + name: "terminating-gateway", + create: proxycfg.TestConfigSnapshotTerminatingGateway, + setup: nil, + }, + { + name: "terminating-gateway-no-services", + create: proxycfg.TestConfigSnapshotTerminatingGatewayNoServices, + setup: nil, + }, } for _, tt := range tests { diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index 2d98b08aac..408230faef 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -38,8 +38,10 @@ func (s *Server) listenersFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot, token s switch cfgSnap.Kind { case structs.ServiceKindConnectProxy: return s.listenersFromSnapshotConnectProxy(cfgSnap, token) + case structs.ServiceKindTerminatingGateway: + return s.listenersFromSnapshotGateway(cfgSnap, token) case structs.ServiceKindMeshGateway: - return s.listenersFromSnapshotMeshGateway(cfgSnap) + return s.listenersFromSnapshotGateway(cfgSnap, token) case structs.ServiceKindIngressGateway: return s.listenersFromSnapshotIngressGateway(cfgSnap) default: @@ -181,8 +183,8 @@ func parseCheckPath(check structs.CheckType) (structs.ExposePath, error) { return path, nil } -// listenersFromSnapshotMeshGateway returns the "listener" for a mesh-gateway service -func (s *Server) listenersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { +// listenersFromSnapshotGateway returns the "listener" for a terminating-gateway or mesh-gateway service +func (s *Server) listenersFromSnapshotGateway(cfgSnap *proxycfg.ConfigSnapshot, token string) ([]proto.Message, error) { cfg, err := ParseGatewayConfig(cfgSnap.Proxy.Config) if err != nil { // Don't hard fail on a config typo, just warn. The parse func returns @@ -190,8 +192,14 @@ func (s *Server) listenersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsh s.Logger.Warn("failed to parse Connect.Proxy.Config", "error", err) } - // TODO - prevent invalid configurations of binding to the same port/addr - // twice including with the any addresses + // Prevent invalid configurations of binding to the same port/addr twice + // including with the any addresses + type namedAddress struct { + name string + structs.ServiceAddress + } + seen := make(map[structs.ServiceAddress]bool) + addrs := make([]namedAddress, 0) var resources []proto.Message if !cfg.NoDefaultBind { @@ -200,31 +208,58 @@ func (s *Server) listenersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsh addr = "0.0.0.0" } - l, err := s.makeGatewayListener("default", addr, cfgSnap.Port, cfgSnap) - if err != nil { - return nil, err + a := structs.ServiceAddress{ + Address: addr, + Port: cfgSnap.Port, + } + if !seen[a] { + addrs = append(addrs, namedAddress{name: "default", ServiceAddress: a}) + seen[a] = true } - resources = append(resources, l) } if cfg.BindTaggedAddresses { for name, addrCfg := range cfgSnap.TaggedAddresses { - l, err := s.makeGatewayListener(name, addrCfg.Address, addrCfg.Port, cfgSnap) - if err != nil { - return nil, err + a := structs.ServiceAddress{ + Address: addrCfg.Address, + Port: addrCfg.Port, + } + if !seen[a] { + addrs = append(addrs, namedAddress{name: name, ServiceAddress: a}) + seen[a] = true } - resources = append(resources, l) } } for name, addrCfg := range cfg.BindAddresses { - l, err := s.makeGatewayListener(name, addrCfg.Address, addrCfg.Port, cfgSnap) - if err != nil { - return nil, err + a := structs.ServiceAddress{ + Address: addrCfg.Address, + Port: addrCfg.Port, + } + if !seen[a] { + addrs = append(addrs, namedAddress{name: name, ServiceAddress: a}) + seen[a] = true + } + } + + // Make listeners once deduplicated + for _, a := range addrs { + var l *envoy.Listener + + switch cfgSnap.Kind { + case structs.ServiceKindTerminatingGateway: + l, err = s.makeTerminatingGatewayListener(a.name, a.Address, a.Port, cfgSnap, token) + if err != nil { + return nil, err + } + case structs.ServiceKindMeshGateway: + l, err = s.makeMeshGatewayListener(a.name, a.Address, a.Port, cfgSnap) + if err != nil { + return nil, err + } } resources = append(resources, l) } - return resources, err } @@ -329,7 +364,7 @@ func makeListenerFromUserConfig(configJSON string) (*envoy.Listener, error) { // specify custom listener params in config but still get our certs delivered // dynamically and intentions enforced without coming up with some complicated // templating/merging solution. -func injectConnectFilters(cfgSnap *proxycfg.ConfigSnapshot, token string, listener *envoy.Listener) error { +func injectConnectFilters(cfgSnap *proxycfg.ConfigSnapshot, token string, listener *envoy.Listener, setTLS bool) error { authFilter, err := makeExtAuthFilter(token) if err != nil { return err @@ -339,10 +374,11 @@ func injectConnectFilters(cfgSnap *proxycfg.ConfigSnapshot, token string, listen listener.FilterChains[idx].Filters = append([]envoylistener.Filter{authFilter}, listener.FilterChains[idx].Filters...) - // Force our TLS for all filter chains on a public listener - listener.FilterChains[idx].TlsContext = &envoyauth.DownstreamTlsContext{ - CommonTlsContext: makeCommonTLSContext(cfgSnap), - RequireClientCertificate: &types.BoolValue{Value: true}, + if setTLS { + listener.FilterChains[idx].TlsContext = &envoyauth.DownstreamTlsContext{ + CommonTlsContext: makeCommonTLSContext(cfgSnap, cfgSnap.Leaf()), + RequireClientCertificate: &types.BoolValue{Value: true}, + } } } return nil @@ -402,7 +438,7 @@ func (s *Server) makePublicListener(cfgSnap *proxycfg.ConfigSnapshot, token stri } } - err = injectConnectFilters(cfgSnap, token, l) + err = injectConnectFilters(cfgSnap, token, l, true) return l, err } @@ -517,7 +553,52 @@ func (s *Server) makeUpstreamListenerIgnoreDiscoveryChain( return l, nil } -func (s *Server) makeGatewayListener(name, addr string, port int, cfgSnap *proxycfg.ConfigSnapshot) (*envoy.Listener, error) { +func (s *Server) makeTerminatingGatewayListener(name, addr string, port int, cfgSnap *proxycfg.ConfigSnapshot, token string) (*envoy.Listener, error) { + l := makeListener(name, addr, port) + + tlsInspector, err := makeTLSInspectorListenerFilter() + if err != nil { + return nil, err + } + l.ListenerFilters = []envoylistener.ListenerFilter{tlsInspector} + + sniCluster, err := makeSNIClusterFilter() + if err != nil { + return nil, err + } + + // Make a FilterChain for each linked service + // Match on the cluster name, + for svc, _ := range cfgSnap.TerminatingGateway.ServiceGroups { + clusterName := connect.ServiceSNI(svc.ID, "", svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) + + // The cluster name here doesn't matter as the sni_cluster filter will fill it in for us. + tcpProxy, err := makeTCPProxyFilter(name, "", fmt.Sprintf("terminating_gateway_%s_", svc.ID)) + if err != nil { + return nil, err + } + + clusterChain := envoylistener.FilterChain{ + FilterChainMatch: makeSNIFilterChainMatch(clusterName), + Filters: []envoylistener.Filter{ + sniCluster, + tcpProxy, + }, + TlsContext: &envoyauth.DownstreamTlsContext{ + // TODO (gateways) (freddy) Could we get to this point and not have a leaf for the service? + CommonTlsContext: makeCommonTLSContext(cfgSnap, cfgSnap.TerminatingGateway.ServiceLeaves[svc]), + RequireClientCertificate: &types.BoolValue{Value: true}, + }, + } + l.FilterChains = append(l.FilterChains, clusterChain) + } + + err = injectConnectFilters(cfgSnap, token, l, false) + + return l, nil +} + +func (s *Server) makeMeshGatewayListener(name, addr string, port int, cfgSnap *proxycfg.ConfigSnapshot) (*envoy.Listener, error) { tlsInspector, err := makeTLSInspectorListenerFilter() if err != nil { return nil, err @@ -711,11 +792,10 @@ func makeTLSInspectorListenerFilter() (envoylistener.ListenerFilter, error) { return envoylistener.ListenerFilter{Name: util.TlsInspector}, nil } -// TODO(rb): should this be dead code? -func makeSNIFilterChainMatch(sniMatch string) (*envoylistener.FilterChainMatch, error) { +func makeSNIFilterChainMatch(sniMatch string) *envoylistener.FilterChainMatch { return &envoylistener.FilterChainMatch{ ServerNames: []string{sniMatch}, - }, nil + } } func makeSNIClusterFilter() (envoylistener.Filter, error) { @@ -881,7 +961,7 @@ func makeFilter(name string, cfg proto.Message) (envoylistener.Filter, error) { }, nil } -func makeCommonTLSContext(cfgSnap *proxycfg.ConfigSnapshot) *envoyauth.CommonTlsContext { +func makeCommonTLSContext(cfgSnap *proxycfg.ConfigSnapshot, leaf *structs.IssuedCert) *envoyauth.CommonTlsContext { // Concatenate all the root PEMs into one. // TODO(banks): verify this actually works with Envoy (docs are not clear). rootPEMS := "" @@ -892,7 +972,6 @@ func makeCommonTLSContext(cfgSnap *proxycfg.ConfigSnapshot) *envoyauth.CommonTls rootPEMS += root.RootCert } - leaf := cfgSnap.Leaf() return &envoyauth.CommonTlsContext{ TlsParams: &envoyauth.TlsParameters{}, TlsCertificates: []*envoyauth.TlsCertificate{ diff --git a/agent/xds/listeners_test.go b/agent/xds/listeners_test.go index e0c248342e..a2d3023f6c 100644 --- a/agent/xds/listeners_test.go +++ b/agent/xds/listeners_test.go @@ -298,6 +298,37 @@ func TestListenersFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotIngress_SplitterWithResolverRedirectMultiDC, setup: nil, }, + { + name: "terminating-gateway", + create: proxycfg.TestConfigSnapshotTerminatingGateway, + setup: nil, + }, + { + name: "terminating-gateway-no-services", + create: proxycfg.TestConfigSnapshotTerminatingGatewayNoServices, + setup: nil, + }, + { + name: "terminating-gateway-custom-and-tagged-addresses", + create: proxycfg.TestConfigSnapshotTerminatingGateway, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.Proxy.Config = map[string]interface{}{ + "envoy_gateway_no_default_bind": true, + "envoy_gateway_bind_tagged_addresses": true, + "envoy_gateway_bind_addresses": map[string]structs.ServiceAddress{ + "foo": { + Address: "198.17.2.3", + Port: 8080, + }, + // This bind address should not get a listener due to deduplication + "duplicate-of-tagged-wan-addr": { + Address: "198.18.0.1", + Port: 443, + }, + }, + } + }, + }, } for _, tt := range tests { diff --git a/agent/xds/server.go b/agent/xds/server.go index c56fa80d17..2cd84631c3 100644 --- a/agent/xds/server.go +++ b/agent/xds/server.go @@ -263,10 +263,9 @@ func (s *Server) process(stream ADSStream, reqCh <-chan *envoy.DiscoveryRequest) return status.Errorf(codes.PermissionDenied, "permission denied") } case structs.ServiceKindMeshGateway: - cfgSnap.ProxyID.EnterpriseMeta.FillAuthzContext(&authzContext) - if rule != nil && rule.ServiceWrite(cfgSnap.Service, &authzContext) != acl.Allow { - return status.Errorf(codes.PermissionDenied, "permission denied") - } + fallthrough + case structs.ServiceKindTerminatingGateway: + fallthrough case structs.ServiceKindIngressGateway: cfgSnap.ProxyID.EnterpriseMeta.FillAuthzContext(&authzContext) if rule != nil && rule.ServiceWrite(cfgSnap.Service, &authzContext) != acl.Allow { diff --git a/agent/xds/testdata/clusters/terminating-gateway-no-services.golden b/agent/xds/testdata/clusters/terminating-gateway-no-services.golden new file mode 100644 index 0000000000..1e4be3b4e8 --- /dev/null +++ b/agent/xds/testdata/clusters/terminating-gateway-no-services.golden @@ -0,0 +1,7 @@ +{ + "versionInfo": "00000001", + "resources": [ + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/terminating-gateway.golden b/agent/xds/testdata/clusters/terminating-gateway.golden new file mode 100644 index 0000000000..a0afb6b56c --- /dev/null +++ b/agent/xds/testdata/clusters/terminating-gateway.golden @@ -0,0 +1,39 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Cluster", + "name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + } + } + }, + "connectTimeout": "5s", + "outlierDetection": { + + } + }, + { + "@type": "type.googleapis.com/envoy.api.v2.Cluster", + "name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + } + } + }, + "connectTimeout": "5s", + "outlierDetection": { + + } + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/terminating-gateway-no-services.golden b/agent/xds/testdata/endpoints/terminating-gateway-no-services.golden new file mode 100644 index 0000000000..b11569ce9e --- /dev/null +++ b/agent/xds/testdata/endpoints/terminating-gateway-no-services.golden @@ -0,0 +1,7 @@ +{ + "versionInfo": "00000001", + "resources": [ + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/terminating-gateway.golden b/agent/xds/testdata/endpoints/terminating-gateway.golden new file mode 100644 index 0000000000..187a772f76 --- /dev/null +++ b/agent/xds/testdata/endpoints/terminating-gateway.golden @@ -0,0 +1,75 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment", + "clusterName": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.1", + "portValue": 8081 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8081 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment", + "clusterName": "web.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/terminating-gateway-custom-and-tagged-addresses.golden b/agent/xds/testdata/listeners/terminating-gateway-custom-and-tagged-addresses.golden new file mode 100644 index 0000000000..97f5db5e0d --- /dev/null +++ b/agent/xds/testdata/listeners/terminating-gateway-custom-and-tagged-addresses.golden @@ -0,0 +1,277 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "foo:198.17.2.3:8080", + "address": { + "socketAddress": { + "address": "198.17.2.3", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "tlsContext": { + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\\nMIICKDCCAc+gAwIBAgIIT/zLIOrnlRQwCgYIKoZIzj0EAwIwFTETMBEGA1UEAxMK\\nVGVzdCBDQSA4NTAeFw0yMDA0MTEwMDMxMjJaFw0zMDA0MTEwMDMxMjJaMCoxKDAm\\nBgNVBAMTH3dlYi5zdmMuZGVmYXVsdC4xMTExMTExMS5jb25zdWwwWTATBgcqhkjO\\nPQIBBggqhkjOPQMBBwNCAAR3uNYYt8amLQMfae6GpMyFaMBBkGL2ZPANmCy7nsL7\\n5kczishLb0GG1/PoNBQJW5A1Wl7uI/SE77KTThRxk3Wco4HzMIHwMA4GA1UdDwEB\\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\\nBAIwADApBgNVHQ4EIgQgYbec+6bte/VdH3M63TFVmDU0jH5461iZTsiJ1x+18iow\\nKwYDVR0jBCQwIoAgqo5xDZXH+SCNmEyBYOSyc8RlSBX3sJJrSLCtuZp5WxgwWQYD\\nVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1\\nNTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqGSM49\\nBAMCA0cAMEQCIC/xdLblMMnwXGqBn9XkdGOnUEtJW0LU+tKyen5PxRO7AiBjTefh\\n5uZU8QVs2FQTQHN0Omr4ngToHBHNwKl1Flvyqw==\\n-----END CERTIFICATE-----\\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\\nMHcCAQEEIP6roctlkGz+7od7hFwaldpvbnkXmnjvpPBxyKU4NcM9oAoGCCqGSM49\\nAwEHoUQDQgAEd7jWGLfGpi0DH2nuhqTMhWjAQZBi9mTwDZgsu57C++ZHM4rIS29B\\nhtfz6DQUCVuQNVpe7iP0hO+yk04UcZN1nA==\\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.filters.network.sni_cluster" + }, + { + "name": "envoy.tcp_proxy", + "config": { + "cluster": "", + "stat_prefix": "terminating_gateway_web_foo_tcp" + } + } + ] + }, + { + "filterChainMatch": { + "serverNames": [ + "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "tlsContext": { + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\\nMIICKjCCAc+gAwIBAgIICeaPMbQdJsswCgYIKoZIzj0EAwIwFTETMBEGA1UEAxMK\\nVGVzdCBDQSA4NTAeFw0yMDA0MTEwMDE3NTZaFw0zMDA0MTEwMDE3NTZaMCoxKDAm\\nBgNVBAMTH2FwaS5zdmMuZGVmYXVsdC4xMTExMTExMS5jb25zdWwwWTATBgcqhkjO\\nPQIBBggqhkjOPQMBBwNCAASvkFCbA1rP8NxyKAOGoLmVjwSB+dO/ncs5KqourUPw\\nbZfQEETsTaTdO5aWgkJilagD7Z1RZltWk+MGhPleo8/bo4HzMIHwMA4GA1UdDwEB\\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\\nBAIwADApBgNVHQ4EIgQgyNkAhsJy93ueCk9Bjo9DdiZB+eJq7zs4qr9tmrT5zBAw\\nKwYDVR0jBCQwIoAgJDQM9fdkMlYIa/hmjVbXie/3qNMaAS8R9dKPQ2XE05gwWQYD\\nVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1\\nNTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvYXBpMAoGCCqGSM49\\nBAMCA0kAMEYCIQDN/60bmqjeFQpHw52r63Lftuuexl6AHVDI+o7MPYzfKwIhANMJ\\n0s1qRbOUItdIC8y0Ph2woXcj2yXluiPzFT3Ij94k\\n-----END CERTIFICATE-----\\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\\nMHcCAQEEIBdbcIKnjbzUBLHVQANB2P6bQf6SNOtEd6san+82wY21oAoGCCqGSM49\\nAwEHoUQDQgAEr5BQmwNaz/DccigDhqC5lY8EgfnTv53LOSqqLq1D8G2X0BBE7E2k\\n3TuWloJCYpWoA+2dUWZbVpPjBoT5XqPP2w==\\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.filters.network.sni_cluster" + }, + { + "name": "envoy.tcp_proxy", + "config": { + "cluster": "", + "stat_prefix": "terminating_gateway_api_foo_tcp" + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.listener.tls_inspector" + } + ] + }, + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "wan:198.18.0.1:443", + "address": { + "socketAddress": { + "address": "198.18.0.1", + "portValue": 443 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "tlsContext": { + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\\nMIICKDCCAc+gAwIBAgIIT/zLIOrnlRQwCgYIKoZIzj0EAwIwFTETMBEGA1UEAxMK\\nVGVzdCBDQSA4NTAeFw0yMDA0MTEwMDMxMjJaFw0zMDA0MTEwMDMxMjJaMCoxKDAm\\nBgNVBAMTH3dlYi5zdmMuZGVmYXVsdC4xMTExMTExMS5jb25zdWwwWTATBgcqhkjO\\nPQIBBggqhkjOPQMBBwNCAAR3uNYYt8amLQMfae6GpMyFaMBBkGL2ZPANmCy7nsL7\\n5kczishLb0GG1/PoNBQJW5A1Wl7uI/SE77KTThRxk3Wco4HzMIHwMA4GA1UdDwEB\\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\\nBAIwADApBgNVHQ4EIgQgYbec+6bte/VdH3M63TFVmDU0jH5461iZTsiJ1x+18iow\\nKwYDVR0jBCQwIoAgqo5xDZXH+SCNmEyBYOSyc8RlSBX3sJJrSLCtuZp5WxgwWQYD\\nVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1\\nNTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqGSM49\\nBAMCA0cAMEQCIC/xdLblMMnwXGqBn9XkdGOnUEtJW0LU+tKyen5PxRO7AiBjTefh\\n5uZU8QVs2FQTQHN0Omr4ngToHBHNwKl1Flvyqw==\\n-----END CERTIFICATE-----\\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\\nMHcCAQEEIP6roctlkGz+7od7hFwaldpvbnkXmnjvpPBxyKU4NcM9oAoGCCqGSM49\\nAwEHoUQDQgAEd7jWGLfGpi0DH2nuhqTMhWjAQZBi9mTwDZgsu57C++ZHM4rIS29B\\nhtfz6DQUCVuQNVpe7iP0hO+yk04UcZN1nA==\\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.filters.network.sni_cluster" + }, + { + "name": "envoy.tcp_proxy", + "config": { + "cluster": "", + "stat_prefix": "terminating_gateway_web_wan_tcp" + } + } + ] + }, + { + "filterChainMatch": { + "serverNames": [ + "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "tlsContext": { + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\\nMIICKjCCAc+gAwIBAgIICeaPMbQdJsswCgYIKoZIzj0EAwIwFTETMBEGA1UEAxMK\\nVGVzdCBDQSA4NTAeFw0yMDA0MTEwMDE3NTZaFw0zMDA0MTEwMDE3NTZaMCoxKDAm\\nBgNVBAMTH2FwaS5zdmMuZGVmYXVsdC4xMTExMTExMS5jb25zdWwwWTATBgcqhkjO\\nPQIBBggqhkjOPQMBBwNCAASvkFCbA1rP8NxyKAOGoLmVjwSB+dO/ncs5KqourUPw\\nbZfQEETsTaTdO5aWgkJilagD7Z1RZltWk+MGhPleo8/bo4HzMIHwMA4GA1UdDwEB\\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\\nBAIwADApBgNVHQ4EIgQgyNkAhsJy93ueCk9Bjo9DdiZB+eJq7zs4qr9tmrT5zBAw\\nKwYDVR0jBCQwIoAgJDQM9fdkMlYIa/hmjVbXie/3qNMaAS8R9dKPQ2XE05gwWQYD\\nVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1\\nNTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvYXBpMAoGCCqGSM49\\nBAMCA0kAMEYCIQDN/60bmqjeFQpHw52r63Lftuuexl6AHVDI+o7MPYzfKwIhANMJ\\n0s1qRbOUItdIC8y0Ph2woXcj2yXluiPzFT3Ij94k\\n-----END CERTIFICATE-----\\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\\nMHcCAQEEIBdbcIKnjbzUBLHVQANB2P6bQf6SNOtEd6san+82wY21oAoGCCqGSM49\\nAwEHoUQDQgAEr5BQmwNaz/DccigDhqC5lY8EgfnTv53LOSqqLq1D8G2X0BBE7E2k\\n3TuWloJCYpWoA+2dUWZbVpPjBoT5XqPP2w==\\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.filters.network.sni_cluster" + }, + { + "name": "envoy.tcp_proxy", + "config": { + "cluster": "", + "stat_prefix": "terminating_gateway_api_wan_tcp" + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.listener.tls_inspector" + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/terminating-gateway-no-services.golden b/agent/xds/testdata/listeners/terminating-gateway-no-services.golden new file mode 100644 index 0000000000..01f4a6e1b2 --- /dev/null +++ b/agent/xds/testdata/listeners/terminating-gateway-no-services.golden @@ -0,0 +1,22 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "default:1.2.3.4:8443", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8443 + } + }, + "listenerFilters": [ + { + "name": "envoy.listener.tls_inspector" + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/terminating-gateway.golden b/agent/xds/testdata/listeners/terminating-gateway.golden new file mode 100644 index 0000000000..39bd9ea786 --- /dev/null +++ b/agent/xds/testdata/listeners/terminating-gateway.golden @@ -0,0 +1,142 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "default:1.2.3.4:8443", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8443 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "tlsContext": { + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\\nMIICKDCCAc+gAwIBAgIIT/zLIOrnlRQwCgYIKoZIzj0EAwIwFTETMBEGA1UEAxMK\\nVGVzdCBDQSA4NTAeFw0yMDA0MTEwMDMxMjJaFw0zMDA0MTEwMDMxMjJaMCoxKDAm\\nBgNVBAMTH3dlYi5zdmMuZGVmYXVsdC4xMTExMTExMS5jb25zdWwwWTATBgcqhkjO\\nPQIBBggqhkjOPQMBBwNCAAR3uNYYt8amLQMfae6GpMyFaMBBkGL2ZPANmCy7nsL7\\n5kczishLb0GG1/PoNBQJW5A1Wl7uI/SE77KTThRxk3Wco4HzMIHwMA4GA1UdDwEB\\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\\nBAIwADApBgNVHQ4EIgQgYbec+6bte/VdH3M63TFVmDU0jH5461iZTsiJ1x+18iow\\nKwYDVR0jBCQwIoAgqo5xDZXH+SCNmEyBYOSyc8RlSBX3sJJrSLCtuZp5WxgwWQYD\\nVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1\\nNTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqGSM49\\nBAMCA0cAMEQCIC/xdLblMMnwXGqBn9XkdGOnUEtJW0LU+tKyen5PxRO7AiBjTefh\\n5uZU8QVs2FQTQHN0Omr4ngToHBHNwKl1Flvyqw==\\n-----END CERTIFICATE-----\\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\\nMHcCAQEEIP6roctlkGz+7od7hFwaldpvbnkXmnjvpPBxyKU4NcM9oAoGCCqGSM49\\nAwEHoUQDQgAEd7jWGLfGpi0DH2nuhqTMhWjAQZBi9mTwDZgsu57C++ZHM4rIS29B\\nhtfz6DQUCVuQNVpe7iP0hO+yk04UcZN1nA==\\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.filters.network.sni_cluster" + }, + { + "name": "envoy.tcp_proxy", + "config": { + "cluster": "", + "stat_prefix": "terminating_gateway_web_default_tcp" + } + } + ] + }, + { + "filterChainMatch": { + "serverNames": [ + "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "tlsContext": { + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\\nMIICKjCCAc+gAwIBAgIICeaPMbQdJsswCgYIKoZIzj0EAwIwFTETMBEGA1UEAxMK\\nVGVzdCBDQSA4NTAeFw0yMDA0MTEwMDE3NTZaFw0zMDA0MTEwMDE3NTZaMCoxKDAm\\nBgNVBAMTH2FwaS5zdmMuZGVmYXVsdC4xMTExMTExMS5jb25zdWwwWTATBgcqhkjO\\nPQIBBggqhkjOPQMBBwNCAASvkFCbA1rP8NxyKAOGoLmVjwSB+dO/ncs5KqourUPw\\nbZfQEETsTaTdO5aWgkJilagD7Z1RZltWk+MGhPleo8/bo4HzMIHwMA4GA1UdDwEB\\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\\nBAIwADApBgNVHQ4EIgQgyNkAhsJy93ueCk9Bjo9DdiZB+eJq7zs4qr9tmrT5zBAw\\nKwYDVR0jBCQwIoAgJDQM9fdkMlYIa/hmjVbXie/3qNMaAS8R9dKPQ2XE05gwWQYD\\nVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1\\nNTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvYXBpMAoGCCqGSM49\\nBAMCA0kAMEYCIQDN/60bmqjeFQpHw52r63Lftuuexl6AHVDI+o7MPYzfKwIhANMJ\\n0s1qRbOUItdIC8y0Ph2woXcj2yXluiPzFT3Ij94k\\n-----END CERTIFICATE-----\\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\\nMHcCAQEEIBdbcIKnjbzUBLHVQANB2P6bQf6SNOtEd6san+82wY21oAoGCCqGSM49\\nAwEHoUQDQgAEr5BQmwNaz/DccigDhqC5lY8EgfnTv53LOSqqLq1D8G2X0BBE7E2k\\n3TuWloJCYpWoA+2dUWZbVpPjBoT5XqPP2w==\\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.filters.network.sni_cluster" + }, + { + "name": "envoy.tcp_proxy", + "config": { + "cluster": "", + "stat_prefix": "terminating_gateway_api_default_tcp" + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.listener.tls_inspector" + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file