consul/agent/xds/extensionruntime/runtime_config.go

198 lines
7.7 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 13:12:13 +00:00
// 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
}