// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package xds import ( "fmt" envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/proxycfg" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/envoyextensions/xdscommon" ) type discoChainTargets struct { baseClusterName string targets []targetInfo failover bool failoverPolicy structs.ServiceResolverFailoverPolicy } type targetInfo struct { TargetID string TLSContext *envoy_tls_v3.UpstreamTlsContext // Region is the region from the failover target's Locality. nil means the // target is in the local Consul cluster. Region *string PrioritizeByLocality *structs.DiscoveryPrioritizeByLocality } type discoChainTargetGroup struct { Targets []targetInfo ClusterName string } func (ft discoChainTargets) groupedTargets() ([]discoChainTargetGroup, error) { var targetGroups []discoChainTargetGroup if !ft.failover { targetGroups = append(targetGroups, discoChainTargetGroup{ ClusterName: ft.baseClusterName, Targets: ft.targets, }) return targetGroups, nil } switch ft.failoverPolicy.Mode { case "sequential", "": return ft.sequential() case "order-by-locality": return ft.orderByLocality() default: return targetGroups, fmt.Errorf("unexpected failover policy") } } func (s *ResourceGenerator) mapDiscoChainTargets(cfgSnap *proxycfg.ConfigSnapshot, chain *structs.CompiledDiscoveryChain, node *structs.DiscoveryGraphNode, upstreamConfig structs.UpstreamConfig, forMeshGateway bool) (discoChainTargets, error) { failoverTargets := discoChainTargets{} if node.Resolver == nil { return discoChainTargets{}, fmt.Errorf("impossible to process a non-resolver node") } primaryTargetID := node.Resolver.Target upstreamsSnapshot, err := cfgSnap.ToConfigSnapshotUpstreams() if err != nil && !forMeshGateway { return discoChainTargets{}, err } failoverTargets.baseClusterName = s.getTargetClusterName(upstreamsSnapshot, chain, primaryTargetID, forMeshGateway) tids := []string{primaryTargetID} failover := node.Resolver.Failover if failover != nil && !forMeshGateway { tids = append(tids, failover.Targets...) failoverTargets.failover = true if failover.Policy == nil { failoverTargets.failoverPolicy = structs.ServiceResolverFailoverPolicy{} } else { failoverTargets.failoverPolicy = *failover.Policy } } for _, tid := range tids { target := chain.Targets[tid] var sni, rootPEMs string var spiffeIDs []string targetUID := proxycfg.NewUpstreamIDFromTargetID(tid) ti := targetInfo{TargetID: tid, PrioritizeByLocality: target.PrioritizeByLocality} configureTLS := true if forMeshGateway { // We only initiate TLS if we're doing an L7 proxy. configureTLS = structs.IsProtocolHTTPLike(upstreamConfig.Protocol) } if !configureTLS { failoverTargets.targets = append(failoverTargets.targets, ti) continue } if targetUID.Peer != "" { tbs, _ := upstreamsSnapshot.UpstreamPeerTrustBundles.Get(targetUID.Peer) rootPEMs = tbs.ConcatenatedRootPEMs() peerMeta, found := upstreamsSnapshot.UpstreamPeerMeta(targetUID) if !found { s.Logger.Warn("failed to fetch upstream peering metadata", "target", targetUID) continue } sni = peerMeta.PrimarySNI() spiffeIDs = peerMeta.SpiffeID region := target.Locality.GetRegion() ti.Region = ®ion } else { sni = target.SNI rootPEMs = cfgSnap.RootPEMs() spiffeIDs = []string{connect.SpiffeIDService{ Host: cfgSnap.Roots.TrustDomain, Namespace: target.Namespace, Partition: target.Partition, Datacenter: target.Datacenter, Service: target.Service, }.URI().String()} } commonTLSContext := makeCommonTLSContext( cfgSnap.Leaf(), rootPEMs, makeTLSParametersFromProxyTLSConfig(cfgSnap.MeshConfigTLSOutgoing()), ) err := injectSANMatcher(commonTLSContext, false, spiffeIDs...) if err != nil { return failoverTargets, fmt.Errorf("failed to inject SAN matcher rules for cluster %q: %v", sni, err) } tlsContext := &envoy_tls_v3.UpstreamTlsContext{ CommonTlsContext: commonTLSContext, Sni: sni, } ti.TLSContext = tlsContext failoverTargets.targets = append(failoverTargets.targets, ti) } return failoverTargets, nil } func (ft discoChainTargets) sequential() ([]discoChainTargetGroup, error) { var targetGroups []discoChainTargetGroup for i, t := range ft.targets { targetGroups = append(targetGroups, discoChainTargetGroup{ ClusterName: fmt.Sprintf("%s%d~%s", xdscommon.FailoverClusterNamePrefix, i, ft.baseClusterName), Targets: []targetInfo{t}, }) } return targetGroups, nil }