f3c80c4eef
Protobuf Refactoring for Multi-Module Cleanliness This commit includes the following: Moves all packages that were within proto/ to proto/private Rewrites imports to account for the packages being moved Adds in buf.work.yaml to enable buf workspaces Names the proto-public buf module so that we can override the Go package imports within proto/buf.yaml Bumps the buf version dependency to 1.14.0 (I was trying out the version to see if it would get around an issue - it didn't but it also doesn't break things and it seemed best to keep up with the toolchain changes) Why: In the future we will need to consume other protobuf dependencies such as the Google HTTP annotations for openapi generation or grpc-gateway usage. There were some recent changes to have our own ratelimiting annotations. The two combined were not working when I was trying to use them together (attempting to rebase another branch) Buf workspaces should be the solution to the problem Buf workspaces means that each module will have generated Go code that embeds proto file names relative to the proto dir and not the top level repo root. This resulted in proto file name conflicts in the Go global protobuf type registry. The solution to that was to add in a private/ directory into the path within the proto/ directory. That then required rewriting all the imports. Is this safe? AFAICT yes The gRPC wire protocol doesn't seem to care about the proto file names (although the Go grpc code does tack on the proto file name as Metadata in the ServiceDesc) Other than imports, there were no changes to any generated code as a result of this.
1257 lines
43 KiB
Go
1257 lines
43 KiB
Go
package proxycfg
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
"github.com/hashicorp/consul/agent/connect"
|
|
"github.com/hashicorp/consul/agent/consul/discoverychain"
|
|
"github.com/hashicorp/consul/agent/proxycfg/internal/watch"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/lib"
|
|
"github.com/hashicorp/consul/proto/private/pbpeering"
|
|
)
|
|
|
|
// TODO(ingress): Can we think of a better for this bag of data?
|
|
// A shared data structure that contains information about discovered upstreams
|
|
type ConfigSnapshotUpstreams struct {
|
|
Leaf *structs.IssuedCert
|
|
|
|
MeshConfig *structs.MeshConfigEntry
|
|
MeshConfigSet bool
|
|
|
|
// DiscoveryChain is a map of UpstreamID -> CompiledDiscoveryChain's, and
|
|
// is used to determine what services could be targeted by this upstream.
|
|
// We then instantiate watches for those targets.
|
|
DiscoveryChain map[UpstreamID]*structs.CompiledDiscoveryChain
|
|
|
|
// WatchedDiscoveryChains is a map of UpstreamID -> CancelFunc's
|
|
// in order to cancel any watches when the proxy's configuration is
|
|
// changed. Ingress gateways and transparent proxies need this because
|
|
// discovery chain watches are added and removed through the lifecycle
|
|
// of a single proxycfg state instance.
|
|
WatchedDiscoveryChains map[UpstreamID]context.CancelFunc
|
|
|
|
// WatchedUpstreams is a map of UpstreamID -> (map of TargetID ->
|
|
// CancelFunc's) in order to cancel any watches when the configuration is
|
|
// changed.
|
|
WatchedUpstreams map[UpstreamID]map[string]context.CancelFunc
|
|
|
|
// WatchedUpstreamEndpoints is a map of UpstreamID -> (map of
|
|
// TargetID -> CheckServiceNodes) and is used to determine the backing
|
|
// endpoints of an upstream.
|
|
WatchedUpstreamEndpoints map[UpstreamID]map[string]structs.CheckServiceNodes
|
|
|
|
// UpstreamPeerTrustBundles is a map of (PeerName -> PeeringTrustBundle).
|
|
// It is used to store trust bundles for upstream TLS transport sockets.
|
|
UpstreamPeerTrustBundles watch.Map[PeerName, *pbpeering.PeeringTrustBundle]
|
|
|
|
// WatchedGateways is a map of UpstreamID -> (map of GatewayKey.String() ->
|
|
// CancelFunc) in order to cancel watches for mesh gateways
|
|
WatchedGateways map[UpstreamID]map[string]context.CancelFunc
|
|
|
|
// WatchedGatewayEndpoints is a map of UpstreamID -> (map of
|
|
// GatewayKey.String() -> CheckServiceNodes) and is used to determine the
|
|
// backing endpoints of a mesh gateway.
|
|
WatchedGatewayEndpoints map[UpstreamID]map[string]structs.CheckServiceNodes
|
|
|
|
// WatchedLocalGWEndpoints is used to store the backing endpoints of
|
|
// a local mesh gateway. Currently, this is used by peered upstreams
|
|
// configured with local mesh gateway mode so that they can watch for
|
|
// gateway endpoints.
|
|
//
|
|
// Note that the string form of GatewayKey is used as the key so empty
|
|
// fields can be normalized in OSS.
|
|
// GatewayKey.String() -> structs.CheckServiceNodes
|
|
WatchedLocalGWEndpoints watch.Map[string, structs.CheckServiceNodes]
|
|
|
|
// UpstreamConfig is a map to an upstream's configuration.
|
|
UpstreamConfig map[UpstreamID]*structs.Upstream
|
|
|
|
// PassthroughEndpoints is a map of: UpstreamID -> (map of TargetID ->
|
|
// (set of IP addresses)). It contains the upstream endpoints that
|
|
// can be dialed directly by a transparent proxy.
|
|
PassthroughUpstreams map[UpstreamID]map[string]map[string]struct{}
|
|
|
|
// PassthroughIndices is a map of: address -> indexedTarget.
|
|
// It is used to track the modify index associated with a passthrough address.
|
|
// Tracking this index helps break ties when a single address is shared by
|
|
// more than one upstream due to a race.
|
|
PassthroughIndices map[string]indexedTarget
|
|
|
|
// IntentionUpstreams is a set of upstreams inferred from intentions.
|
|
//
|
|
// This list only applies to proxies registered in 'transparent' mode.
|
|
IntentionUpstreams map[UpstreamID]struct{}
|
|
|
|
// PeeredUpstreams is a set of all upstream targets in a local partition.
|
|
//
|
|
// This list only applies to proxies registered in 'transparent' mode.
|
|
PeeredUpstreams map[UpstreamID]struct{}
|
|
|
|
// PeerUpstreamEndpoints is a map of UpstreamID -> (set of IP addresses)
|
|
// and used to determine the backing endpoints of an upstream in another
|
|
// peer.
|
|
PeerUpstreamEndpoints watch.Map[UpstreamID, structs.CheckServiceNodes]
|
|
|
|
PeerUpstreamEndpointsUseHostnames map[UpstreamID]struct{}
|
|
}
|
|
|
|
// indexedTarget is used to associate the Raft modify index of a resource
|
|
// with the corresponding upstream target.
|
|
type indexedTarget struct {
|
|
upstreamID UpstreamID
|
|
targetID string
|
|
idx uint64
|
|
}
|
|
|
|
type GatewayKey struct {
|
|
Datacenter string
|
|
Partition string
|
|
}
|
|
|
|
func (k GatewayKey) String() string {
|
|
resp := k.Datacenter
|
|
if !acl.IsDefaultPartition(k.Partition) {
|
|
resp = k.Partition + "." + resp
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func (k GatewayKey) IsEmpty() bool {
|
|
return k.Partition == "" && k.Datacenter == ""
|
|
}
|
|
|
|
func (k GatewayKey) Matches(dc, partition string) bool {
|
|
return acl.EqualPartitions(k.Partition, partition) && k.Datacenter == dc
|
|
}
|
|
|
|
func gatewayKeyFromString(s string) GatewayKey {
|
|
split := strings.SplitN(s, ".", 2)
|
|
|
|
if len(split) == 1 {
|
|
return GatewayKey{Datacenter: split[0], Partition: acl.DefaultPartitionName}
|
|
}
|
|
return GatewayKey{Partition: split[0], Datacenter: split[1]}
|
|
}
|
|
|
|
type configSnapshotConnectProxy struct {
|
|
ConfigSnapshotUpstreams
|
|
|
|
InboundPeerTrustBundlesSet bool
|
|
InboundPeerTrustBundles []*pbpeering.PeeringTrustBundle
|
|
|
|
WatchedServiceChecks map[structs.ServiceID][]structs.CheckType // TODO: missing garbage collection
|
|
PreparedQueryEndpoints map[UpstreamID]structs.CheckServiceNodes // DEPRECATED:see:WatchedUpstreamEndpoints
|
|
|
|
// NOTE: Intentions stores a list of lists as returned by the Intentions
|
|
// Match RPC. So far we only use the first list as the list of matching
|
|
// intentions.
|
|
Intentions structs.Intentions
|
|
IntentionsSet bool
|
|
|
|
DestinationsUpstream watch.Map[UpstreamID, *structs.ServiceConfigEntry]
|
|
DestinationGateways watch.Map[UpstreamID, structs.CheckServiceNodes]
|
|
}
|
|
|
|
// isEmpty is a test helper
|
|
func (c *configSnapshotConnectProxy) isEmpty() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
return c.Leaf == nil &&
|
|
!c.IntentionsSet &&
|
|
len(c.DiscoveryChain) == 0 &&
|
|
len(c.WatchedDiscoveryChains) == 0 &&
|
|
len(c.WatchedUpstreams) == 0 &&
|
|
len(c.WatchedUpstreamEndpoints) == 0 &&
|
|
c.UpstreamPeerTrustBundles.Len() == 0 &&
|
|
len(c.WatchedGateways) == 0 &&
|
|
len(c.WatchedGatewayEndpoints) == 0 &&
|
|
len(c.WatchedServiceChecks) == 0 &&
|
|
len(c.PreparedQueryEndpoints) == 0 &&
|
|
len(c.UpstreamConfig) == 0 &&
|
|
len(c.PassthroughUpstreams) == 0 &&
|
|
len(c.IntentionUpstreams) == 0 &&
|
|
c.DestinationGateways.Len() == 0 &&
|
|
c.DestinationsUpstream.Len() == 0 &&
|
|
len(c.PeeredUpstreams) == 0 &&
|
|
!c.InboundPeerTrustBundlesSet &&
|
|
!c.MeshConfigSet &&
|
|
c.PeerUpstreamEndpoints.Len() == 0 &&
|
|
len(c.PeerUpstreamEndpointsUseHostnames) == 0
|
|
}
|
|
|
|
func (c *configSnapshotConnectProxy) IsImplicitUpstream(uid UpstreamID) bool {
|
|
_, intentionImplicit := c.IntentionUpstreams[uid]
|
|
_, peeringImplicit := c.PeeredUpstreams[uid]
|
|
return intentionImplicit || peeringImplicit
|
|
}
|
|
|
|
func (c *configSnapshotConnectProxy) GetUpstream(uid UpstreamID, entMeta *acl.EnterpriseMeta) (*structs.Upstream, bool) {
|
|
upstream, found := c.UpstreamConfig[uid]
|
|
// We should fallback to the wildcard defaults generated from service-defaults + proxy-defaults
|
|
// whenever we don't find the upstream config.
|
|
if !found {
|
|
wildcardUID := NewWildcardUID(entMeta)
|
|
upstream = c.UpstreamConfig[wildcardUID]
|
|
}
|
|
|
|
explicit := upstream != nil && upstream.HasLocalPortOrSocket()
|
|
implicit := c.IsImplicitUpstream(uid)
|
|
return upstream, !implicit && !explicit
|
|
}
|
|
|
|
type configSnapshotTerminatingGateway struct {
|
|
MeshConfig *structs.MeshConfigEntry
|
|
MeshConfigSet bool
|
|
|
|
// WatchedServices is a map of service name to a cancel function. This cancel
|
|
// function is tied to the watch of linked service instances for the given
|
|
// id. If the linked services watch would indicate the removal of
|
|
// a service altogether we then cancel watching that service for its endpoints.
|
|
WatchedServices map[structs.ServiceName]context.CancelFunc
|
|
|
|
// WatchedIntentions is a map of service name to a cancel function.
|
|
// This cancel function is tied to the watch of intentions for linked services.
|
|
// As with WatchedServices, intention watches will be cancelled when services
|
|
// are no longer linked to the gateway.
|
|
WatchedIntentions map[structs.ServiceName]context.CancelFunc
|
|
|
|
// NOTE: Intentions stores a map of list of lists as returned by the Intentions
|
|
// Match RPC. So far we only use the first list as the list of matching
|
|
// intentions.
|
|
//
|
|
// A key being present implies that we have gotten at least one watch reply for the
|
|
// service. This is logically the same as ConnectProxy.IntentionsSet==true
|
|
Intentions map[structs.ServiceName]structs.Intentions
|
|
|
|
// WatchedLeaves is a map of ServiceName to a cancel function.
|
|
// This cancel function is tied to the watch of leaf certs for linked services.
|
|
// As with WatchedServices, leaf watches will be cancelled when services
|
|
// are no longer linked to the gateway.
|
|
WatchedLeaves map[structs.ServiceName]context.CancelFunc
|
|
|
|
// ServiceLeaves is a map of ServiceName to a leaf cert.
|
|
// Terminating gateways will present different certificates depending
|
|
// on the service that the caller is trying to reach.
|
|
ServiceLeaves map[structs.ServiceName]*structs.IssuedCert
|
|
|
|
// WatchedConfigs is a map of ServiceName to a cancel function. This cancel
|
|
// function is tied to the watch of service configs for linked services. As
|
|
// with WatchedServices, service config watches will be cancelled when
|
|
// services are no longer linked to the gateway.
|
|
WatchedConfigs map[structs.ServiceName]context.CancelFunc
|
|
|
|
// ServiceConfigs is a map of service name to the resolved service config
|
|
// for that service.
|
|
ServiceConfigs map[structs.ServiceName]*structs.ServiceConfigResponse
|
|
|
|
// WatchedResolvers is a map of ServiceName 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.ServiceName]context.CancelFunc
|
|
|
|
// ServiceResolvers is a map of service name to an associated
|
|
// service-resolver config entry for that service.
|
|
ServiceResolvers map[structs.ServiceName]*structs.ServiceResolverConfigEntry
|
|
ServiceResolversSet map[structs.ServiceName]bool
|
|
|
|
// ServiceGroups is a map of service name to the service instances of that
|
|
// service in the local datacenter.
|
|
ServiceGroups map[structs.ServiceName]structs.CheckServiceNodes
|
|
|
|
// GatewayServices is a map of service name to the config entry association
|
|
// between the gateway and a service. TLS configuration stored here is
|
|
// used for TLS origination from the gateway to the linked service.
|
|
// This map does not include GatewayServices that represent Endpoints to external
|
|
// destinations.
|
|
GatewayServices map[structs.ServiceName]structs.GatewayService
|
|
|
|
// DestinationServices is a map of service name to GatewayServices that represent
|
|
// a destination to an external destination of the service mesh.
|
|
DestinationServices map[structs.ServiceName]structs.GatewayService
|
|
|
|
// HostnameServices is a map of service name to service instances with a hostname as the address.
|
|
// If hostnames are configured they must be provided to Envoy via CDS not EDS.
|
|
HostnameServices map[structs.ServiceName]structs.CheckServiceNodes
|
|
}
|
|
|
|
// ValidServices returns the list of service keys that have enough data to be emitted.
|
|
func (c *configSnapshotTerminatingGateway) ValidServices() []structs.ServiceName {
|
|
out := make([]structs.ServiceName, 0, len(c.ServiceGroups))
|
|
for svc := range c.ServiceGroups {
|
|
// It only counts if ALL of our watches have come back (with data or not).
|
|
|
|
// Skip the service if we don't know if there is a resolver or not.
|
|
if _, ok := c.ServiceResolversSet[svc]; !ok {
|
|
continue
|
|
}
|
|
|
|
// Skip the service if we don't have a cert to present for mTLS.
|
|
if cert, ok := c.ServiceLeaves[svc]; !ok || cert == nil {
|
|
continue
|
|
}
|
|
|
|
// Skip the service if we haven't gotten our intentions yet.
|
|
if _, intentionsSet := c.Intentions[svc]; !intentionsSet {
|
|
continue
|
|
}
|
|
|
|
// Skip the service if we haven't gotten our service config yet to know
|
|
// the protocol.
|
|
if _, ok := c.ServiceConfigs[svc]; !ok {
|
|
continue
|
|
}
|
|
|
|
out = append(out, svc)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ValidDestinations returns the list of service keys (that represent exclusively endpoints) that have enough data to be emitted.
|
|
func (c *configSnapshotTerminatingGateway) ValidDestinations() []structs.ServiceName {
|
|
out := make([]structs.ServiceName, 0, len(c.DestinationServices))
|
|
for svc := range c.DestinationServices {
|
|
// It only counts if ALL of our watches have come back (with data or not).
|
|
|
|
// Skip the service if we don't have a cert to present for mTLS.
|
|
if cert, ok := c.ServiceLeaves[svc]; !ok || cert == nil {
|
|
continue
|
|
}
|
|
|
|
// Skip the service if we haven't gotten our intentions yet.
|
|
if _, intentionsSet := c.Intentions[svc]; !intentionsSet {
|
|
continue
|
|
}
|
|
|
|
// Skip the service if we haven't gotten our service config yet to know
|
|
// the protocol.
|
|
if conf, ok := c.ServiceConfigs[svc]; !ok || len(conf.Destination.Addresses) == 0 {
|
|
continue
|
|
}
|
|
|
|
out = append(out, svc)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// isEmpty is a test helper
|
|
func (c *configSnapshotTerminatingGateway) isEmpty() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
return len(c.ServiceLeaves) == 0 &&
|
|
len(c.WatchedLeaves) == 0 &&
|
|
len(c.WatchedIntentions) == 0 &&
|
|
len(c.Intentions) == 0 &&
|
|
len(c.ServiceGroups) == 0 &&
|
|
len(c.WatchedServices) == 0 &&
|
|
len(c.ServiceResolvers) == 0 &&
|
|
len(c.ServiceResolversSet) == 0 &&
|
|
len(c.WatchedResolvers) == 0 &&
|
|
len(c.ServiceConfigs) == 0 &&
|
|
len(c.WatchedConfigs) == 0 &&
|
|
len(c.GatewayServices) == 0 &&
|
|
len(c.DestinationServices) == 0 &&
|
|
len(c.HostnameServices) == 0 &&
|
|
!c.MeshConfigSet
|
|
}
|
|
|
|
type PeerServersValue struct {
|
|
Addresses []structs.ServiceAddress
|
|
Index uint64
|
|
UseCDS bool
|
|
}
|
|
|
|
type PeeringServiceValue struct {
|
|
Nodes structs.CheckServiceNodes
|
|
UseCDS bool
|
|
}
|
|
|
|
type configSnapshotMeshGateway struct {
|
|
// WatchedServices is a map of service name to a cancel function. This cancel
|
|
// function is tied to the watch of connect enabled services for the given
|
|
// id. If the main datacenter services watch would indicate the removal of
|
|
// a service altogether we then cancel watching that service for its
|
|
// connect endpoints.
|
|
WatchedServices map[structs.ServiceName]context.CancelFunc
|
|
|
|
// WatchedServicesSet indicates that the watch on the datacenters services
|
|
// has completed. Even when there are no connect services, this being set
|
|
// (and the Connect roots being available) will be enough for the config
|
|
// snapshot to be considered valid. In the case of Envoy, this allows it to
|
|
// start its listeners even when no services would be proxied and allow its
|
|
// health check to pass.
|
|
WatchedServicesSet bool
|
|
|
|
// WatchedGateways is a map of GatewayKeys to a cancel function.
|
|
// This cancel function is tied to the watch of mesh-gateway services in
|
|
// that datacenter/partition.
|
|
WatchedGateways map[string]context.CancelFunc
|
|
|
|
// ServiceGroups is a map of service name to the service instances of that
|
|
// service in the local datacenter.
|
|
ServiceGroups map[structs.ServiceName]structs.CheckServiceNodes
|
|
|
|
// PeeringServices is a map of peer name -> (map of
|
|
// service name -> CheckServiceNodes) and is used to determine the backing
|
|
// endpoints of a service on a peer.
|
|
PeeringServices map[string]map[structs.ServiceName]PeeringServiceValue
|
|
|
|
// WatchedPeeringServices is a map of peer name -> (map of service name ->
|
|
// cancel function) and is used to track watches on services within a peer.
|
|
WatchedPeeringServices map[string]map[structs.ServiceName]context.CancelFunc
|
|
|
|
// WatchedPeers is a map of peer name -> cancel functions. It is used to
|
|
// track watches on peers.
|
|
WatchedPeers map[string]context.CancelFunc
|
|
|
|
// ServiceResolvers is a map of service name to an associated
|
|
// service-resolver config entry for that service.
|
|
ServiceResolvers map[structs.ServiceName]*structs.ServiceResolverConfigEntry
|
|
|
|
// GatewayGroups is a map of datacenter names to services of kind
|
|
// mesh-gateway in that datacenter.
|
|
GatewayGroups map[string]structs.CheckServiceNodes
|
|
|
|
// FedStateGateways is a map of datacenter names to mesh gateways in that
|
|
// datacenter.
|
|
FedStateGateways map[string]structs.CheckServiceNodes
|
|
|
|
// WatchedLocalServers is a map of (structs.ConsulServiceName -> structs.CheckServiceNodes)`
|
|
// Mesh gateways can spin up watches for local servers both for
|
|
// WAN federation and for peering. This map ensures we only have one
|
|
// watch at a time.
|
|
WatchedLocalServers watch.Map[string, structs.CheckServiceNodes]
|
|
|
|
// HostnameDatacenters is a map of datacenters to mesh gateway instances with a hostname as the address.
|
|
// If hostnames are configured they must be provided to Envoy via CDS not EDS.
|
|
HostnameDatacenters map[string]structs.CheckServiceNodes
|
|
|
|
// ExportedServicesSlice is a sorted slice of services that are exported to
|
|
// connected peers.
|
|
ExportedServicesSlice []structs.ServiceName
|
|
|
|
// ExportedServicesWithPeers is a map of exported service name to a sorted
|
|
// slice of peers that they are exported to.
|
|
ExportedServicesWithPeers map[structs.ServiceName][]string
|
|
|
|
// ExportedServicesSet indicates that the watch on the list of
|
|
// peer-exported services has completed at least once.
|
|
ExportedServicesSet bool
|
|
|
|
// DiscoveryChain is a map of the peer-exported service names to their
|
|
// local compiled discovery chain. This will be populated regardless of
|
|
// L4/L7 status of the chain.
|
|
DiscoveryChain map[structs.ServiceName]*structs.CompiledDiscoveryChain
|
|
|
|
// WatchedDiscoveryChains is a map of peer-exported service names to a
|
|
// cancel function.
|
|
WatchedDiscoveryChains map[structs.ServiceName]context.CancelFunc
|
|
|
|
// MeshConfig is the mesh config entry that should be used for services
|
|
// fronted by this mesh gateway.
|
|
MeshConfig *structs.MeshConfigEntry
|
|
|
|
// MeshConfigSet indicates that the watch on the mesh config entry has
|
|
// completed at least once.
|
|
MeshConfigSet bool
|
|
|
|
// Leaf is the leaf cert to be used by this mesh gateway.
|
|
Leaf *structs.IssuedCert
|
|
|
|
// LeafCertWatchCancel is a CancelFunc to use when refreshing this gateway's
|
|
// leaf cert watch with different parameters.
|
|
LeafCertWatchCancel context.CancelFunc
|
|
|
|
// PeerServers is the map of peering server names to their addresses.
|
|
PeerServers map[string]PeerServersValue
|
|
|
|
// PeerServersWatchCancel is a CancelFunc to use when resetting the watch
|
|
// on all peerings as it is enabled/disabled.
|
|
PeerServersWatchCancel context.CancelFunc
|
|
|
|
// PeeringTrustBundles is the list of trust bundles for peers where
|
|
// services have been exported to using this mesh gateway.
|
|
PeeringTrustBundles []*pbpeering.PeeringTrustBundle
|
|
|
|
// PeeringTrustBundlesSet indicates that the watch on the peer trust
|
|
// bundles has completed at least once.
|
|
PeeringTrustBundlesSet bool
|
|
}
|
|
|
|
// MeshGatewayValidExportedServices ensures that the following data is present
|
|
// if it exists for a service before it returns that in the set of services to
|
|
// expose.
|
|
//
|
|
// - peering info
|
|
// - discovery chain
|
|
func (c *ConfigSnapshot) MeshGatewayValidExportedServices() []structs.ServiceName {
|
|
out := make([]structs.ServiceName, 0, len(c.MeshGateway.ExportedServicesSlice))
|
|
for _, svc := range c.MeshGateway.ExportedServicesSlice {
|
|
if _, ok := c.MeshGateway.ExportedServicesWithPeers[svc]; !ok {
|
|
continue // not possible
|
|
}
|
|
|
|
if _, ok := c.MeshGateway.ServiceGroups[svc]; !ok {
|
|
continue // unregistered services
|
|
}
|
|
|
|
chain, ok := c.MeshGateway.DiscoveryChain[svc]
|
|
if !ok {
|
|
continue // ignore; not ready
|
|
}
|
|
|
|
if structs.IsProtocolHTTPLike(chain.Protocol) {
|
|
if c.MeshGateway.Leaf == nil {
|
|
continue // ignore; not ready
|
|
}
|
|
}
|
|
out = append(out, svc)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (c *ConfigSnapshot) GetMeshGatewayEndpoints(key GatewayKey) structs.CheckServiceNodes {
|
|
// Mesh gateways in remote DCs are discovered in two ways:
|
|
//
|
|
// 1. Via an Internal.ServiceDump RPC in the remote DC (GatewayGroups).
|
|
// 2. In the federation state that is replicated from the primary DC (FedStateGateways).
|
|
//
|
|
// We determine which set to use based on whichever contains the highest
|
|
// raft ModifyIndex (and is therefore most up-to-date).
|
|
//
|
|
// Previously, GatewayGroups was always given presedence over FedStateGateways
|
|
// but this was problematic when using mesh gateways for WAN federation.
|
|
//
|
|
// Consider the following example:
|
|
//
|
|
// - Primary and Secondary DCs are WAN Federated via local mesh gateways.
|
|
//
|
|
// - Secondary DC's mesh gateway is running on an ephemeral compute instance
|
|
// and is abruptly terminated and rescheduled with a *new IP address*.
|
|
//
|
|
// - Primary DC's mesh gateway is no longer able to connect to the Secondary
|
|
// DC as its proxy is configured with the old IP address. Therefore any RPC
|
|
// from the Primary to the Secondary DC will fail (including the one to
|
|
// discover the gateway's new IP address).
|
|
//
|
|
// - Secondary DC performs its regular anti-entropy of federation state data
|
|
// to the Primary DC (this succeeds as there is still connectivity in this
|
|
// direction).
|
|
//
|
|
// - At this point the Primary DC's mesh gateway should observe the new IP
|
|
// address and reconfigure its proxy, however as we always prioritised
|
|
// GatewayGroups this didn't happen and the connection remained severed.
|
|
maxModifyIndex := func(vals structs.CheckServiceNodes) uint64 {
|
|
var max uint64
|
|
for _, v := range vals {
|
|
if i := v.Service.RaftIndex.ModifyIndex; i > max {
|
|
max = i
|
|
}
|
|
}
|
|
return max
|
|
}
|
|
|
|
endpoints := c.MeshGateway.GatewayGroups[key.String()]
|
|
fedStateEndpoints := c.MeshGateway.FedStateGateways[key.String()]
|
|
|
|
if maxModifyIndex(fedStateEndpoints) > maxModifyIndex(endpoints) {
|
|
return fedStateEndpoints
|
|
}
|
|
return endpoints
|
|
}
|
|
|
|
func (c *configSnapshotMeshGateway) IsServiceExported(svc structs.ServiceName) bool {
|
|
if c == nil || len(c.ExportedServicesWithPeers) == 0 {
|
|
return false
|
|
}
|
|
|
|
_, ok := c.ExportedServicesWithPeers[svc]
|
|
return ok
|
|
}
|
|
|
|
func (c *configSnapshotMeshGateway) GatewayKeys() []GatewayKey {
|
|
sz1, sz2 := len(c.GatewayGroups), len(c.FedStateGateways)
|
|
|
|
sz := sz1
|
|
if sz2 > sz1 {
|
|
sz = sz2
|
|
}
|
|
|
|
keys := make([]GatewayKey, 0, sz)
|
|
for key := range c.FedStateGateways {
|
|
keys = append(keys, gatewayKeyFromString(key))
|
|
}
|
|
for key := range c.GatewayGroups {
|
|
gk := gatewayKeyFromString(key)
|
|
if _, ok := c.FedStateGateways[gk.Datacenter]; !ok {
|
|
keys = append(keys, gk)
|
|
}
|
|
}
|
|
|
|
// Always sort the results to ensure we generate deterministic things over
|
|
// xDS, such as mesh-gateway listener filter chains.
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
if keys[i].Datacenter != keys[j].Datacenter {
|
|
return keys[i].Datacenter < keys[j].Datacenter
|
|
}
|
|
return keys[i].Partition < keys[j].Partition
|
|
})
|
|
return keys
|
|
}
|
|
|
|
// isEmpty is a test helper
|
|
func (c *configSnapshotMeshGateway) isEmpty() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
return len(c.WatchedServices) == 0 &&
|
|
!c.WatchedServicesSet &&
|
|
len(c.WatchedGateways) == 0 &&
|
|
len(c.ServiceGroups) == 0 &&
|
|
len(c.ServiceResolvers) == 0 &&
|
|
len(c.GatewayGroups) == 0 &&
|
|
len(c.FedStateGateways) == 0 &&
|
|
len(c.HostnameDatacenters) == 0 &&
|
|
c.WatchedLocalServers.Len() == 0 &&
|
|
c.isEmptyPeering()
|
|
}
|
|
|
|
// isEmptyPeering is a test helper
|
|
func (c *configSnapshotMeshGateway) isEmptyPeering() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
|
|
return len(c.ExportedServicesSlice) == 0 &&
|
|
len(c.ExportedServicesWithPeers) == 0 &&
|
|
!c.ExportedServicesSet &&
|
|
len(c.DiscoveryChain) == 0 &&
|
|
len(c.WatchedDiscoveryChains) == 0 &&
|
|
!c.MeshConfigSet &&
|
|
c.LeafCertWatchCancel == nil &&
|
|
c.Leaf == nil &&
|
|
len(c.PeeringTrustBundles) == 0 &&
|
|
!c.PeeringTrustBundlesSet
|
|
}
|
|
|
|
type upstreamIDSet map[UpstreamID]struct{}
|
|
|
|
func (u upstreamIDSet) add(uid UpstreamID) {
|
|
u[uid] = struct{}{}
|
|
}
|
|
|
|
type routeUpstreamSet map[structs.ResourceReference]upstreamIDSet
|
|
|
|
func (r routeUpstreamSet) hasUpstream(uid UpstreamID) bool {
|
|
for _, set := range r {
|
|
if _, ok := set[uid]; ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (r routeUpstreamSet) set(route structs.ResourceReference, set upstreamIDSet) {
|
|
r[route] = set
|
|
}
|
|
|
|
func (r routeUpstreamSet) delete(route structs.ResourceReference) {
|
|
delete(r, route)
|
|
}
|
|
|
|
type listenerUpstreamMap map[APIGatewayListenerKey]structs.Upstreams
|
|
type listenerRouteUpstreams map[structs.ResourceReference]listenerUpstreamMap
|
|
|
|
func (l listenerRouteUpstreams) set(route structs.ResourceReference, listener APIGatewayListenerKey, upstreams structs.Upstreams) {
|
|
if _, ok := l[route]; !ok {
|
|
l[route] = make(listenerUpstreamMap)
|
|
}
|
|
l[route][listener] = upstreams
|
|
}
|
|
|
|
func (l listenerRouteUpstreams) delete(route structs.ResourceReference) {
|
|
delete(l, route)
|
|
}
|
|
|
|
func (l listenerRouteUpstreams) toUpstreams() map[IngressListenerKey]structs.Upstreams {
|
|
listeners := make(map[IngressListenerKey]structs.Upstreams, len(l))
|
|
for _, listenerMap := range l {
|
|
for listener, set := range listenerMap {
|
|
listeners[listener] = append(listeners[listener], set...)
|
|
}
|
|
}
|
|
return listeners
|
|
}
|
|
|
|
type configSnapshotAPIGateway struct {
|
|
ConfigSnapshotUpstreams
|
|
|
|
TLSConfig structs.GatewayTLSConfig
|
|
|
|
// GatewayConfigLoaded is used to determine if we have received the initial
|
|
// api-gateway config entry yet.
|
|
GatewayConfigLoaded bool
|
|
GatewayConfig *structs.APIGatewayConfigEntry
|
|
|
|
// BoundGatewayConfigLoaded is used to determine if we have received the initial
|
|
// bound-api-gateway config entry yet.
|
|
BoundGatewayConfigLoaded bool
|
|
BoundGatewayConfig *structs.BoundAPIGatewayConfigEntry
|
|
|
|
// Hosts is the list of extra host entries to add to our leaf cert's DNS SANs
|
|
Hosts []string
|
|
AreHostsSet bool
|
|
|
|
// LeafCertWatchCancel is a CancelFunc to use when refreshing this gateway's
|
|
// leaf cert watch with different parameters.
|
|
//LeafCertWatchCancel context.CancelFunc
|
|
|
|
// Upstreams is a list of upstreams this ingress gateway should serve traffic
|
|
// to. This is constructed from the ingress-gateway config entry, and uses
|
|
// the GatewayServices RPC to retrieve them.
|
|
// TODO Determine if this is updated "for free" or not. If not, we might need
|
|
// to do some work to populate it in handlerAPIGateway
|
|
Upstreams listenerRouteUpstreams
|
|
|
|
// UpstreamsSet is the unique set of UpstreamID the gateway routes to.
|
|
UpstreamsSet routeUpstreamSet
|
|
|
|
HTTPRoutes watch.Map[structs.ResourceReference, *structs.HTTPRouteConfigEntry]
|
|
TCPRoutes watch.Map[structs.ResourceReference, *structs.TCPRouteConfigEntry]
|
|
Certificates watch.Map[structs.ResourceReference, *structs.InlineCertificateConfigEntry]
|
|
|
|
// LeafCertWatchCancel is a CancelFunc to use when refreshing this gateway's
|
|
// leaf cert watch with different parameters.
|
|
LeafCertWatchCancel context.CancelFunc
|
|
|
|
// Listeners is the original listener config from the api-gateway config
|
|
// entry to save us trying to pass fields through Upstreams
|
|
Listeners map[string]structs.APIGatewayListener
|
|
// this acts as an intermediary for inlining certificates
|
|
ListenerCertificates map[IngressListenerKey][]structs.InlineCertificateConfigEntry
|
|
|
|
BoundListeners map[string]structs.BoundAPIGatewayListener
|
|
}
|
|
|
|
// ToIngress converts a configSnapshotAPIGateway to a configSnapshotIngressGateway.
|
|
// This is temporary, for the sake of re-using existing codepaths when integrating
|
|
// Consul API Gateway into Consul core.
|
|
//
|
|
// FUTURE: Remove when API gateways have custom snapshot generation
|
|
func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotIngressGateway, error) {
|
|
// Convert API Gateway Listeners to Ingress Listeners.
|
|
ingressListeners := make(map[IngressListenerKey]structs.IngressListener, len(c.Listeners))
|
|
ingressUpstreams := make(map[IngressListenerKey]structs.Upstreams, len(c.Listeners))
|
|
synthesizedChains := map[UpstreamID]*structs.CompiledDiscoveryChain{}
|
|
watchedUpstreamEndpoints := make(map[UpstreamID]map[string]structs.CheckServiceNodes)
|
|
watchedGatewayEndpoints := make(map[UpstreamID]map[string]structs.CheckServiceNodes)
|
|
|
|
// reset the cached certificates
|
|
c.ListenerCertificates = make(map[IngressListenerKey][]structs.InlineCertificateConfigEntry)
|
|
|
|
for name, listener := range c.Listeners {
|
|
boundListener, ok := c.BoundListeners[name]
|
|
if !ok {
|
|
// Skip any listeners that don't have a bound listener. Once the bound listener is created, this will be run again.
|
|
continue
|
|
}
|
|
|
|
if !c.GatewayConfig.ListenerIsReady(name) {
|
|
// skip any listeners that might be in an invalid state
|
|
continue
|
|
}
|
|
|
|
ingressListener := structs.IngressListener{
|
|
Port: listener.Port,
|
|
Protocol: string(listener.Protocol),
|
|
}
|
|
|
|
// Create a synthesized discovery chain for each service.
|
|
services, upstreams, compiled, err := c.synthesizeChains(datacenter, listener, boundListener)
|
|
if err != nil {
|
|
return configSnapshotIngressGateway{}, err
|
|
}
|
|
|
|
if len(upstreams) == 0 {
|
|
// skip if we can't construct any upstreams
|
|
continue
|
|
}
|
|
|
|
ingressListener.Services = services
|
|
for i, service := range services {
|
|
id := NewUpstreamIDFromServiceName(structs.NewServiceName(service.Name, &service.EnterpriseMeta))
|
|
upstreamEndpoints := make(map[string]structs.CheckServiceNodes)
|
|
gatewayEndpoints := make(map[string]structs.CheckServiceNodes)
|
|
|
|
// add the watched endpoints and gateway endpoints under the new upstream
|
|
for _, endpoints := range c.WatchedUpstreamEndpoints {
|
|
for targetID, endpoint := range endpoints {
|
|
upstreamEndpoints[targetID] = endpoint
|
|
}
|
|
}
|
|
for _, endpoints := range c.WatchedGatewayEndpoints {
|
|
for targetID, endpoint := range endpoints {
|
|
gatewayEndpoints[targetID] = endpoint
|
|
}
|
|
}
|
|
|
|
synthesizedChains[id] = compiled[i]
|
|
watchedUpstreamEndpoints[id] = upstreamEndpoints
|
|
watchedGatewayEndpoints[id] = gatewayEndpoints
|
|
}
|
|
|
|
key := IngressListenerKey{
|
|
Port: listener.Port,
|
|
Protocol: string(listener.Protocol),
|
|
}
|
|
|
|
// Configure TLS for the ingress listener
|
|
tls, err := c.toIngressTLS(key, listener, boundListener)
|
|
if err != nil {
|
|
return configSnapshotIngressGateway{}, err
|
|
}
|
|
|
|
ingressListener.TLS = tls
|
|
ingressListeners[key] = ingressListener
|
|
ingressUpstreams[key] = upstreams
|
|
}
|
|
|
|
snapshotUpstreams := c.DeepCopy().ConfigSnapshotUpstreams
|
|
snapshotUpstreams.DiscoveryChain = synthesizedChains
|
|
snapshotUpstreams.WatchedUpstreamEndpoints = watchedUpstreamEndpoints
|
|
snapshotUpstreams.WatchedGatewayEndpoints = watchedGatewayEndpoints
|
|
|
|
return configSnapshotIngressGateway{
|
|
Upstreams: ingressUpstreams,
|
|
ConfigSnapshotUpstreams: snapshotUpstreams,
|
|
GatewayConfigLoaded: true,
|
|
Listeners: ingressListeners,
|
|
}, nil
|
|
}
|
|
|
|
func (c *configSnapshotAPIGateway) synthesizeChains(datacenter string, listener structs.APIGatewayListener, boundListener structs.BoundAPIGatewayListener) ([]structs.IngressService, structs.Upstreams, []*structs.CompiledDiscoveryChain, error) {
|
|
chains := []*structs.CompiledDiscoveryChain{}
|
|
trustDomain := ""
|
|
|
|
DOMAIN_LOOP:
|
|
for _, chain := range c.DiscoveryChain {
|
|
for _, target := range chain.Targets {
|
|
if !target.External {
|
|
trustDomain = connect.TrustDomainForTarget(*target)
|
|
if trustDomain != "" {
|
|
break DOMAIN_LOOP
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
synthesizer := discoverychain.NewGatewayChainSynthesizer(datacenter, trustDomain, listener.Name, c.GatewayConfig)
|
|
synthesizer.SetHostname(listener.GetHostname())
|
|
for _, routeRef := range boundListener.Routes {
|
|
switch routeRef.Kind {
|
|
case structs.HTTPRoute:
|
|
route, ok := c.HTTPRoutes.Get(routeRef)
|
|
if !ok || listener.Protocol != structs.ListenerProtocolHTTP {
|
|
continue
|
|
}
|
|
synthesizer.AddHTTPRoute(*route)
|
|
for _, service := range route.GetServices() {
|
|
id := NewUpstreamIDFromServiceName(structs.NewServiceName(service.Name, &service.EnterpriseMeta))
|
|
if chain := c.DiscoveryChain[id]; chain != nil {
|
|
chains = append(chains, chain)
|
|
}
|
|
}
|
|
case structs.TCPRoute:
|
|
route, ok := c.TCPRoutes.Get(routeRef)
|
|
if !ok || listener.Protocol != structs.ListenerProtocolTCP {
|
|
continue
|
|
}
|
|
synthesizer.AddTCPRoute(*route)
|
|
for _, service := range route.GetServices() {
|
|
id := NewUpstreamIDFromServiceName(structs.NewServiceName(service.Name, &service.EnterpriseMeta))
|
|
if chain := c.DiscoveryChain[id]; chain != nil {
|
|
chains = append(chains, chain)
|
|
}
|
|
}
|
|
default:
|
|
return nil, nil, nil, fmt.Errorf("unknown route kind %q", routeRef.Kind)
|
|
}
|
|
}
|
|
|
|
if len(chains) == 0 {
|
|
return nil, nil, nil, nil
|
|
}
|
|
|
|
services, compiled, err := synthesizer.Synthesize(chains...)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
// reconstruct the upstreams
|
|
upstreams := make([]structs.Upstream, 0, len(services))
|
|
for _, service := range services {
|
|
upstreams = append(upstreams, structs.Upstream{
|
|
DestinationName: service.Name,
|
|
DestinationNamespace: service.NamespaceOrDefault(),
|
|
DestinationPartition: service.PartitionOrDefault(),
|
|
IngressHosts: service.Hosts,
|
|
LocalBindPort: listener.Port,
|
|
Config: map[string]interface{}{
|
|
"protocol": string(listener.Protocol),
|
|
},
|
|
})
|
|
}
|
|
|
|
return services, upstreams, compiled, err
|
|
}
|
|
|
|
func (c *configSnapshotAPIGateway) toIngressTLS(key IngressListenerKey, listener structs.APIGatewayListener, bound structs.BoundAPIGatewayListener) (*structs.GatewayTLSConfig, error) {
|
|
if len(listener.TLS.Certificates) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
for _, certRef := range bound.Certificates {
|
|
cert, ok := c.Certificates.Get(certRef)
|
|
if !ok {
|
|
continue
|
|
}
|
|
c.ListenerCertificates[key] = append(c.ListenerCertificates[key], *cert)
|
|
}
|
|
|
|
return &structs.GatewayTLSConfig{
|
|
Enabled: true,
|
|
TLSMinVersion: listener.TLS.MinVersion,
|
|
TLSMaxVersion: listener.TLS.MaxVersion,
|
|
CipherSuites: listener.TLS.CipherSuites,
|
|
}, nil
|
|
}
|
|
|
|
type configSnapshotIngressGateway struct {
|
|
ConfigSnapshotUpstreams
|
|
|
|
// TLSConfig is the gateway-level TLS configuration. Listener/service level
|
|
// config is preserved in the Listeners map below.
|
|
TLSConfig structs.GatewayTLSConfig
|
|
|
|
// GatewayConfigLoaded is used to determine if we have received the initial
|
|
// ingress-gateway config entry yet.
|
|
GatewayConfigLoaded bool
|
|
|
|
// Hosts is the list of extra host entries to add to our leaf cert's DNS SANs.
|
|
Hosts []string
|
|
HostsSet bool
|
|
|
|
// LeafCertWatchCancel is a CancelFunc to use when refreshing this gateway's
|
|
// leaf cert watch with different parameters.
|
|
LeafCertWatchCancel context.CancelFunc
|
|
|
|
// Upstreams is a list of upstreams this ingress gateway should serve traffic
|
|
// to. This is constructed from the ingress-gateway config entry, and uses
|
|
// the GatewayServices RPC to retrieve them.
|
|
Upstreams map[IngressListenerKey]structs.Upstreams
|
|
|
|
// UpstreamsSet is the unique set of UpstreamID the gateway routes to.
|
|
UpstreamsSet map[UpstreamID]struct{}
|
|
|
|
// Listeners is the original listener config from the ingress-gateway config
|
|
// entry to save us trying to pass fields through Upstreams
|
|
Listeners map[IngressListenerKey]structs.IngressListener
|
|
|
|
// Defaults is the default configuration for upstream service instances
|
|
Defaults structs.IngressServiceConfig
|
|
}
|
|
|
|
// isEmpty is a test helper
|
|
func (c *configSnapshotIngressGateway) isEmpty() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
return len(c.Upstreams) == 0 &&
|
|
len(c.UpstreamsSet) == 0 &&
|
|
len(c.DiscoveryChain) == 0 &&
|
|
len(c.WatchedUpstreams) == 0 &&
|
|
len(c.WatchedUpstreamEndpoints) == 0 &&
|
|
!c.MeshConfigSet
|
|
}
|
|
|
|
type APIGatewayListenerKey = IngressListenerKey
|
|
|
|
type IngressListenerKey struct {
|
|
Protocol string
|
|
Port int
|
|
}
|
|
|
|
func (k *IngressListenerKey) RouteName() string {
|
|
return fmt.Sprintf("%d", k.Port)
|
|
}
|
|
|
|
func IngressListenerKeyFromGWService(s structs.GatewayService) IngressListenerKey {
|
|
return IngressListenerKey{Protocol: s.Protocol, Port: s.Port}
|
|
}
|
|
|
|
func IngressListenerKeyFromListener(l structs.IngressListener) IngressListenerKey {
|
|
return IngressListenerKey{Protocol: l.Protocol, Port: l.Port}
|
|
}
|
|
|
|
// ConfigSnapshot captures all the resulting config needed for a proxy instance.
|
|
// It is meant to be point-in-time coherent and is used to deliver the current
|
|
// config state to observers who need it to be pushed in (e.g. XDS server).
|
|
type ConfigSnapshot struct {
|
|
Kind structs.ServiceKind
|
|
Service string
|
|
ProxyID ProxyID
|
|
Address string
|
|
Port int
|
|
ServiceMeta map[string]string
|
|
TaggedAddresses map[string]structs.ServiceAddress
|
|
Proxy structs.ConnectProxyConfig
|
|
Datacenter string
|
|
IntentionDefaultAllow bool
|
|
Locality GatewayKey
|
|
|
|
ServerSNIFn ServerSNIFunc
|
|
Roots *structs.IndexedCARoots
|
|
|
|
// connect-proxy specific
|
|
ConnectProxy configSnapshotConnectProxy
|
|
|
|
// terminating-gateway specific
|
|
TerminatingGateway configSnapshotTerminatingGateway
|
|
|
|
// mesh-gateway specific
|
|
MeshGateway configSnapshotMeshGateway
|
|
|
|
// ingress-gateway specific
|
|
IngressGateway configSnapshotIngressGateway
|
|
|
|
// api-gateway specific
|
|
APIGateway configSnapshotAPIGateway
|
|
}
|
|
|
|
// Valid returns whether or not the snapshot has all required fields filled yet.
|
|
func (s *ConfigSnapshot) Valid() bool {
|
|
switch s.Kind {
|
|
case structs.ServiceKindConnectProxy:
|
|
if s.Proxy.Mode == structs.ProxyModeTransparent && !s.ConnectProxy.MeshConfigSet {
|
|
return false
|
|
}
|
|
return s.Roots != nil &&
|
|
s.ConnectProxy.Leaf != nil &&
|
|
s.ConnectProxy.IntentionsSet &&
|
|
s.ConnectProxy.MeshConfigSet
|
|
|
|
case structs.ServiceKindTerminatingGateway:
|
|
return s.Roots != nil &&
|
|
s.TerminatingGateway.MeshConfigSet
|
|
|
|
case structs.ServiceKindMeshGateway:
|
|
if s.MeshGateway.WatchedLocalServers.Len() == 0 {
|
|
if s.ServiceMeta[structs.MetaWANFederationKey] == "1" {
|
|
return false
|
|
}
|
|
if cfg := s.MeshConfig(); cfg.PeerThroughMeshGateways() {
|
|
return false
|
|
}
|
|
}
|
|
return s.Roots != nil &&
|
|
(s.MeshGateway.WatchedServicesSet || len(s.MeshGateway.ServiceGroups) > 0) &&
|
|
s.MeshGateway.ExportedServicesSet &&
|
|
s.MeshGateway.MeshConfigSet &&
|
|
s.MeshGateway.PeeringTrustBundlesSet
|
|
|
|
case structs.ServiceKindIngressGateway:
|
|
return s.Roots != nil &&
|
|
s.IngressGateway.Leaf != nil &&
|
|
s.IngressGateway.GatewayConfigLoaded &&
|
|
s.IngressGateway.HostsSet &&
|
|
s.IngressGateway.MeshConfigSet
|
|
|
|
case structs.ServiceKindAPIGateway:
|
|
// TODO Is this the proper set of things to validate?
|
|
return s.Roots != nil &&
|
|
s.APIGateway.Leaf != nil &&
|
|
s.APIGateway.GatewayConfigLoaded &&
|
|
s.APIGateway.BoundGatewayConfigLoaded &&
|
|
s.APIGateway.AreHostsSet &&
|
|
s.APIGateway.MeshConfigSet
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Clone makes a deep copy of the snapshot we can send to other goroutines
|
|
// without worrying that they will racily read or mutate shared maps etc.
|
|
func (s *ConfigSnapshot) Clone() *ConfigSnapshot {
|
|
snap := s.DeepCopy()
|
|
|
|
// nil these out as anything receiving one of these clones does not need them and should never "cancel" our watches
|
|
switch s.Kind {
|
|
case structs.ServiceKindConnectProxy:
|
|
// common with connect-proxy and ingress-gateway
|
|
snap.ConnectProxy.WatchedUpstreams = nil
|
|
snap.ConnectProxy.WatchedGateways = nil
|
|
snap.ConnectProxy.WatchedDiscoveryChains = nil
|
|
case structs.ServiceKindTerminatingGateway:
|
|
snap.TerminatingGateway.WatchedServices = nil
|
|
snap.TerminatingGateway.WatchedIntentions = nil
|
|
snap.TerminatingGateway.WatchedLeaves = nil
|
|
snap.TerminatingGateway.WatchedConfigs = nil
|
|
snap.TerminatingGateway.WatchedResolvers = nil
|
|
case structs.ServiceKindMeshGateway:
|
|
snap.MeshGateway.WatchedGateways = nil
|
|
snap.MeshGateway.WatchedServices = nil
|
|
case structs.ServiceKindIngressGateway:
|
|
// common with connect-proxy and ingress-gateway
|
|
snap.IngressGateway.WatchedUpstreams = nil
|
|
snap.IngressGateway.WatchedGateways = nil
|
|
snap.IngressGateway.WatchedDiscoveryChains = nil
|
|
// only ingress-gateway
|
|
snap.IngressGateway.LeafCertWatchCancel = nil
|
|
case structs.ServiceKindAPIGateway:
|
|
// common with connect-proxy and api-gateway
|
|
snap.APIGateway.WatchedUpstreams = nil
|
|
snap.APIGateway.WatchedGateways = nil
|
|
snap.APIGateway.WatchedDiscoveryChains = nil
|
|
|
|
// only api-gateway
|
|
//snap.APIGateway.LeafCertWatchCancel = nil
|
|
//snap.APIGateway.
|
|
}
|
|
|
|
return snap
|
|
}
|
|
|
|
func (s *ConfigSnapshot) Leaf() *structs.IssuedCert {
|
|
switch s.Kind {
|
|
case structs.ServiceKindConnectProxy:
|
|
return s.ConnectProxy.Leaf
|
|
case structs.ServiceKindIngressGateway:
|
|
return s.IngressGateway.Leaf
|
|
case structs.ServiceKindAPIGateway:
|
|
return s.APIGateway.Leaf
|
|
case structs.ServiceKindMeshGateway:
|
|
return s.MeshGateway.Leaf
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *ConfigSnapshot) PeeringTrustBundles() []*pbpeering.PeeringTrustBundle {
|
|
switch s.Kind {
|
|
case structs.ServiceKindConnectProxy:
|
|
return s.ConnectProxy.InboundPeerTrustBundles
|
|
case structs.ServiceKindMeshGateway:
|
|
return s.MeshGateway.PeeringTrustBundles
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// RootPEMs returns all PEM-encoded public certificates for the root CA.
|
|
func (s *ConfigSnapshot) RootPEMs() string {
|
|
var rootPEMs string
|
|
for _, root := range s.Roots.Roots {
|
|
rootPEMs += lib.EnsureTrailingNewline(root.RootCert)
|
|
}
|
|
return rootPEMs
|
|
}
|
|
|
|
func (s *ConfigSnapshot) MeshConfig() *structs.MeshConfigEntry {
|
|
switch s.Kind {
|
|
case structs.ServiceKindConnectProxy:
|
|
return s.ConnectProxy.MeshConfig
|
|
case structs.ServiceKindIngressGateway:
|
|
return s.IngressGateway.MeshConfig
|
|
case structs.ServiceKindAPIGateway:
|
|
return s.APIGateway.MeshConfig
|
|
case structs.ServiceKindTerminatingGateway:
|
|
return s.TerminatingGateway.MeshConfig
|
|
case structs.ServiceKindMeshGateway:
|
|
return s.MeshGateway.MeshConfig
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *ConfigSnapshot) MeshConfigTLSIncoming() *structs.MeshDirectionalTLSConfig {
|
|
mesh := s.MeshConfig()
|
|
if mesh == nil || mesh.TLS == nil {
|
|
return nil
|
|
}
|
|
return mesh.TLS.Incoming
|
|
}
|
|
|
|
func (s *ConfigSnapshot) MeshConfigTLSOutgoing() *structs.MeshDirectionalTLSConfig {
|
|
mesh := s.MeshConfig()
|
|
if mesh == nil || mesh.TLS == nil {
|
|
return nil
|
|
}
|
|
return mesh.TLS.Outgoing
|
|
}
|
|
|
|
func (s *ConfigSnapshot) ToConfigSnapshotUpstreams() (*ConfigSnapshotUpstreams, error) {
|
|
switch s.Kind {
|
|
case structs.ServiceKindConnectProxy:
|
|
return &s.ConnectProxy.ConfigSnapshotUpstreams, nil
|
|
case structs.ServiceKindIngressGateway:
|
|
return &s.IngressGateway.ConfigSnapshotUpstreams, nil
|
|
case structs.ServiceKindAPIGateway:
|
|
return &s.APIGateway.ConfigSnapshotUpstreams, nil
|
|
default:
|
|
// This is a coherence check and should never fail
|
|
return nil, fmt.Errorf("No upstream snapshot for gateway mode %q", s.Kind)
|
|
}
|
|
}
|
|
|
|
func (u *ConfigSnapshotUpstreams) UpstreamPeerMeta(uid UpstreamID) (structs.PeeringServiceMeta, bool) {
|
|
nodes, _ := u.PeerUpstreamEndpoints.Get(uid)
|
|
if len(nodes) == 0 {
|
|
return structs.PeeringServiceMeta{}, false
|
|
}
|
|
|
|
// In agent/rpc/peering/subscription_manager.go we denormalize the
|
|
// PeeringServiceMeta data onto each replicated service instance to convey
|
|
// this information back to the importing side of the peering.
|
|
//
|
|
// This data is guaranteed (subject to any eventual consistency lag around
|
|
// updates) to be the same across all instances, so we only need to take
|
|
// the first item.
|
|
//
|
|
// TODO(peering): consider replicating this "common to all instances" data
|
|
// using a different replication type and persist it separately in the
|
|
// catalog to avoid this weird construction.
|
|
csn := nodes[0]
|
|
if csn.Service == nil {
|
|
return structs.PeeringServiceMeta{}, false
|
|
}
|
|
return *csn.Service.Connect.PeerMeta, true
|
|
}
|
|
|
|
// PeeredUpstreamIDs returns a slice of peered UpstreamIDs from explicit config entries
|
|
// and implicit imported services.
|
|
// Upstreams whose trust bundles have not been stored in the snapshot are ignored.
|
|
func (u *ConfigSnapshotUpstreams) PeeredUpstreamIDs() []UpstreamID {
|
|
out := make([]UpstreamID, 0, u.PeerUpstreamEndpoints.Len())
|
|
u.PeerUpstreamEndpoints.ForEachKey(func(uid UpstreamID) bool {
|
|
if _, ok := u.PeerUpstreamEndpoints.Get(uid); !ok {
|
|
// uid might exist in the map but if Set hasn't been called, skip for now.
|
|
return true
|
|
}
|
|
|
|
if _, ok := u.UpstreamPeerTrustBundles.Get(uid.Peer); !ok {
|
|
// The trust bundle for this upstream is not available yet, skip for now.
|
|
return true
|
|
}
|
|
out = append(out, uid)
|
|
return true
|
|
})
|
|
return out
|
|
}
|