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.
264 lines
9.1 KiB
264 lines
9.1 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: MPL-2.0 |
|
|
|
package configentry |
|
|
|
import ( |
|
"fmt" |
|
|
|
"github.com/hashicorp/go-hclog" |
|
memdb "github.com/hashicorp/go-memdb" |
|
"github.com/imdario/mergo" |
|
"github.com/mitchellh/copystructure" |
|
|
|
"github.com/hashicorp/consul/acl" |
|
"github.com/hashicorp/consul/agent/structs" |
|
) |
|
|
|
type StateStore interface { |
|
ReadResolvedServiceConfigEntries(memdb.WatchSet, string, *acl.EnterpriseMeta, []structs.ServiceID, structs.ProxyMode) (uint64, *ResolvedServiceConfigSet, error) |
|
} |
|
|
|
// MergeNodeServiceWithCentralConfig merges a service instance (NodeService) with the |
|
// proxy-defaults/global and service-defaults/:service config entries. |
|
// This common helper is used by the blocking query function of different RPC endpoints |
|
// that need to return a fully resolved service defintion. |
|
func MergeNodeServiceWithCentralConfig( |
|
ws memdb.WatchSet, |
|
state StateStore, |
|
ns *structs.NodeService, |
|
logger hclog.Logger) (uint64, *structs.NodeService, error) { |
|
|
|
serviceName := ns.Service |
|
var upstreams []structs.PeeredServiceName |
|
if ns.IsSidecarProxy() { |
|
// This is a sidecar proxy, ignore the proxy service's config since we are |
|
// managed by the target service config. |
|
serviceName = ns.Proxy.DestinationServiceName |
|
|
|
// Also if we have any upstreams defined, add them to the defaults lookup request |
|
// so we can learn about their configs. |
|
for _, us := range ns.Proxy.Upstreams { |
|
if us.DestinationType == "" || us.DestinationType == structs.UpstreamDestTypeService { |
|
psn := us.DestinationID() |
|
if psn.Peer == "" { |
|
psn.ServiceName.EnterpriseMeta.Merge(&ns.EnterpriseMeta) |
|
} else { |
|
// Peer services should not have their namespace overwritten. |
|
psn.ServiceName.EnterpriseMeta.OverridePartition(ns.EnterpriseMeta.PartitionOrDefault()) |
|
} |
|
upstreams = append(upstreams, psn) |
|
} |
|
} |
|
} |
|
|
|
configReq := &structs.ServiceConfigRequest{ |
|
Name: serviceName, |
|
MeshGateway: ns.Proxy.MeshGateway, |
|
Mode: ns.Proxy.Mode, |
|
UpstreamServiceNames: upstreams, |
|
EnterpriseMeta: ns.EnterpriseMeta, |
|
} |
|
|
|
// prefer using this vs directly calling the ConfigEntry.ResolveServiceConfig RPC |
|
// so as to pass down the same watch set to also watch on changes to |
|
// proxy-defaults/global and service-defaults. |
|
cfgIndex, configEntries, err := state.ReadResolvedServiceConfigEntries( |
|
ws, |
|
configReq.Name, |
|
&configReq.EnterpriseMeta, |
|
configReq.GetLocalUpstreamIDs(), |
|
configReq.Mode, |
|
) |
|
if err != nil { |
|
return 0, nil, fmt.Errorf("Failure looking up service config entries for %s: %v", |
|
ns.ID, err) |
|
} |
|
|
|
defaults, err := ComputeResolvedServiceConfig( |
|
configReq, |
|
configEntries, |
|
logger, |
|
) |
|
if err != nil { |
|
return 0, nil, fmt.Errorf("Failure computing service defaults for %s: %v", |
|
ns.ID, err) |
|
} |
|
|
|
mergedns, err := MergeServiceConfig(defaults, ns) |
|
if err != nil { |
|
return 0, nil, fmt.Errorf("Failure merging service definition with config entry defaults for %s: %v", |
|
ns.ID, err) |
|
} |
|
|
|
return cfgIndex, mergedns, nil |
|
} |
|
|
|
// MergeServiceConfig merges the service into defaults to produce the final effective |
|
// config for the specified service. |
|
func MergeServiceConfig(defaults *structs.ServiceConfigResponse, service *structs.NodeService) (*structs.NodeService, error) { |
|
if defaults == nil { |
|
return service, nil |
|
} |
|
|
|
// We don't want to change s.registration in place since it is our source of |
|
// truth about what was actually registered before defaults applied. So copy |
|
// it first. |
|
nsRaw, err := copystructure.Copy(service) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// Merge proxy defaults |
|
ns := nsRaw.(*structs.NodeService) |
|
|
|
if err := mergo.Merge(&ns.Proxy.Config, defaults.ProxyConfig); err != nil { |
|
return nil, err |
|
} |
|
if err := mergo.Merge(&ns.Proxy.Expose, defaults.Expose); err != nil { |
|
return nil, err |
|
} |
|
if err := mergo.Merge(&ns.Proxy.AccessLogs, defaults.AccessLogs); err != nil { |
|
return nil, err |
|
} |
|
|
|
// defaults.EnvoyExtensions contains the extensions from the proxy defaults config entry followed by extensions from |
|
// the service defaults config entry. This adds the extensions to structs.NodeService.Proxy which in turn is copied |
|
// into the proxycfg snapshot to ensure the local service's extensions are accessible from the snapshot. |
|
// |
|
// This will replace any existing extensions in the NodeService but that is ok because defaults.EnvoyExtensions |
|
// should have the latest extensions computed from service defaults and proxy defaults. |
|
ns.Proxy.EnvoyExtensions = nil |
|
if len(defaults.EnvoyExtensions) > 0 { |
|
nsExtensions := make([]structs.EnvoyExtension, len(defaults.EnvoyExtensions)) |
|
for i, ext := range defaults.EnvoyExtensions { |
|
nsExtensions[i] = structs.EnvoyExtension{ |
|
Name: ext.Name, |
|
Required: ext.Required, |
|
Arguments: ext.Arguments, |
|
} |
|
} |
|
ns.Proxy.EnvoyExtensions = nsExtensions |
|
} |
|
|
|
if ns.Proxy.MeshGateway.Mode == structs.MeshGatewayModeDefault { |
|
ns.Proxy.MeshGateway.Mode = defaults.MeshGateway.Mode |
|
} |
|
if ns.Proxy.Mode == structs.ProxyModeDefault { |
|
ns.Proxy.Mode = defaults.Mode |
|
} |
|
if ns.Proxy.TransparentProxy.OutboundListenerPort == 0 { |
|
ns.Proxy.TransparentProxy.OutboundListenerPort = defaults.TransparentProxy.OutboundListenerPort |
|
} |
|
if !ns.Proxy.TransparentProxy.DialedDirectly { |
|
ns.Proxy.TransparentProxy.DialedDirectly = defaults.TransparentProxy.DialedDirectly |
|
} |
|
|
|
if ns.Proxy.MutualTLSMode == structs.MutualTLSModeDefault { |
|
ns.Proxy.MutualTLSMode = defaults.MutualTLSMode |
|
} |
|
|
|
// remoteUpstreams contains synthetic Upstreams generated from central config (service-defaults.UpstreamConfigs). |
|
remoteUpstreams := make(map[structs.PeeredServiceName]structs.Upstream) |
|
|
|
// If the arguments did not fully normalize tenancy stuff, take care of that now. |
|
entMeta := ns.EnterpriseMeta |
|
entMeta.Normalize() |
|
|
|
for _, us := range defaults.UpstreamConfigs { |
|
parsed, err := structs.ParseUpstreamConfigNoDefaults(us.Config) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to parse upstream config map for %s: %v", us.Upstream.String(), err) |
|
} |
|
|
|
// If the defaults did not fully normalize tenancy stuff, take care of |
|
// that now too. |
|
psn := us.Upstream // only normalize the copy |
|
psn.ServiceName.EnterpriseMeta.Normalize() |
|
|
|
// Normalize the partition field specially. |
|
if psn.Peer != "" { |
|
psn.ServiceName.OverridePartition(entMeta.PartitionOrDefault()) |
|
} |
|
|
|
remoteUpstreams[psn] = structs.Upstream{ |
|
DestinationNamespace: psn.ServiceName.NamespaceOrDefault(), |
|
DestinationPartition: psn.ServiceName.PartitionOrDefault(), |
|
DestinationName: psn.ServiceName.Name, |
|
DestinationPeer: psn.Peer, |
|
Config: us.Config, |
|
MeshGateway: parsed.MeshGateway, |
|
CentrallyConfigured: true, |
|
} |
|
} |
|
|
|
// localUpstreams stores the upstreams seen from the local registration so that we can merge in the synthetic entries. |
|
// In transparent proxy mode ns.Proxy.Upstreams will likely be empty because users do not need to define upstreams explicitly. |
|
// So to store upstream-specific flags from central config, we add entries to ns.Proxy.Upstreams with those values. |
|
localUpstreams := make(map[structs.PeeredServiceName]struct{}) |
|
|
|
// Merge upstream defaults into the local registration |
|
for i := range ns.Proxy.Upstreams { |
|
// Get a pointer not a value copy of the upstream struct |
|
us := &ns.Proxy.Upstreams[i] |
|
if us.DestinationType != "" && us.DestinationType != structs.UpstreamDestTypeService { |
|
continue |
|
} |
|
|
|
uid := us.DestinationID() |
|
|
|
// Normalize the partition field specially. |
|
if uid.Peer != "" { |
|
uid.ServiceName.OverridePartition(entMeta.PartitionOrDefault()) |
|
} |
|
|
|
localUpstreams[uid] = struct{}{} |
|
remoteCfg, ok := remoteUpstreams[uid] |
|
if !ok { |
|
// No config defaults to merge |
|
continue |
|
} |
|
|
|
// The local upstream config mode has the highest precedence, so only overwrite when it's set to the default |
|
if us.MeshGateway.Mode == structs.MeshGatewayModeDefault { |
|
us.MeshGateway.Mode = remoteCfg.MeshGateway.Mode |
|
} |
|
|
|
preMergeProtocol, found := us.Config["protocol"] |
|
// Merge in everything else that is read from the map |
|
if err := mergo.Merge(&us.Config, remoteCfg.Config); err != nil { |
|
return nil, err |
|
} |
|
|
|
// Reset the protocol to its pre-merged version for peering upstreams. |
|
if us.DestinationPeer != "" { |
|
if found { |
|
us.Config["protocol"] = preMergeProtocol |
|
} else { |
|
delete(us.Config, "protocol") |
|
} |
|
} |
|
|
|
// Delete the mesh gateway key from opaque config since this is the value that was resolved from |
|
// the servers and NOT the final merged value for this upstream. |
|
// Note that we use the "mesh_gateway" key and not other variants like "MeshGateway" because |
|
// UpstreamConfig.MergeInto and ResolveServiceConfig only use "mesh_gateway". |
|
delete(us.Config, "mesh_gateway") |
|
} |
|
|
|
// Ensure upstreams present in central config are represented in the local configuration. |
|
// This does not apply outside of transparent mode because in that situation every possible upstream already exists |
|
// inside of ns.Proxy.Upstreams. |
|
if ns.Proxy.Mode == structs.ProxyModeTransparent { |
|
for id, remote := range remoteUpstreams { |
|
if _, ok := localUpstreams[id]; ok { |
|
// Remote upstream is already present locally |
|
continue |
|
} |
|
|
|
ns.Proxy.Upstreams = append(ns.Proxy.Upstreams, remote) |
|
} |
|
} |
|
|
|
return ns, err |
|
}
|
|
|