Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
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.
 
 
 
 
 
 

197 lines
7.7 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package extensionruntime
import (
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
)
func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.CompoundServiceName][]extensioncommon.RuntimeConfig {
extensionsMap := make(map[api.CompoundServiceName][]api.EnvoyExtension)
upstreamMap := make(map[api.CompoundServiceName]*extensioncommon.UpstreamData)
var kind api.ServiceKind
extensionConfigurationsMap := make(map[api.CompoundServiceName][]extensioncommon.RuntimeConfig)
trustDomain := ""
if cfgSnap.Roots != nil {
trustDomain = cfgSnap.Roots.TrustDomain
}
switch cfgSnap.Kind {
case structs.ServiceKindConnectProxy:
kind = api.ServiceKindConnectProxy
outgoingKindByService := make(map[api.CompoundServiceName]api.ServiceKind)
vipForService := make(map[api.CompoundServiceName]string)
for uid, upstreamData := range cfgSnap.ConnectProxy.WatchedUpstreamEndpoints {
sn := upstreamIDToCompoundServiceName(uid)
for _, serviceNodes := range upstreamData {
for _, serviceNode := range serviceNodes {
if serviceNode.Service == nil {
continue
}
vip := serviceNode.Service.TaggedAddresses[structs.TaggedAddressVirtualIP].Address
if vip != "" {
if _, ok := vipForService[sn]; !ok {
vipForService[sn] = vip
}
}
// Store the upstream's kind, and for ServiceKindTypical we don't do anything because we'll default
// any unset upstreams to ServiceKindConnectProxy below.
switch serviceNode.Service.Kind {
case structs.ServiceKindTypical:
default:
outgoingKindByService[sn] = api.ServiceKind(serviceNode.Service.Kind)
}
// We only need the kind from one instance, so break once we find it.
break
}
}
}
// TODO(peering): consider PeerUpstreamEndpoints in addition to DiscoveryChain
// These are the discovery chains for upstreams which have the Envoy Extensions applied to the local service.
for uid, chain := range cfgSnap.ConnectProxy.DiscoveryChain {
compoundServiceName := upstreamIDToCompoundServiceName(uid)
extensionsMap[compoundServiceName] = convertEnvoyExtensions(chain.EnvoyExtensions)
primarySNI := connect.ServiceSNI(uid.Name, "", chain.Namespace, chain.Partition, cfgSnap.Datacenter, trustDomain)
snis := make(map[string]struct{})
for _, t := range chain.Targets {
// SNI isn't set for peered services. We don't support peered services yet.
if t.SNI != "" {
snis[t.SNI] = struct{}{}
}
}
outgoingKind, ok := outgoingKindByService[compoundServiceName]
if !ok {
outgoingKind = api.ServiceKindConnectProxy
}
upstreamMap[compoundServiceName] = &extensioncommon.UpstreamData{
PrimarySNI: primarySNI,
SNIs: snis,
VIP: vipForService[compoundServiceName],
EnvoyID: uid.EnvoyID(),
OutgoingProxyKind: outgoingKind,
}
}
// Adds extensions configured for the local service to the RuntimeConfig. This only applies to
// connect-proxies because extensions are either global or tied to a specific service, so the terminating
// gateway's Envoy resources for the local service (i.e not to upstreams) would never need to be modified.
localSvc := api.CompoundServiceName{
Name: cfgSnap.Proxy.DestinationServiceName,
Namespace: cfgSnap.ProxyID.NamespaceOrDefault(),
Partition: cfgSnap.ProxyID.PartitionOrEmpty(),
}
extensionConfigurationsMap[localSvc] = []extensioncommon.RuntimeConfig{}
cfgSnapExts := convertEnvoyExtensions(cfgSnap.Proxy.EnvoyExtensions)
for _, ext := range cfgSnapExts {
extCfg := extensioncommon.RuntimeConfig{
EnvoyExtension: ext,
ServiceName: localSvc,
IsSourcedFromUpstream: false,
Upstreams: upstreamMap,
Kind: kind,
Protocol: proxyConfigProtocol(cfgSnap.Proxy.Config),
}
extensionConfigurationsMap[localSvc] = append(extensionConfigurationsMap[localSvc], extCfg)
}
case structs.ServiceKindTerminatingGateway:
kind = api.ServiceKindTerminatingGateway
for svc, c := range cfgSnap.TerminatingGateway.ServiceConfigs {
compoundServiceName := serviceNameToCompoundServiceName(svc)
extensionsMap[compoundServiceName] = convertEnvoyExtensions(c.EnvoyExtensions)
primarySNI := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain)
envoyID := proxycfg.NewUpstreamIDFromServiceName(svc)
snis := map[string]struct{}{primarySNI: {}}
resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc]
if hasResolver {
for subsetName := range resolver.Subsets {
sni := connect.ServiceSNI(svc.Name, subsetName, svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain)
snis[sni] = struct{}{}
}
}
upstreamMap[compoundServiceName] = &extensioncommon.UpstreamData{
PrimarySNI: primarySNI,
SNIs: snis,
EnvoyID: envoyID.EnvoyID(),
OutgoingProxyKind: api.ServiceKindTerminatingGateway,
}
}
}
// If applicable, include extension configuration for remote upstreams of the local service.
// This only applies to specific extensions authorized to apply to remote proxies.
for svc, exts := range extensionsMap {
extensionConfigurationsMap[svc] = []extensioncommon.RuntimeConfig{}
for _, ext := range exts {
if appliesToRemoteDownstreams(ext) {
extCfg := extensioncommon.RuntimeConfig{
EnvoyExtension: ext,
Kind: kind,
ServiceName: svc,
IsSourcedFromUpstream: true,
Upstreams: upstreamMap,
Protocol: proxyConfigProtocol(cfgSnap.Proxy.Config),
}
extensionConfigurationsMap[svc] = append(extensionConfigurationsMap[svc], extCfg)
}
}
}
return extensionConfigurationsMap
}
func serviceNameToCompoundServiceName(svc structs.ServiceName) api.CompoundServiceName {
return api.CompoundServiceName{
Name: svc.Name,
Partition: svc.PartitionOrDefault(),
Namespace: svc.NamespaceOrDefault(),
}
}
func upstreamIDToCompoundServiceName(uid proxycfg.UpstreamID) api.CompoundServiceName {
return api.CompoundServiceName{
Name: uid.Name,
Partition: uid.PartitionOrDefault(),
Namespace: uid.NamespaceOrDefault(),
}
}
func convertEnvoyExtensions(structExtensions structs.EnvoyExtensions) []api.EnvoyExtension {
return structExtensions.ToAPI()
}
func proxyConfigProtocol(cfg map[string]any) string {
if p, exists := cfg["protocol"]; exists {
if protocol, ok := p.(string); ok {
return protocol
}
}
return ""
}
// appliesToRemoteDownstreams returns true if the given extension should be applied to remote downstream proxies of the
// service targeted by the extension, rather than just the local proxy. In the context of GetRuntimeConfigurations, this
// determines whether the extension should apply to the local proxy (a downstream of the configured service).
//
// Currently, only the AWS Lambda and Validate extensions are allowed to apply to downstream proxies.
//
// See extensioncommon.RuntimeConfig.IsSourcedFromUpstream and UpstreamEnvoyExtender doc for more information. We make
// this check here out of precaution s.t. even if an unauthorized extension is erroneously constructed with the
// UpstreamEnvoyExtender, this check will not allow the upstream extension configuration to be provided.
func appliesToRemoteDownstreams(extension api.EnvoyExtension) bool {
return extension.Name == api.BuiltinAWSLambdaExtension || extension.Name == api.BuiltinValidateExtension
}