mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
429 lines
16 KiB
429 lines
16 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package proxycfg |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
"strings" |
|
|
|
cachetype "github.com/hashicorp/consul/agent/cache-types" |
|
"github.com/hashicorp/consul/agent/leafcert" |
|
"github.com/hashicorp/consul/agent/structs" |
|
"github.com/hashicorp/consul/proto/private/pbpeering" |
|
) |
|
|
|
type handlerTerminatingGateway struct { |
|
handlerState |
|
} |
|
|
|
// initialize sets up the initial watches needed based on the terminating-gateway registration |
|
func (s *handlerTerminatingGateway) initialize(ctx context.Context) (ConfigSnapshot, error) { |
|
snap := newConfigSnapshotFromServiceInstance(s.serviceInstance, s.stateConfig) |
|
// Watch for root changes |
|
err := s.dataSources.CARoots.Notify(ctx, &structs.DCSpecificRequest{ |
|
Datacenter: s.source.Datacenter, |
|
QueryOptions: structs.QueryOptions{Token: s.token}, |
|
Source: *s.source, |
|
}, rootsWatchID, s.ch) |
|
if err != nil { |
|
s.logger.Error("failed to register watch for root changes", "error", err) |
|
return snap, err |
|
} |
|
|
|
// Get information about the entire service mesh. |
|
err = s.dataSources.ConfigEntry.Notify(ctx, &structs.ConfigEntryQuery{ |
|
Kind: structs.MeshConfig, |
|
Name: structs.MeshConfigMesh, |
|
Datacenter: s.source.Datacenter, |
|
QueryOptions: structs.QueryOptions{Token: s.token}, |
|
EnterpriseMeta: *structs.DefaultEnterpriseMetaInPartition(s.proxyID.PartitionOrDefault()), |
|
}, meshConfigEntryID, s.ch) |
|
if err != nil { |
|
return snap, err |
|
} |
|
|
|
// Watch for the terminating-gateway's linked services |
|
err = s.dataSources.GatewayServices.Notify(ctx, &structs.ServiceSpecificRequest{ |
|
Datacenter: s.source.Datacenter, |
|
QueryOptions: structs.QueryOptions{Token: s.token}, |
|
ServiceName: s.service, |
|
EnterpriseMeta: s.proxyID.EnterpriseMeta, |
|
}, gatewayServicesWatchID, s.ch) |
|
if err != nil { |
|
s.logger.Error("failed to register watch for linked services", "error", err) |
|
return snap, err |
|
} |
|
|
|
snap.TerminatingGateway.WatchedServices = make(map[structs.ServiceName]context.CancelFunc) |
|
snap.TerminatingGateway.WatchedIntentions = make(map[structs.ServiceName]context.CancelFunc) |
|
snap.TerminatingGateway.Intentions = make(map[structs.ServiceName]structs.SimplifiedIntentions) |
|
snap.TerminatingGateway.WatchedLeaves = make(map[structs.ServiceName]context.CancelFunc) |
|
snap.TerminatingGateway.ServiceLeaves = make(map[structs.ServiceName]*structs.IssuedCert) |
|
snap.TerminatingGateway.WatchedConfigs = make(map[structs.ServiceName]context.CancelFunc) |
|
snap.TerminatingGateway.ServiceConfigs = make(map[structs.ServiceName]*structs.ServiceConfigResponse) |
|
snap.TerminatingGateway.WatchedResolvers = make(map[structs.ServiceName]context.CancelFunc) |
|
snap.TerminatingGateway.ServiceResolvers = make(map[structs.ServiceName]*structs.ServiceResolverConfigEntry) |
|
snap.TerminatingGateway.ServiceResolversSet = make(map[structs.ServiceName]bool) |
|
snap.TerminatingGateway.ServiceGroups = make(map[structs.ServiceName]structs.CheckServiceNodes) |
|
snap.TerminatingGateway.GatewayServices = make(map[structs.ServiceName]structs.GatewayService) |
|
snap.TerminatingGateway.DestinationServices = make(map[structs.ServiceName]structs.GatewayService) |
|
snap.TerminatingGateway.HostnameServices = make(map[structs.ServiceName]structs.CheckServiceNodes) |
|
snap.TerminatingGateway.WatchedInboundPeerTrustBundles = make(map[structs.ServiceName]context.CancelFunc) |
|
snap.TerminatingGateway.InboundPeerTrustBundles = make(map[structs.ServiceName][]*pbpeering.PeeringTrustBundle) |
|
return snap, nil |
|
} |
|
|
|
func (s *handlerTerminatingGateway) handleUpdate(ctx context.Context, u UpdateEvent, snap *ConfigSnapshot) error { |
|
if u.Err != nil { |
|
return fmt.Errorf("error filling agent cache: %v", u.Err) |
|
} |
|
logger := s.logger |
|
|
|
switch { |
|
case u.CorrelationID == rootsWatchID: |
|
roots, ok := u.Result.(*structs.IndexedCARoots) |
|
if !ok { |
|
return fmt.Errorf("invalid type for response: %T", u.Result) |
|
} |
|
snap.Roots = roots |
|
|
|
case u.CorrelationID == meshConfigEntryID: |
|
resp, ok := u.Result.(*structs.ConfigEntryResponse) |
|
if !ok { |
|
return fmt.Errorf("invalid type for response: %T", u.Result) |
|
} |
|
|
|
if resp.Entry != nil { |
|
meshConf, ok := resp.Entry.(*structs.MeshConfigEntry) |
|
if !ok { |
|
return fmt.Errorf("invalid type for config entry: %T", resp.Entry) |
|
} |
|
snap.TerminatingGateway.MeshConfig = meshConf |
|
} else { |
|
snap.TerminatingGateway.MeshConfig = nil |
|
} |
|
snap.TerminatingGateway.MeshConfigSet = true |
|
|
|
// Update watches based on the current list of services associated with the terminating-gateway |
|
case u.CorrelationID == gatewayServicesWatchID: |
|
services, ok := u.Result.(*structs.IndexedGatewayServices) |
|
if !ok { |
|
return fmt.Errorf("invalid type for response: %T", u.Result) |
|
} |
|
|
|
svcMap := make(map[structs.ServiceName]struct{}) |
|
for _, svc := range services.Services { |
|
// Make sure to add every service to this map, we use it to cancel watches below. |
|
svcMap[svc.Service] = struct{}{} |
|
|
|
// Store the gateway <-> service mapping for TLS origination |
|
if svc.ServiceKind == structs.GatewayServiceKindDestination { |
|
snap.TerminatingGateway.DestinationServices[svc.Service] = *svc |
|
} else { |
|
snap.TerminatingGateway.GatewayServices[svc.Service] = *svc |
|
} |
|
|
|
// Watch the health endpoint to discover endpoints for the service |
|
if _, ok := snap.TerminatingGateway.WatchedServices[svc.Service]; !ok && !(svc.ServiceKind == structs.GatewayServiceKindDestination) { |
|
|
|
ctx, cancel := context.WithCancel(ctx) |
|
err := s.dataSources.Health.Notify(ctx, &structs.ServiceSpecificRequest{ |
|
Datacenter: s.source.Datacenter, |
|
QueryOptions: structs.QueryOptions{Token: s.token}, |
|
ServiceName: svc.Service.Name, |
|
EnterpriseMeta: svc.Service.EnterpriseMeta, |
|
|
|
// The gateway acts as the service's proxy, so we do NOT want to discover other proxies |
|
Connect: false, |
|
}, externalServiceIDPrefix+svc.Service.String(), s.ch) |
|
|
|
if err != nil { |
|
logger.Error("failed to register watch for external-service", |
|
"service", svc.Service.String(), |
|
"error", err, |
|
) |
|
cancel() |
|
return err |
|
} |
|
snap.TerminatingGateway.WatchedServices[svc.Service] = cancel |
|
} |
|
|
|
// Watch intentions with this service as their destination |
|
// The gateway will enforce intentions for connections to the service |
|
if _, ok := snap.TerminatingGateway.WatchedIntentions[svc.Service]; !ok { |
|
ctx, cancel := context.WithCancel(ctx) |
|
err := s.dataSources.Intentions.Notify(ctx, &structs.ServiceSpecificRequest{ |
|
Datacenter: s.source.Datacenter, |
|
QueryOptions: structs.QueryOptions{Token: s.token}, |
|
EnterpriseMeta: svc.Service.EnterpriseMeta, |
|
ServiceName: svc.Service.Name, |
|
}, serviceIntentionsIDPrefix+svc.Service.String(), s.ch) |
|
|
|
if err != nil { |
|
logger.Error("failed to register watch for service-intentions", |
|
"service", svc.Service.String(), |
|
"error", err, |
|
) |
|
cancel() |
|
return err |
|
} |
|
snap.TerminatingGateway.WatchedIntentions[svc.Service] = cancel |
|
} |
|
|
|
if _, ok := snap.TerminatingGateway.WatchedInboundPeerTrustBundles[svc.Service]; !ok { |
|
ctx, cancel := context.WithCancel(ctx) |
|
|
|
err := s.dataSources.TrustBundleList.Notify(ctx, &cachetype.TrustBundleListRequest{ |
|
Request: &pbpeering.TrustBundleListByServiceRequest{ |
|
ServiceName: svc.Service.Name, |
|
Namespace: svc.Service.EnterpriseMeta.NamespaceOrDefault(), |
|
Partition: svc.Service.EnterpriseMeta.PartitionOrDefault(), |
|
}, |
|
QueryOptions: structs.QueryOptions{Token: s.token}, |
|
}, peerTrustBundleIDPrefix+svc.Service.String(), s.ch) |
|
|
|
if err != nil { |
|
logger.Error("failed to register watch for peer trust bundles", |
|
"service", svc.Service.String(), |
|
"error", err, |
|
) |
|
cancel() |
|
return err |
|
} |
|
snap.TerminatingGateway.WatchedInboundPeerTrustBundles[svc.Service] = cancel |
|
} |
|
|
|
// Watch leaf certificate for the service |
|
// This cert is used to terminate mTLS connections on the service's behalf |
|
if _, ok := snap.TerminatingGateway.WatchedLeaves[svc.Service]; !ok { |
|
ctx, cancel := context.WithCancel(ctx) |
|
err := s.dataSources.LeafCertificate.Notify(ctx, &leafcert.ConnectCALeafRequest{ |
|
Datacenter: s.source.Datacenter, |
|
Token: s.token, |
|
Service: svc.Service.Name, |
|
EnterpriseMeta: svc.Service.EnterpriseMeta, |
|
}, serviceLeafIDPrefix+svc.Service.String(), s.ch) |
|
|
|
if err != nil { |
|
logger.Error("failed to register watch for a service-leaf", |
|
"service", svc.Service.String(), |
|
"error", err, |
|
) |
|
cancel() |
|
return err |
|
} |
|
snap.TerminatingGateway.WatchedLeaves[svc.Service] = cancel |
|
} |
|
|
|
// Watch service configs for the service. |
|
// These are used to determine the protocol for the target service. |
|
if _, ok := snap.TerminatingGateway.WatchedConfigs[svc.Service]; !ok { |
|
ctx, cancel := context.WithCancel(ctx) |
|
err := s.dataSources.ResolvedServiceConfig.Notify(ctx, &structs.ServiceConfigRequest{ |
|
Datacenter: s.source.Datacenter, |
|
QueryOptions: structs.QueryOptions{Token: s.token}, |
|
Name: svc.Service.Name, |
|
EnterpriseMeta: svc.Service.EnterpriseMeta, |
|
}, serviceConfigIDPrefix+svc.Service.String(), s.ch) |
|
|
|
if err != nil { |
|
logger.Error("failed to register watch for a resolved service config", |
|
"service", svc.Service.String(), |
|
"error", err, |
|
) |
|
cancel() |
|
return err |
|
} |
|
snap.TerminatingGateway.WatchedConfigs[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 && !(svc.ServiceKind == structs.GatewayServiceKindDestination) { |
|
|
|
ctx, cancel := context.WithCancel(ctx) |
|
err := s.dataSources.ConfigEntry.Notify(ctx, &structs.ConfigEntryQuery{ |
|
Datacenter: s.source.Datacenter, |
|
QueryOptions: structs.QueryOptions{Token: s.token}, |
|
Kind: structs.ServiceResolver, |
|
Name: svc.Service.Name, |
|
EnterpriseMeta: svc.Service.EnterpriseMeta, |
|
}, serviceResolverIDPrefix+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 |
|
} |
|
} |
|
|
|
// Delete gateway service mapping for services that were not in the update |
|
for sn := range snap.TerminatingGateway.GatewayServices { |
|
if _, ok := svcMap[sn]; !ok { |
|
delete(snap.TerminatingGateway.GatewayServices, sn) |
|
} |
|
} |
|
|
|
// Delete endpoint service mapping for services that were not in the update |
|
for sn := range snap.TerminatingGateway.DestinationServices { |
|
if _, ok := svcMap[sn]; !ok { |
|
delete(snap.TerminatingGateway.DestinationServices, sn) |
|
} |
|
} |
|
|
|
// Clean up services with hostname mapping for services that were not in the update |
|
for sn := range snap.TerminatingGateway.HostnameServices { |
|
if _, ok := svcMap[sn]; !ok { |
|
delete(snap.TerminatingGateway.HostnameServices, sn) |
|
} |
|
} |
|
|
|
// Cancel service instance watches for services that were not in the update |
|
for sn, cancelFn := range snap.TerminatingGateway.WatchedServices { |
|
if _, ok := svcMap[sn]; !ok { |
|
logger.Debug("canceling watch for service", "service", sn.String()) |
|
delete(snap.TerminatingGateway.WatchedServices, sn) |
|
delete(snap.TerminatingGateway.ServiceGroups, sn) |
|
cancelFn() |
|
} |
|
} |
|
|
|
// Cancel leaf cert watches for services that were not in the update |
|
for sn, cancelFn := range snap.TerminatingGateway.WatchedLeaves { |
|
if _, ok := svcMap[sn]; !ok { |
|
logger.Debug("canceling watch for leaf cert", "service", sn.String()) |
|
delete(snap.TerminatingGateway.WatchedLeaves, sn) |
|
delete(snap.TerminatingGateway.ServiceLeaves, sn) |
|
cancelFn() |
|
} |
|
} |
|
|
|
// Cancel service config watches for services that were not in the update |
|
for sn, cancelFn := range snap.TerminatingGateway.WatchedConfigs { |
|
if _, ok := svcMap[sn]; !ok { |
|
logger.Debug("canceling watch for resolved service config", "service", sn.String()) |
|
delete(snap.TerminatingGateway.WatchedConfigs, sn) |
|
delete(snap.TerminatingGateway.ServiceConfigs, sn) |
|
cancelFn() |
|
} |
|
} |
|
|
|
// Cancel service-resolver watches for services that were not in the update |
|
for sn, cancelFn := range snap.TerminatingGateway.WatchedResolvers { |
|
if _, ok := svcMap[sn]; !ok { |
|
logger.Debug("canceling watch for service-resolver", "service", sn.String()) |
|
delete(snap.TerminatingGateway.WatchedResolvers, sn) |
|
delete(snap.TerminatingGateway.ServiceResolvers, sn) |
|
delete(snap.TerminatingGateway.ServiceResolversSet, sn) |
|
cancelFn() |
|
} |
|
} |
|
|
|
// Cancel watches for peered trust bundle that were not in the update |
|
for sn, cancelFn := range snap.TerminatingGateway.WatchedInboundPeerTrustBundles { |
|
if _, ok := svcMap[sn]; !ok { |
|
logger.Debug("canceling watch for peered trust bundle", "service", sn.String()) |
|
delete(snap.TerminatingGateway.WatchedInboundPeerTrustBundles, sn) |
|
delete(snap.TerminatingGateway.InboundPeerTrustBundles, sn) |
|
cancelFn() |
|
} |
|
} |
|
|
|
// Cancel intention watches for services that were not in the update |
|
for sn, cancelFn := range snap.TerminatingGateway.WatchedIntentions { |
|
if _, ok := svcMap[sn]; !ok { |
|
logger.Debug("canceling watch for intention", "service", sn.String()) |
|
delete(snap.TerminatingGateway.WatchedIntentions, sn) |
|
delete(snap.TerminatingGateway.Intentions, sn) |
|
cancelFn() |
|
} |
|
} |
|
|
|
case strings.HasPrefix(u.CorrelationID, externalServiceIDPrefix): |
|
resp, ok := u.Result.(*structs.IndexedCheckServiceNodes) |
|
if !ok { |
|
return fmt.Errorf("invalid type for response: %T", u.Result) |
|
} |
|
|
|
sn := structs.ServiceNameFromString(strings.TrimPrefix(u.CorrelationID, externalServiceIDPrefix)) |
|
delete(snap.TerminatingGateway.ServiceGroups, sn) |
|
delete(snap.TerminatingGateway.HostnameServices, sn) |
|
|
|
if len(resp.Nodes) > 0 { |
|
snap.TerminatingGateway.ServiceGroups[sn] = resp.Nodes |
|
snap.TerminatingGateway.HostnameServices[sn] = hostnameEndpoints( |
|
s.logger, |
|
snap.Locality, |
|
resp.Nodes, |
|
) |
|
} |
|
|
|
// Store leaf cert for watched service |
|
case strings.HasPrefix(u.CorrelationID, serviceLeafIDPrefix): |
|
leaf, ok := u.Result.(*structs.IssuedCert) |
|
if !ok { |
|
return fmt.Errorf("invalid type for response: %T", u.Result) |
|
} |
|
|
|
sn := structs.ServiceNameFromString(strings.TrimPrefix(u.CorrelationID, serviceLeafIDPrefix)) |
|
snap.TerminatingGateway.ServiceLeaves[sn] = leaf |
|
|
|
case strings.HasPrefix(u.CorrelationID, serviceConfigIDPrefix): |
|
serviceConfig, ok := u.Result.(*structs.ServiceConfigResponse) |
|
if !ok { |
|
return fmt.Errorf("invalid type for response: %T", u.Result) |
|
} |
|
|
|
sn := structs.ServiceNameFromString(strings.TrimPrefix(u.CorrelationID, serviceConfigIDPrefix)) |
|
snap.TerminatingGateway.ServiceConfigs[sn] = serviceConfig |
|
|
|
case strings.HasPrefix(u.CorrelationID, serviceResolverIDPrefix): |
|
resp, ok := u.Result.(*structs.ConfigEntryResponse) |
|
if !ok { |
|
return fmt.Errorf("invalid type for response: %T", u.Result) |
|
} |
|
|
|
sn := structs.ServiceNameFromString(strings.TrimPrefix(u.CorrelationID, serviceResolverIDPrefix)) |
|
// There should only ever be one entry for a service resolver within a namespace |
|
if resolver, ok := resp.Entry.(*structs.ServiceResolverConfigEntry); ok { |
|
snap.TerminatingGateway.ServiceResolvers[sn] = resolver |
|
snap.TerminatingGateway.ServiceResolversSet[sn] = true |
|
} else { |
|
// we likely have a deleted service resolver, and our cast is a nil |
|
// cast, so clear this out |
|
delete(snap.TerminatingGateway.ServiceResolvers, sn) |
|
snap.TerminatingGateway.ServiceResolversSet[sn] = false |
|
} |
|
|
|
case strings.HasPrefix(u.CorrelationID, serviceIntentionsIDPrefix): |
|
resp, ok := u.Result.(structs.SimplifiedIntentions) |
|
if !ok { |
|
return fmt.Errorf("invalid type for response: %T", u.Result) |
|
} |
|
|
|
sn := structs.ServiceNameFromString(strings.TrimPrefix(u.CorrelationID, serviceIntentionsIDPrefix)) |
|
snap.TerminatingGateway.Intentions[sn] = resp |
|
|
|
case strings.HasPrefix(u.CorrelationID, peerTrustBundleIDPrefix): |
|
resp, ok := u.Result.(*pbpeering.TrustBundleListByServiceResponse) |
|
if !ok { |
|
return fmt.Errorf("invalid type for response: %T", u.Result) |
|
} |
|
if len(resp.Bundles) > 0 { |
|
sn := structs.ServiceNameFromString(strings.TrimPrefix(u.CorrelationID, peerTrustBundleIDPrefix)) |
|
snap.TerminatingGateway.InboundPeerTrustBundles[sn] = resp.Bundles |
|
} |
|
|
|
default: |
|
// do nothing |
|
} |
|
|
|
return nil |
|
}
|
|
|