2022-03-08 19:37:24 +00:00
|
|
|
package xdscommon
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/golang/protobuf/proto"
|
2022-03-15 14:07:40 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/consul/agent/connect"
|
|
|
|
"github.com/hashicorp/consul/agent/proxycfg"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
|
|
"github.com/hashicorp/consul/api"
|
2022-03-08 19:37:24 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Resource types in xDS v3. These are copied from
|
|
|
|
// envoyproxy/go-control-plane/pkg/resource/v3/resource.go since we don't need any of
|
|
|
|
// the rest of that package.
|
|
|
|
apiTypePrefix = "type.googleapis.com/"
|
|
|
|
|
|
|
|
// EndpointType is the TypeURL for Endpoint discovery responses.
|
|
|
|
EndpointType = apiTypePrefix + "envoy.config.endpoint.v3.ClusterLoadAssignment"
|
|
|
|
|
|
|
|
// ClusterType is the TypeURL for Cluster discovery responses.
|
|
|
|
ClusterType = apiTypePrefix + "envoy.config.cluster.v3.Cluster"
|
|
|
|
|
|
|
|
// RouteType is the TypeURL for Route discovery responses.
|
|
|
|
RouteType = apiTypePrefix + "envoy.config.route.v3.RouteConfiguration"
|
|
|
|
|
|
|
|
// ListenerType is the TypeURL for Listener discovery responses.
|
|
|
|
ListenerType = apiTypePrefix + "envoy.config.listener.v3.Listener"
|
|
|
|
)
|
|
|
|
|
|
|
|
type IndexedResources struct {
|
|
|
|
// Index is a map of typeURL => resourceName => resource
|
|
|
|
Index map[string]map[string]proto.Message
|
|
|
|
|
|
|
|
// ChildIndex is a map of typeURL => parentResourceName => list of
|
|
|
|
// childResourceNames. This only applies if the child and parent do not
|
|
|
|
// share a name.
|
|
|
|
ChildIndex map[string]map[string][]string
|
|
|
|
}
|
|
|
|
|
|
|
|
func EmptyIndexedResources() *IndexedResources {
|
|
|
|
return &IndexedResources{
|
|
|
|
Index: map[string]map[string]proto.Message{
|
|
|
|
ListenerType: make(map[string]proto.Message),
|
|
|
|
RouteType: make(map[string]proto.Message),
|
|
|
|
ClusterType: make(map[string]proto.Message),
|
|
|
|
EndpointType: make(map[string]proto.Message),
|
|
|
|
},
|
|
|
|
ChildIndex: map[string]map[string][]string{
|
|
|
|
ListenerType: make(map[string][]string),
|
|
|
|
ClusterType: make(map[string][]string),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2022-03-15 14:07:40 +00:00
|
|
|
|
|
|
|
type ServiceConfig struct {
|
|
|
|
// Kind identifies the final proxy kind that will make the request to the
|
|
|
|
// destination service.
|
2022-12-19 20:19:37 +00:00
|
|
|
Kind api.ServiceKind
|
|
|
|
EnvoyExtensions []api.EnvoyExtension
|
2022-03-15 14:07:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// PluginConfiguration is passed into Envoy plugins. It should depend on the
|
|
|
|
// API client rather than the structs package because the API client is meant
|
|
|
|
// to be public.
|
|
|
|
type PluginConfiguration struct {
|
|
|
|
// ServiceConfigs is a mapping from service names to the data Envoy plugins
|
|
|
|
// need to override the default Envoy configurations.
|
|
|
|
ServiceConfigs map[api.CompoundServiceName]ServiceConfig
|
|
|
|
|
|
|
|
// SNIToServiceName is a mapping from SNIs to service names. This allows
|
|
|
|
// Envoy plugins to easily convert from an SNI Envoy resource name to the
|
|
|
|
// associated service's CompoundServiceName
|
|
|
|
SNIToServiceName map[string]api.CompoundServiceName
|
|
|
|
|
|
|
|
// EnvoyIDToServiceName is a mapping from EnvoyIDs to service names. This allows
|
|
|
|
// Envoy plugins to easily convert from an EnvoyID Envoy resource name to the
|
|
|
|
// associated service's CompoundServiceName
|
|
|
|
EnvoyIDToServiceName map[string]api.CompoundServiceName
|
|
|
|
|
2022-03-31 20:24:46 +00:00
|
|
|
// Kind is mode the local Envoy proxy is running in. For now, only
|
|
|
|
// terminating gateways are supported.
|
2022-03-15 14:07:40 +00:00
|
|
|
Kind api.ServiceKind
|
|
|
|
}
|
|
|
|
|
|
|
|
// MakePluginConfiguration generates the configuration that will be sent to
|
|
|
|
// Envoy plugins.
|
|
|
|
func MakePluginConfiguration(cfgSnap *proxycfg.ConfigSnapshot) PluginConfiguration {
|
|
|
|
serviceConfigs := make(map[api.CompoundServiceName]ServiceConfig)
|
|
|
|
sniMappings := make(map[string]api.CompoundServiceName)
|
|
|
|
envoyIDMappings := make(map[string]api.CompoundServiceName)
|
|
|
|
|
|
|
|
trustDomain := ""
|
|
|
|
if cfgSnap.Roots != nil {
|
|
|
|
trustDomain = cfgSnap.Roots.TrustDomain
|
|
|
|
}
|
|
|
|
|
|
|
|
switch cfgSnap.Kind {
|
2022-05-05 20:39:39 +00:00
|
|
|
case structs.ServiceKindConnectProxy:
|
|
|
|
connectProxies := make(map[proxycfg.UpstreamID]struct{})
|
|
|
|
for uid, upstreamData := range cfgSnap.ConnectProxy.WatchedUpstreamEndpoints {
|
|
|
|
for _, serviceNodes := range upstreamData {
|
|
|
|
// Lambdas and likely other integrations won't be attached to nodes.
|
|
|
|
// After agentless, we may need to reconsider this.
|
|
|
|
if len(serviceNodes) == 0 {
|
|
|
|
connectProxies[uid] = struct{}{}
|
|
|
|
}
|
|
|
|
for _, serviceNode := range serviceNodes {
|
|
|
|
if serviceNode.Service.Kind == structs.ServiceKindTypical || serviceNode.Service.Kind == structs.ServiceKindConnectProxy {
|
|
|
|
connectProxies[uid] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-03 21:42:50 +00:00
|
|
|
// TODO(peering): consider PeerUpstreamEndpoints in addition to DiscoveryChain
|
|
|
|
|
2022-05-05 20:39:39 +00:00
|
|
|
for uid, dc := range cfgSnap.ConnectProxy.DiscoveryChain {
|
|
|
|
if _, ok := connectProxies[uid]; !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
serviceConfigs[upstreamIDToCompoundServiceName(uid)] = ServiceConfig{
|
2022-12-19 20:19:37 +00:00
|
|
|
Kind: api.ServiceKindConnectProxy,
|
|
|
|
EnvoyExtensions: convertEnvoyExtensions(dc.EnvoyExtensions),
|
2022-05-05 20:39:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
compoundServiceName := upstreamIDToCompoundServiceName(uid)
|
|
|
|
meta := uid.EnterpriseMeta
|
|
|
|
sni := connect.ServiceSNI(uid.Name, "", meta.NamespaceOrDefault(), meta.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain)
|
|
|
|
sniMappings[sni] = compoundServiceName
|
|
|
|
envoyIDMappings[uid.EnvoyID()] = compoundServiceName
|
|
|
|
}
|
2022-03-15 14:07:40 +00:00
|
|
|
case structs.ServiceKindTerminatingGateway:
|
|
|
|
for svc, c := range cfgSnap.TerminatingGateway.ServiceConfigs {
|
|
|
|
compoundServiceName := serviceNameToCompoundServiceName(svc)
|
|
|
|
serviceConfigs[compoundServiceName] = ServiceConfig{
|
2022-12-19 20:19:37 +00:00
|
|
|
EnvoyExtensions: convertEnvoyExtensions(c.EnvoyExtensions),
|
|
|
|
Kind: api.ServiceKindTerminatingGateway,
|
2022-03-15 14:07:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sni := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain)
|
|
|
|
sniMappings[sni] = compoundServiceName
|
|
|
|
|
|
|
|
envoyID := proxycfg.NewUpstreamIDFromServiceName(svc)
|
|
|
|
envoyIDMappings[envoyID.EnvoyID()] = compoundServiceName
|
2022-04-13 15:45:25 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
sniMappings[sni] = compoundServiceName
|
|
|
|
}
|
|
|
|
}
|
2022-03-15 14:07:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return PluginConfiguration{
|
|
|
|
ServiceConfigs: serviceConfigs,
|
|
|
|
SNIToServiceName: sniMappings,
|
|
|
|
EnvoyIDToServiceName: envoyIDMappings,
|
|
|
|
Kind: api.ServiceKind(cfgSnap.Kind),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func serviceNameToCompoundServiceName(svc structs.ServiceName) api.CompoundServiceName {
|
|
|
|
return api.CompoundServiceName{
|
|
|
|
Name: svc.Name,
|
|
|
|
Partition: svc.PartitionOrDefault(),
|
|
|
|
Namespace: svc.NamespaceOrDefault(),
|
|
|
|
}
|
|
|
|
}
|
2022-05-05 20:39:39 +00:00
|
|
|
|
|
|
|
func upstreamIDToCompoundServiceName(uid proxycfg.UpstreamID) api.CompoundServiceName {
|
|
|
|
return api.CompoundServiceName{
|
|
|
|
Name: uid.Name,
|
|
|
|
Partition: uid.PartitionOrDefault(),
|
|
|
|
Namespace: uid.NamespaceOrDefault(),
|
|
|
|
}
|
|
|
|
}
|
2022-12-19 20:19:37 +00:00
|
|
|
|
|
|
|
func convertEnvoyExtensions(structExtensions []structs.EnvoyExtension) []api.EnvoyExtension {
|
|
|
|
var extensions []api.EnvoyExtension
|
|
|
|
|
|
|
|
for _, e := range structExtensions {
|
|
|
|
extensions = append(extensions, api.EnvoyExtension{
|
|
|
|
Name: e.Name,
|
|
|
|
Required: e.Required,
|
|
|
|
Arguments: e.Arguments,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return extensions
|
|
|
|
}
|