From 913b13f31fa42b2d570d7ca4dc15b95b88c8cea7 Mon Sep 17 00:00:00 2001 From: freddygv Date: Tue, 14 Apr 2020 08:59:23 -0600 Subject: [PATCH] Add subset support --- agent/proxycfg/snapshot.go | 13 +- agent/proxycfg/state.go | 47 ++++ agent/proxycfg/state_test.go | 60 +++- agent/proxycfg/testing.go | 6 + agent/xds/clusters.go | 47 ++-- agent/xds/clusters_test.go | 57 ++++ agent/xds/endpoints.go | 51 ++-- agent/xds/endpoints_test.go | 70 +++++ agent/xds/listeners.go | 63 +++-- agent/xds/listeners_test.go | 34 +++ ...ting-gateway-ignore-extra-resolvers.golden | 71 +++++ ...terminating-gateway-service-subsets.golden | 71 +++++ ...ting-gateway-default-service-subset.golden | 107 +++++++ ...terminating-gateway-service-subsets.golden | 119 ++++++++ ...terminating-gateway-service-subsets.golden | 260 ++++++++++++++++++ 15 files changed, 1007 insertions(+), 69 deletions(-) create mode 100644 agent/xds/testdata/clusters/terminating-gateway-ignore-extra-resolvers.golden create mode 100644 agent/xds/testdata/clusters/terminating-gateway-service-subsets.golden create mode 100644 agent/xds/testdata/endpoints/terminating-gateway-default-service-subset.golden create mode 100644 agent/xds/testdata/endpoints/terminating-gateway-service-subsets.golden create mode 100644 agent/xds/testdata/listeners/terminating-gateway-service-subsets.golden diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index 1adea131f2..a370dec371 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -81,6 +81,16 @@ type configSnapshotTerminatingGateway struct { // on the service that the caller is trying to reach. ServiceLeaves map[structs.ServiceID]*structs.IssuedCert + // WatchedResolvers is a map of ServiceID to a cancel function. + // This cancel function is tied to the watch of resolvers for linked services. + // As with WatchedServices, resolver watches will be cancelled when services + // are no longer linked to the gateway. + WatchedResolvers map[structs.ServiceID]context.CancelFunc + + // ServiceResolvers is a map of service id to an associated + // service-resolver config entry for that service. + ServiceResolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry + // ServiceGroups is a map of service id to the service instances of that // service in the local datacenter. ServiceGroups map[structs.ServiceID]structs.CheckServiceNodes @@ -94,7 +104,8 @@ func (c *configSnapshotTerminatingGateway) IsEmpty() bool { len(c.WatchedLeaves) == 0 && len(c.WatchedIntentions) == 0 && len(c.ServiceGroups) == 0 && - len(c.WatchedServices) == 0 + len(c.WatchedServices) == 0 && + len(c.ServiceResolvers) == 0 } type configSnapshotMeshGateway struct { diff --git a/agent/proxycfg/state.go b/agent/proxycfg/state.go index dccd42c4cc..b73614d21d 100644 --- a/agent/proxycfg/state.go +++ b/agent/proxycfg/state.go @@ -536,8 +536,10 @@ func (s *state) initialConfigSnapshot() ConfigSnapshot { snap.TerminatingGateway.WatchedServices = make(map[structs.ServiceID]context.CancelFunc) snap.TerminatingGateway.WatchedLeaves = make(map[structs.ServiceID]context.CancelFunc) snap.TerminatingGateway.WatchedIntentions = make(map[structs.ServiceID]context.CancelFunc) + snap.TerminatingGateway.WatchedResolvers = make(map[structs.ServiceID]context.CancelFunc) snap.TerminatingGateway.ServiceLeaves = make(map[structs.ServiceID]*structs.IssuedCert) snap.TerminatingGateway.ServiceGroups = make(map[structs.ServiceID]structs.CheckServiceNodes) + snap.TerminatingGateway.ServiceResolvers = make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry) case structs.ServiceKindMeshGateway: snap.MeshGateway.WatchedServices = make(map[structs.ServiceID]context.CancelFunc) snap.MeshGateway.WatchedDatacenters = make(map[string]context.CancelFunc) @@ -983,6 +985,29 @@ func (s *state) handleUpdateTerminatingGateway(u cache.UpdateEvent, snap *Config } snap.TerminatingGateway.WatchedLeaves[svc.Service] = cancel } + + // Watch service resolvers for the service + // These are used to create clusters and endpoints for the service subsets + if _, ok := snap.TerminatingGateway.WatchedResolvers[svc.Service]; !ok { + ctx, cancel := context.WithCancel(s.ctx) + err := s.cache.Notify(ctx, cachetype.ConfigEntriesName, &structs.ConfigEntryQuery{ + Datacenter: s.source.Datacenter, + QueryOptions: structs.QueryOptions{Token: s.token}, + Kind: structs.ServiceResolver, + Name: svc.Service.ID, + EnterpriseMeta: svc.Service.EnterpriseMeta, + }, fmt.Sprintf("service-resolver:%s", svc.Service.String()), s.ch) + + if err != nil { + logger.Error("failed to register watch for a service-resolver", + "service", svc.Service.String(), + "error", err, + ) + cancel() + return err + } + snap.TerminatingGateway.WatchedResolvers[svc.Service] = cancel + } } // Cancel service instance watches for services that were not in the update @@ -1005,6 +1030,16 @@ func (s *state) handleUpdateTerminatingGateway(u cache.UpdateEvent, snap *Config } } + // Cancel service-resolver watches for services that were not in the update + for sid, cancelFn := range snap.TerminatingGateway.WatchedResolvers { + if _, ok := svcMap[sid]; !ok { + logger.Debug("canceling watch for service-resolver", "service", sid.String()) + delete(snap.TerminatingGateway.WatchedResolvers, sid) + delete(snap.TerminatingGateway.ServiceResolvers, sid) + cancelFn() + } + } + // Cancel intention watches for services that were not in the update for sid, cancelFn := range snap.TerminatingGateway.WatchedIntentions { if _, ok := svcMap[sid]; !ok { @@ -1044,6 +1079,18 @@ func (s *state) handleUpdateTerminatingGateway(u cache.UpdateEvent, snap *Config sid := structs.ServiceIDFromString(strings.TrimPrefix(u.CorrelationID, "service-leaf:")) snap.TerminatingGateway.ServiceLeaves[sid] = leaf + case strings.HasPrefix(u.CorrelationID, "service-resolver:"): + configEntries, ok := u.Result.(*structs.IndexedConfigEntries) + if !ok { + return fmt.Errorf("invalid type for response: %T", u.Result) + } + // There should only ever be one entry for a service resolver within a namespace + if len(configEntries.Entries) == 1 { + if resolver, ok := configEntries.Entries[0].(*structs.ServiceResolverConfigEntry); ok { + snap.TerminatingGateway.ServiceResolvers[structs.NewServiceID(resolver.Name, &resolver.EnterpriseMeta)] = resolver + } + } + case strings.HasPrefix(u.CorrelationID, "service-intentions:"): // no-op: Intentions don't get stored in the snapshot, calls to ConnectAuthorize will fetch them from the cache diff --git a/agent/proxycfg/state_test.go b/agent/proxycfg/state_test.go index 6261f4bcce..acaf83be7b 100644 --- a/agent/proxycfg/state_test.go +++ b/agent/proxycfg/state_test.go @@ -206,6 +206,18 @@ func genVerifyLeafWatch(expectedService string, expectedDatacenter string) verif } } +func genVerifyResolverWatch(expectedService, expectedDatacenter, expectedKind string) verifyWatchRequest { + return func(t testing.TB, cacheType string, request cache.Request) { + require.Equal(t, cachetype.ConfigEntriesName, cacheType) + + reqReal, ok := request.(*structs.ConfigEntryQuery) + require.True(t, ok) + require.Equal(t, expectedDatacenter, reqReal.Datacenter) + require.Equal(t, expectedService, reqReal.Name) + require.Equal(t, expectedKind, reqReal.Kind) + } +} + func genVerifyIntentionWatch(expectedService string, expectedDatacenter string) verifyWatchRequest { return func(t testing.TB, cacheType string, request cache.Request) { require.Equal(t, cachetype.IntentionMatchName, cacheType) @@ -827,8 +839,11 @@ func TestState_WatchesAndUpdates(t *testing.T) { require.True(t, snap.ConnectProxy.IsEmpty()) require.Equal(t, indexedRoots, snap.Roots) require.Empty(t, snap.TerminatingGateway.WatchedServices) - require.Empty(t, snap.TerminatingGateway.WatchedLeaves) require.Empty(t, snap.TerminatingGateway.ServiceGroups) + require.Empty(t, snap.TerminatingGateway.WatchedLeaves) + require.Empty(t, snap.TerminatingGateway.ServiceLeaves) + require.Empty(t, snap.TerminatingGateway.WatchedResolvers) + require.Empty(t, snap.TerminatingGateway.ServiceResolvers) require.Empty(t, snap.TerminatingGateway.WatchedIntentions) }, }, @@ -904,6 +919,10 @@ func TestState_WatchesAndUpdates(t *testing.T) { require.Len(t, snap.TerminatingGateway.WatchedLeaves, 2) require.Contains(t, snap.TerminatingGateway.WatchedLeaves, db) require.Contains(t, snap.TerminatingGateway.WatchedLeaves, billing) + + require.Len(t, snap.TerminatingGateway.WatchedResolvers, 2) + require.Contains(t, snap.TerminatingGateway.WatchedResolvers, db) + require.Contains(t, snap.TerminatingGateway.WatchedResolvers, billing) }, }, verificationStage{ @@ -963,6 +982,41 @@ func TestState_WatchesAndUpdates(t *testing.T) { require.Equal(t, snap.TerminatingGateway.ServiceLeaves[structs.NewServiceID("db", nil)], issuedCert) }, }, + verificationStage{ + requiredWatches: map[string]verifyWatchRequest{ + "service-resolver:db": genVerifyResolverWatch("db", "dc1", structs.ServiceResolver), + }, + events: []cache.UpdateEvent{ + cache.UpdateEvent{ + CorrelationID: "service-resolver:db", + Result: &structs.IndexedConfigEntries{ + Kind: structs.ServiceResolver, + Entries: []structs.ConfigEntry{ + &structs.ServiceResolverConfigEntry{ + Name: "db", + Kind: structs.ServiceResolver, + Redirect: &structs.ServiceResolverRedirect{ + Service: "db", + Datacenter: "dc2", + }, + }, + }, + }, + Err: nil, + }, + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + want := &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "db", + Redirect: &structs.ServiceResolverRedirect{ + Service: "db", + Datacenter: "dc2", + }, + } + require.Equal(t, want, snap.TerminatingGateway.ServiceResolvers[structs.NewServiceID("db", nil)]) + }, + }, verificationStage{ events: []cache.UpdateEvent{ cache.UpdateEvent{ @@ -993,9 +1047,13 @@ func TestState_WatchesAndUpdates(t *testing.T) { require.Len(t, snap.TerminatingGateway.WatchedLeaves, 1) require.Contains(t, snap.TerminatingGateway.WatchedLeaves, billing) + require.Len(t, snap.TerminatingGateway.WatchedResolvers, 1) + require.Contains(t, snap.TerminatingGateway.WatchedResolvers, billing) + // There was no update event for billing's leaf/endpoints, so length is 0 require.Len(t, snap.TerminatingGateway.ServiceGroups, 0) require.Len(t, snap.TerminatingGateway.ServiceLeaves, 0) + require.Len(t, snap.TerminatingGateway.ServiceResolvers, 0) }, }, }, diff --git a/agent/proxycfg/testing.go b/agent/proxycfg/testing.go index bc73c9f6b2..9b2667dd39 100644 --- a/agent/proxycfg/testing.go +++ b/agent/proxycfg/testing.go @@ -1475,6 +1475,12 @@ func testConfigSnapshotTerminatingGateway(t testing.T, populateServices bool) *C if populateServices { web := structs.NewServiceID("web", nil) webNodes := TestUpstreamNodes(t) + webNodes[0].Service.Meta = map[string]string{ + "version": "1", + } + webNodes[1].Service.Meta = map[string]string{ + "version": "2", + } api := structs.NewServiceID("api", nil) apiNodes := TestUpstreamNodes(t) diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index b9cc587239..449a76f21d 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -120,22 +120,9 @@ func makeExposeClusterName(destinationPort int) string { } // clustersFromSnapshotTerminatingGateway returns the xDS API representation of the "clusters" -// for a terminating gateway. This will include 1 cluster per service. +// for a terminating gateway. This will include 1 cluster per service and service subset. 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 + return s.clustersFromServicesAndResolvers(cfgSnap, cfgSnap.TerminatingGateway.ServiceGroups, cfgSnap.TerminatingGateway.ServiceResolvers) } // clustersFromSnapshotMeshGateway returns the xDS API representation of the "clusters" @@ -185,10 +172,26 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho } } - // generate the per-service clusters - for svc, _ := range cfgSnap.MeshGateway.ServiceGroups { + // generate the per-service/subset clusters + c, err := s.clustersFromServicesAndResolvers(cfgSnap, cfgSnap.MeshGateway.ServiceGroups, cfgSnap.MeshGateway.ServiceResolvers) + if err != nil { + return nil, err + } + clusters = append(clusters, c...) + + return clusters, nil +} + +func (s *Server) clustersFromServicesAndResolvers( + cfgSnap *proxycfg.ConfigSnapshot, + services map[structs.ServiceID]structs.CheckServiceNodes, + resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry) ([]proto.Message, error) { + + clusters := make([]proto.Message, 0, len(services)) + + for svc, _ := range services { clusterName := connect.ServiceSNI(svc.ID, "", svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) - resolver, hasResolver := cfgSnap.MeshGateway.ServiceResolvers[svc] + resolver, hasResolver := resolvers[svc] // Create the cluster for default/unnamed services var cluster *envoy.Cluster @@ -199,7 +202,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho cluster, err = s.makeGatewayCluster(clusterName, cfgSnap) } if err != nil { - return nil, err + return nil, fmt.Errorf("failed to make %s cluster: %v", cfgSnap.Kind, err) } clusters = append(clusters, cluster) @@ -211,7 +214,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho cluster, err := s.makeGatewayClusterWithConnectTimeout(clusterName, cfgSnap, resolver.ConnectTimeout) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to make %s cluster: %v", cfgSnap.Kind, err) } clusters = append(clusters, cluster) } @@ -529,9 +532,9 @@ func (s *Server) makeGatewayCluster(clusterName string, cfgSnap *proxycfg.Config return s.makeGatewayClusterWithConnectTimeout(clusterName, cfgSnap, 0) } -// makeGatewayClusterWithConnectTimeout initializes a mesh gateway cluster +// makeGatewayClusterWithConnectTimeout initializes a gateway cluster // with the specified connect timeout. If the timeout is 0, the connect timeout -// defaults to use the mesh gateway timeout. +// defaults to use the configured gateway timeout. func (s *Server) makeGatewayClusterWithConnectTimeout(clusterName string, cfgSnap *proxycfg.ConfigSnapshot, connectTimeout time.Duration) (*envoy.Cluster, error) { cfg, err := ParseGatewayConfig(cfgSnap.Proxy.Config) diff --git a/agent/xds/clusters_test.go b/agent/xds/clusters_test.go index 223fbe7880..0e4bb264f1 100644 --- a/agent/xds/clusters_test.go +++ b/agent/xds/clusters_test.go @@ -428,6 +428,63 @@ func TestClustersFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotTerminatingGatewayNoServices, setup: nil, }, + { + name: "terminating-gateway-service-subsets", + create: proxycfg.TestConfigSnapshotTerminatingGateway, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.TerminatingGateway.ServiceResolvers = map[structs.ServiceID]*structs.ServiceResolverConfigEntry{ + structs.NewServiceID("web", nil): { + Kind: structs.ServiceResolver, + Name: "web", + Subsets: map[string]structs.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.Version == 1", + }, + "v2": { + Filter: "Service.Meta.Version == 2", + OnlyPassing: true, + }, + }, + }, + } + }, + }, + { + name: "terminating-gateway-ignore-extra-resolvers", + create: proxycfg.TestConfigSnapshotTerminatingGateway, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.TerminatingGateway.ServiceResolvers = map[structs.ServiceID]*structs.ServiceResolverConfigEntry{ + structs.NewServiceID("web", nil): { + Kind: structs.ServiceResolver, + Name: "web", + DefaultSubset: "v2", + Subsets: map[string]structs.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.Version == 1", + }, + "v2": { + Filter: "Service.Meta.Version == 2", + OnlyPassing: true, + }, + }, + }, + structs.NewServiceID("notfound", nil): { + Kind: structs.ServiceResolver, + Name: "notfound", + DefaultSubset: "v2", + Subsets: map[string]structs.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.Version == 1", + }, + "v2": { + Filter: "Service.Meta.Version == 2", + OnlyPassing: true, + }, + }, + }, + } + }, + }, } for _, tt := range tests { diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index 1fef77c43f..b82fb16cbd 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -108,19 +108,7 @@ func (s *Server) filterSubsetEndpoints(subset *structs.ServiceResolverSubset, en } 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 + return s.endpointsFromServicesAndResolvers(cfgSnap, cfgSnap.TerminatingGateway.ServiceGroups, cfgSnap.TerminatingGateway.ServiceResolvers) } func (s *Server) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { @@ -209,38 +197,53 @@ func (s *Server) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsh } // Generate the endpoints for each service and its subsets - for svc, endpoints := range cfgSnap.MeshGateway.ServiceGroups { - clusterEndpoints := make(map[string]loadAssignmentEndpointGroup) - clusterEndpoints[UnnamedSubset] = loadAssignmentEndpointGroup{Endpoints: endpoints, OnlyPassing: false} + e, err := s.endpointsFromServicesAndResolvers(cfgSnap, cfgSnap.MeshGateway.ServiceGroups, cfgSnap.MeshGateway.ServiceResolvers) + if err != nil { + return nil, err + } + resources = append(resources, e...) + + return resources, nil +} + +func (s *Server) endpointsFromServicesAndResolvers( + cfgSnap *proxycfg.ConfigSnapshot, + services map[structs.ServiceID]structs.CheckServiceNodes, + resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry) ([]proto.Message, error) { + + resources := make([]proto.Message, 0, len(services)) + + // generate the endpoints for the linked service groups + for svc, endpoints := range services { + clusterEndpoints := make(map[string][]loadAssignmentEndpointGroup) + clusterEndpoints[UnnamedSubset] = []loadAssignmentEndpointGroup{{Endpoints: endpoints, OnlyPassing: false}} // Collect all of the loadAssignmentEndpointGroups for the various subsets. We do this before generating // the endpoints for the default/unnamed subset so that we can take into account the DefaultSubset on the // service-resolver which may prevent the default/unnamed cluster from creating endpoints for all service // instances. - if resolver, hasResolver := cfgSnap.MeshGateway.ServiceResolvers[svc]; hasResolver { + if resolver, hasResolver := resolvers[svc]; hasResolver { for subsetName, subset := range resolver.Subsets { subsetEndpoints, err := s.filterSubsetEndpoints(&subset, endpoints) if err != nil { return nil, err } - group := loadAssignmentEndpointGroup{Endpoints: subsetEndpoints, OnlyPassing: subset.OnlyPassing} - clusterEndpoints[subsetName] = group + groups := []loadAssignmentEndpointGroup{{Endpoints: subsetEndpoints, OnlyPassing: subset.OnlyPassing}} + clusterEndpoints[subsetName] = groups // if this subset is the default then override the unnamed subset with this configuration if subsetName == resolver.DefaultSubset { - clusterEndpoints[UnnamedSubset] = group + clusterEndpoints[UnnamedSubset] = groups } } } // now generate the load assignment for all subsets - for subsetName, group := range clusterEndpoints { + for subsetName, groups := range clusterEndpoints { clusterName := connect.ServiceSNI(svc.ID, subsetName, svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) la := makeLoadAssignment( clusterName, - []loadAssignmentEndpointGroup{ - group, - }, + groups, cfgSnap.Datacenter, ) resources = append(resources, la) diff --git a/agent/xds/endpoints_test.go b/agent/xds/endpoints_test.go index e39b93a51a..e5c08c1638 100644 --- a/agent/xds/endpoints_test.go +++ b/agent/xds/endpoints_test.go @@ -466,6 +466,76 @@ func Test_endpointsFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotTerminatingGatewayNoServices, setup: nil, }, + { + name: "terminating-gateway-service-subsets", + create: proxycfg.TestConfigSnapshotTerminatingGateway, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.TerminatingGateway.ServiceResolvers = map[structs.ServiceID]*structs.ServiceResolverConfigEntry{ + structs.NewServiceID("web", nil): { + Kind: structs.ServiceResolver, + Name: "web", + Subsets: map[string]structs.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.version == 1", + }, + "v2": { + Filter: "Service.Meta.version == 2", + OnlyPassing: true, + }, + }, + }, + structs.NewServiceID("web", nil): { + Kind: structs.ServiceResolver, + Name: "web", + Subsets: map[string]structs.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.version == 1", + }, + "v2": { + Filter: "Service.Meta.version == 2", + OnlyPassing: true, + }, + }, + }, + } + }, + }, + { + name: "terminating-gateway-default-service-subset", + create: proxycfg.TestConfigSnapshotTerminatingGateway, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.TerminatingGateway.ServiceResolvers = map[structs.ServiceID]*structs.ServiceResolverConfigEntry{ + structs.NewServiceID("web", nil): &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "web", + DefaultSubset: "v2", + Subsets: map[string]structs.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.version == 1", + }, + "v2": { + Filter: "Service.Meta.version == 2", + OnlyPassing: true, + }, + }, + }, + structs.NewServiceID("web", nil): &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "web", + DefaultSubset: "v2", + Subsets: map[string]structs.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.version == 1", + }, + "v2": { + Filter: "Service.Meta.version == 2", + OnlyPassing: true, + }, + }, + }, + } + }, + }, } for _, tt := range tests { diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index 408230faef..573a410604 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -562,35 +562,31 @@ func (s *Server) makeTerminatingGatewayListener(name, addr string, port int, cfg } 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) + resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc] - // 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)) + clusterChain, err := s.sniFilterChainTerminatingGateway(name, clusterName, svc, cfgSnap) 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}, - }, + return nil, fmt.Errorf("failed to make filter chain for cluster %q: %v", clusterName, err) } l.FilterChains = append(l.FilterChains, clusterChain) + + // if there is a service-resolver for this service then also setup subset filter chains for it + if hasResolver { + // generate 1 filter chain for each service subset + for subsetName := range resolver.Subsets { + clusterName := connect.ServiceSNI(svc.ID, subsetName, svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) + + clusterChain, err := s.sniFilterChainTerminatingGateway(name, clusterName, svc, cfgSnap) + if err != nil { + return nil, fmt.Errorf("failed to make filter chain for cluster %q: %v", clusterName, err) + } + l.FilterChains = append(l.FilterChains, clusterChain) + } + } } err = injectConnectFilters(cfgSnap, token, l, false) @@ -598,6 +594,31 @@ func (s *Server) makeTerminatingGatewayListener(name, addr string, port int, cfg return l, nil } +func (s *Server) sniFilterChainTerminatingGateway(listener, cluster string, service structs.ServiceID, cfgSnap *proxycfg.ConfigSnapshot) (envoylistener.FilterChain, error) { + sniCluster, err := makeSNIClusterFilter() + if err != nil { + return envoylistener.FilterChain{}, err + } + + // The cluster name here doesn't matter as the sni_cluster filter will fill it in for us. + tcpProxy, err := makeTCPProxyFilter(listener, "", fmt.Sprintf("terminating_gateway_%s_", service.String())) + if err != nil { + return envoylistener.FilterChain{}, err + } + + return envoylistener.FilterChain{ + FilterChainMatch: makeSNIFilterChainMatch(cluster), + Filters: []envoylistener.Filter{ + sniCluster, + tcpProxy, + }, + TlsContext: &envoyauth.DownstreamTlsContext{ + CommonTlsContext: makeCommonTLSContext(cfgSnap, cfgSnap.TerminatingGateway.ServiceLeaves[service]), + RequireClientCertificate: &types.BoolValue{Value: true}, + }, + }, err +} + func (s *Server) makeMeshGatewayListener(name, addr string, port int, cfgSnap *proxycfg.ConfigSnapshot) (*envoy.Listener, error) { tlsInspector, err := makeTLSInspectorListenerFilter() if err != nil { diff --git a/agent/xds/listeners_test.go b/agent/xds/listeners_test.go index a2d3023f6c..841e4bb14b 100644 --- a/agent/xds/listeners_test.go +++ b/agent/xds/listeners_test.go @@ -329,6 +329,40 @@ func TestListenersFromSnapshot(t *testing.T) { } }, }, + { + name: "terminating-gateway-service-subsets", + create: proxycfg.TestConfigSnapshotTerminatingGateway, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.TerminatingGateway.ServiceResolvers = map[structs.ServiceID]*structs.ServiceResolverConfigEntry{ + structs.NewServiceID("web", nil): { + Kind: structs.ServiceResolver, + Name: "web", + Subsets: map[string]structs.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.version == 1", + }, + "v2": { + Filter: "Service.Meta.version == 2", + OnlyPassing: true, + }, + }, + }, + structs.NewServiceID("web", nil): { + Kind: structs.ServiceResolver, + Name: "web", + Subsets: map[string]structs.ServiceResolverSubset{ + "v1": { + Filter: "Service.Meta.version == 1", + }, + "v2": { + Filter: "Service.Meta.version == 2", + OnlyPassing: true, + }, + }, + }, + } + }, + }, } for _, tt := range tests { diff --git a/agent/xds/testdata/clusters/terminating-gateway-ignore-extra-resolvers.golden b/agent/xds/testdata/clusters/terminating-gateway-ignore-extra-resolvers.golden new file mode 100644 index 0000000000..f35caea5fc --- /dev/null +++ b/agent/xds/testdata/clusters/terminating-gateway-ignore-extra-resolvers.golden @@ -0,0 +1,71 @@ +{ + "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": "v1.web.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": "v2.web.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/clusters/terminating-gateway-service-subsets.golden b/agent/xds/testdata/clusters/terminating-gateway-service-subsets.golden new file mode 100644 index 0000000000..f35caea5fc --- /dev/null +++ b/agent/xds/testdata/clusters/terminating-gateway-service-subsets.golden @@ -0,0 +1,71 @@ +{ + "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": "v1.web.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": "v2.web.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-default-service-subset.golden b/agent/xds/testdata/endpoints/terminating-gateway-default-service-subset.golden new file mode 100644 index 0000000000..85b000afba --- /dev/null +++ b/agent/xds/testdata/endpoints/terminating-gateway-default-service-subset.golden @@ -0,0 +1,107 @@ +{ + "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": "v1.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 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment", + "clusterName": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "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.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/endpoints/terminating-gateway-service-subsets.golden b/agent/xds/testdata/endpoints/terminating-gateway-service-subsets.golden new file mode 100644 index 0000000000..da06247960 --- /dev/null +++ b/agent/xds/testdata/endpoints/terminating-gateway-service-subsets.golden @@ -0,0 +1,119 @@ +{ + "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": "v1.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 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment", + "clusterName": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.10.1.2", + "portValue": 8080 + } + } + }, + "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-service-subsets.golden b/agent/xds/testdata/listeners/terminating-gateway-service-subsets.golden new file mode 100644 index 0000000000..4973fc4ade --- /dev/null +++ b/agent/xds/testdata/listeners/terminating-gateway-service-subsets.golden @@ -0,0 +1,260 @@ +{ + "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": [ + "v1.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": [ + "v2.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