2023-03-28 22:48:58 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2023-01-30 21:35:26 +00:00
|
|
|
package extensioncommon
|
2022-03-15 14:07:40 +00:00
|
|
|
|
|
|
|
import (
|
2022-03-31 20:24:46 +00:00
|
|
|
"fmt"
|
2023-05-26 18:10:31 +00:00
|
|
|
|
2022-03-31 20:24:46 +00:00
|
|
|
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
|
|
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
2022-04-01 14:32:38 +00:00
|
|
|
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
2022-03-31 20:24:46 +00:00
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
|
|
|
|
"github.com/hashicorp/consul/api"
|
2023-02-06 17:14:35 +00:00
|
|
|
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
2022-03-15 14:07:40 +00:00
|
|
|
)
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
// ClusterMap is a map of clusters indexed by name.
|
|
|
|
type ClusterMap map[string]*envoy_cluster_v3.Cluster
|
|
|
|
|
|
|
|
// ListenerMap is a map of listeners indexed by name.
|
|
|
|
type ListenerMap map[string]*envoy_listener_v3.Listener
|
|
|
|
|
|
|
|
// RouteMap is a map of routes indexed by name.
|
|
|
|
type RouteMap map[string]*envoy_route_v3.RouteConfiguration
|
|
|
|
|
2023-01-30 21:35:26 +00:00
|
|
|
// BasicExtension is the interface that each user of BasicEnvoyExtender must implement. It
|
|
|
|
// is responsible for modifying the xDS structures based on only the state of
|
|
|
|
// the extension.
|
|
|
|
type BasicExtension interface {
|
2023-05-26 18:10:31 +00:00
|
|
|
// CanApply determines if the extension can mutate resources for the given runtime configuration.
|
2023-01-30 21:35:26 +00:00
|
|
|
CanApply(*RuntimeConfig) bool
|
|
|
|
|
|
|
|
// PatchRoute patches a route to include the custom Envoy configuration
|
|
|
|
// required to integrate with the built in extension template.
|
2023-05-26 18:10:31 +00:00
|
|
|
// See also PatchRoutes.
|
2023-01-30 21:35:26 +00:00
|
|
|
PatchRoute(*RuntimeConfig, *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error)
|
2023-01-06 17:13:40 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
// PatchRoutes patches routes to include the custom Envoy configuration
|
|
|
|
// required to integrate with the built in extension template.
|
|
|
|
// This allows extensions to operate on a collection of routes.
|
|
|
|
// For extensions that implement both PatchRoute and PatchRoutes,
|
|
|
|
// PatchRoutes is always called first with the entire collection of routes.
|
|
|
|
// Then PatchRoute is called for each individual route.
|
|
|
|
PatchRoutes(*RuntimeConfig, RouteMap) (RouteMap, error)
|
|
|
|
|
2023-01-30 21:35:26 +00:00
|
|
|
// PatchCluster patches a cluster to include the custom Envoy configuration
|
|
|
|
// required to integrate with the built in extension template.
|
2023-05-26 18:10:31 +00:00
|
|
|
// See also PatchClusters.
|
2023-01-30 21:35:26 +00:00
|
|
|
PatchCluster(*RuntimeConfig, *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error)
|
2023-01-06 17:13:40 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
// PatchClusters patches clusters to include the custom Envoy configuration
|
|
|
|
// required to integrate with the built in extension template.
|
|
|
|
// This allows extensions to operate on a collection of clusters.
|
|
|
|
// For extensions that implement both PatchCluster and PatchClusters,
|
|
|
|
// PatchClusters is always called first with the entire collection of clusters.
|
|
|
|
// Then PatchClusters is called for each individual cluster.
|
|
|
|
PatchClusters(*RuntimeConfig, ClusterMap) (ClusterMap, error)
|
|
|
|
|
2023-01-30 21:35:26 +00:00
|
|
|
// PatchFilter patches an Envoy filter to include the custom Envoy
|
|
|
|
// configuration required to integrate with the built in extension template.
|
2023-05-26 18:10:31 +00:00
|
|
|
// See also PatchFilters.
|
2023-05-23 11:55:06 +00:00
|
|
|
PatchFilter(cfg *RuntimeConfig, f *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error)
|
2023-05-26 18:10:31 +00:00
|
|
|
|
|
|
|
// PatchFilters patches Envoy filters to include the custom Envoy
|
|
|
|
// configuration required to integrate with the built in extension template.
|
|
|
|
// This allows extensions to operate on a collection of filters.
|
|
|
|
// For extensions that implement both PatchFilter and PatchFilters,
|
|
|
|
// PatchFilters is always called first with the entire collection of filters.
|
|
|
|
// Then PatchFilter is called for each individual filter.
|
|
|
|
PatchFilters(cfg *RuntimeConfig, f []*envoy_listener_v3.Filter, isInboundListener bool) ([]*envoy_listener_v3.Filter, error)
|
|
|
|
|
|
|
|
// Validate determines if the runtime configuration provided is valid for the extension.
|
|
|
|
Validate(*RuntimeConfig) error
|
2023-01-30 21:35:26 +00:00
|
|
|
}
|
2023-01-06 17:13:40 +00:00
|
|
|
|
2023-01-30 21:35:26 +00:00
|
|
|
var _ EnvoyExtender = (*BasicEnvoyExtender)(nil)
|
2023-01-06 17:13:40 +00:00
|
|
|
|
2023-01-30 21:35:26 +00:00
|
|
|
// BasicEnvoyExtender provides convenience functions for iterating and applying modifications
|
|
|
|
// to Envoy resources.
|
|
|
|
type BasicEnvoyExtender struct {
|
|
|
|
Extension BasicExtension
|
2023-01-06 17:13:40 +00:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
func (b *BasicEnvoyExtender) Validate(config *RuntimeConfig) error {
|
|
|
|
return b.Extension.Validate(config)
|
2023-01-30 21:35:26 +00:00
|
|
|
}
|
2023-01-06 17:13:40 +00:00
|
|
|
|
2023-05-23 11:55:06 +00:00
|
|
|
func (b *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config *RuntimeConfig) (*xdscommon.IndexedResources, error) {
|
2022-03-31 20:24:46 +00:00
|
|
|
var resultErr error
|
|
|
|
|
2023-05-23 11:55:06 +00:00
|
|
|
// We don't support patching the local proxy with an upstream's config except in special
|
|
|
|
// cases supported by UpstreamEnvoyExtender.
|
|
|
|
if config.IsSourcedFromUpstream {
|
|
|
|
return nil, fmt.Errorf("%q extension applied as local config but is sourced from an upstream of the local service", config.EnvoyExtension.Name)
|
|
|
|
}
|
|
|
|
|
2022-05-05 20:39:39 +00:00
|
|
|
switch config.Kind {
|
2023-05-26 18:10:31 +00:00
|
|
|
// Currently we only support extensions for terminating gateways and connect proxies.
|
2022-05-05 20:39:39 +00:00
|
|
|
case api.ServiceKindTerminatingGateway, api.ServiceKindConnectProxy:
|
|
|
|
default:
|
2022-12-21 06:26:20 +00:00
|
|
|
return resources, nil
|
|
|
|
}
|
|
|
|
|
2023-05-23 11:55:06 +00:00
|
|
|
if !b.Extension.CanApply(config) {
|
2022-12-21 06:26:20 +00:00
|
|
|
return resources, nil
|
2022-03-31 20:24:46 +00:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
clusters := make(ClusterMap)
|
|
|
|
routes := make(RouteMap)
|
|
|
|
listeners := make(ListenerMap)
|
|
|
|
|
2022-03-31 20:24:46 +00:00
|
|
|
for _, indexType := range []string{
|
|
|
|
xdscommon.ListenerType,
|
2022-04-01 14:32:38 +00:00
|
|
|
xdscommon.RouteType,
|
2023-01-27 19:43:16 +00:00
|
|
|
xdscommon.ClusterType,
|
2022-03-31 20:24:46 +00:00
|
|
|
} {
|
|
|
|
for nameOrSNI, msg := range resources.Index[indexType] {
|
|
|
|
switch resource := msg.(type) {
|
|
|
|
case *envoy_cluster_v3.Cluster:
|
2023-05-26 18:10:31 +00:00
|
|
|
clusters[nameOrSNI] = resource
|
2022-03-31 20:24:46 +00:00
|
|
|
case *envoy_listener_v3.Listener:
|
2023-05-26 18:10:31 +00:00
|
|
|
listeners[nameOrSNI] = resource
|
2022-04-01 14:32:38 +00:00
|
|
|
case *envoy_route_v3.RouteConfiguration:
|
2023-05-26 18:10:31 +00:00
|
|
|
routes[nameOrSNI] = resource
|
2022-03-31 20:24:46 +00:00
|
|
|
default:
|
|
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("unsupported type was skipped: %T", resource))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
if patchedClusters, err := b.patchClusters(config, clusters); err == nil {
|
|
|
|
for k, v := range patchedClusters {
|
|
|
|
resources.Index[xdscommon.ClusterType][k] = v
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
resultErr = multierror.Append(resultErr, err)
|
|
|
|
}
|
2022-03-31 20:24:46 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
if patchedListeners, err := b.patchListeners(config, listeners); err == nil {
|
|
|
|
for k, v := range patchedListeners {
|
|
|
|
resources.Index[xdscommon.ListenerType][k] = v
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
resultErr = multierror.Append(resultErr, err)
|
2022-05-05 20:39:39 +00:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
if patchedRoutes, err := b.patchRoutes(config, routes); err == nil {
|
|
|
|
for k, v := range patchedRoutes {
|
|
|
|
resources.Index[xdscommon.RouteType][k] = v
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
resultErr = multierror.Append(resultErr, err)
|
|
|
|
}
|
2022-03-31 20:24:46 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
return resources, resultErr
|
|
|
|
}
|
2022-03-31 20:24:46 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
func (b *BasicEnvoyExtender) patchClusters(config *RuntimeConfig, clusters ClusterMap) (ClusterMap, error) {
|
|
|
|
var resultErr error
|
2022-03-31 20:24:46 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
patchedClusters, err := b.Extension.PatchClusters(config, clusters)
|
|
|
|
if err != nil {
|
|
|
|
return clusters, fmt.Errorf("error patching clusters: %w", err)
|
|
|
|
}
|
|
|
|
for nameOrSNI, cluster := range clusters {
|
|
|
|
patchedCluster, patched, err := b.Extension.PatchCluster(config, cluster)
|
|
|
|
if err != nil {
|
|
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster %q: %w", nameOrSNI, err))
|
|
|
|
}
|
|
|
|
if patched {
|
|
|
|
patchedClusters[nameOrSNI] = patchedCluster
|
|
|
|
} else {
|
|
|
|
patchedClusters[nameOrSNI] = cluster
|
2022-03-31 20:24:46 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-26 18:10:31 +00:00
|
|
|
return patchedClusters, resultErr
|
2022-03-31 20:24:46 +00:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
func (b *BasicEnvoyExtender) patchRoutes(config *RuntimeConfig, routes RouteMap) (RouteMap, error) {
|
2022-05-05 20:39:39 +00:00
|
|
|
var resultErr error
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
patchedRoutes, err := b.Extension.PatchRoutes(config, routes)
|
|
|
|
if err != nil {
|
|
|
|
return routes, fmt.Errorf("error patching routes: %w", err)
|
2023-01-27 19:43:16 +00:00
|
|
|
}
|
2023-05-26 18:10:31 +00:00
|
|
|
for nameOrSNI, route := range patchedRoutes {
|
|
|
|
patchedRoute, patched, err := b.Extension.PatchRoute(config, route)
|
|
|
|
if err != nil {
|
|
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching route %q: %w", nameOrSNI, err))
|
|
|
|
}
|
|
|
|
if patched {
|
|
|
|
patchedRoutes[nameOrSNI] = patchedRoute
|
|
|
|
} else {
|
|
|
|
patchedRoutes[nameOrSNI] = route
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return patchedRoutes, resultErr
|
|
|
|
}
|
2023-01-27 19:43:16 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
func (b *BasicEnvoyExtender) patchListeners(config *RuntimeConfig, m ListenerMap) (ListenerMap, error) {
|
|
|
|
switch config.Kind {
|
|
|
|
case api.ServiceKindTerminatingGateway:
|
|
|
|
return b.patchTerminatingGatewayListeners(config, m)
|
|
|
|
case api.ServiceKindConnectProxy:
|
|
|
|
return b.patchConnectProxyListeners(config, m)
|
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
2023-01-27 19:43:16 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
func (b *BasicEnvoyExtender) patchTerminatingGatewayListeners(config *RuntimeConfig, l ListenerMap) (ListenerMap, error) {
|
|
|
|
var resultErr error
|
|
|
|
for _, listener := range l {
|
|
|
|
for idx, filterChain := range listener.FilterChains {
|
|
|
|
if patchedFilterChain, err := b.patchFilterChain(config, filterChain, IsInboundPublicListener(listener)); err == nil {
|
|
|
|
listener.FilterChains[idx] = patchedFilterChain
|
2023-01-31 16:49:45 +00:00
|
|
|
} else {
|
2023-05-26 18:10:31 +00:00
|
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching tgw filter chain: %w", err))
|
2023-01-27 19:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
return l, resultErr
|
2023-01-27 19:43:16 +00:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
func (b *BasicEnvoyExtender) patchConnectProxyListeners(config *RuntimeConfig, l ListenerMap) (ListenerMap, error) {
|
2023-01-27 19:43:16 +00:00
|
|
|
var resultErr error
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
for nameOrSNI, listener := range l {
|
|
|
|
if IsOutboundTProxyListener(listener) {
|
|
|
|
patchedListener, err := b.patchTProxyListener(config, listener)
|
|
|
|
if err == nil {
|
|
|
|
l[nameOrSNI] = patchedListener
|
|
|
|
} else {
|
|
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching TProxy listener %q: %w", nameOrSNI, err))
|
2022-05-05 20:39:39 +00:00
|
|
|
}
|
2023-05-26 18:10:31 +00:00
|
|
|
} else {
|
2022-05-05 20:39:39 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
patchedListener, err := b.patchConnectProxyListener(config, listener)
|
|
|
|
if err == nil {
|
|
|
|
l[nameOrSNI] = patchedListener
|
2023-01-31 16:49:45 +00:00
|
|
|
} else {
|
2023-05-26 18:10:31 +00:00
|
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching connect proxy listener %q: %w", nameOrSNI, err))
|
2022-05-05 20:39:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
return l, resultErr
|
2022-05-05 20:39:39 +00:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
func (b *BasicEnvoyExtender) patchConnectProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, error) {
|
|
|
|
var resultErr error
|
2023-01-27 19:43:16 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
inbound := IsInboundPublicListener(l)
|
2023-01-27 19:43:16 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
for idx, filterChain := range l.FilterChains {
|
|
|
|
if patchedFilterChain, err := b.patchFilterChain(config, filterChain, inbound); err == nil {
|
|
|
|
l.FilterChains[idx] = patchedFilterChain
|
|
|
|
} else {
|
|
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter chain: %w", err))
|
2023-01-27 19:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-26 18:10:31 +00:00
|
|
|
return l, resultErr
|
2023-01-27 19:43:16 +00:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
func (b *BasicEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, error) {
|
|
|
|
var resultErr error
|
2023-01-27 19:43:16 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
vip := config.Upstreams[config.ServiceName].VIP
|
|
|
|
inbound := IsInboundPublicListener(l)
|
2023-01-27 19:43:16 +00:00
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
for idx, filterChain := range l.FilterChains {
|
|
|
|
match := filterChainTProxyMatch(vip, filterChain)
|
|
|
|
if !match {
|
|
|
|
continue
|
2023-01-27 19:43:16 +00:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
if patchedFilterChain, err := b.patchFilterChain(config, filterChain, inbound); err == nil {
|
|
|
|
l.FilterChains[idx] = patchedFilterChain
|
|
|
|
} else {
|
|
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter chain for %q: %w", vip, err))
|
2023-01-27 19:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
return l, resultErr
|
2023-01-27 19:43:16 +00:00
|
|
|
}
|
|
|
|
|
2023-05-26 18:10:31 +00:00
|
|
|
func (b *BasicEnvoyExtender) patchFilterChain(config *RuntimeConfig, filterChain *envoy_listener_v3.FilterChain, isInboundListener bool) (*envoy_listener_v3.FilterChain, error) {
|
|
|
|
var resultErr error
|
|
|
|
patchedFilters, err := b.Extension.PatchFilters(config, filterChain.Filters, isInboundListener)
|
|
|
|
if err != nil {
|
|
|
|
return filterChain, fmt.Errorf("error patching filters: %w", err)
|
2022-03-31 20:24:46 +00:00
|
|
|
}
|
2023-05-26 18:10:31 +00:00
|
|
|
for idx, filter := range patchedFilters {
|
|
|
|
patchedFilter, patched, err := b.Extension.PatchFilter(config, filter, isInboundListener)
|
|
|
|
if err != nil {
|
|
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter: %w", err))
|
|
|
|
}
|
|
|
|
if patched {
|
|
|
|
patchedFilters[idx] = patchedFilter
|
|
|
|
} else {
|
|
|
|
patchedFilters[idx] = filter
|
|
|
|
}
|
2022-03-31 20:24:46 +00:00
|
|
|
}
|
2023-05-26 18:10:31 +00:00
|
|
|
filterChain.Filters = patchedFilters
|
|
|
|
return filterChain, err
|
2022-03-15 14:07:40 +00:00
|
|
|
}
|