mirror of https://github.com/status-im/consul.git
ENT->OSS merge for Consolidate `ListEnvoyExtender` into `BasicEnvoyExtender` (#17491)
This commit is contained in:
parent
3605fde865
commit
2740d12d44
|
@ -28,6 +28,8 @@ import (
|
||||||
var _ extensioncommon.BasicExtension = (*awsLambda)(nil)
|
var _ extensioncommon.BasicExtension = (*awsLambda)(nil)
|
||||||
|
|
||||||
type awsLambda struct {
|
type awsLambda struct {
|
||||||
|
extensioncommon.BasicExtensionAdapter
|
||||||
|
|
||||||
ARN string
|
ARN string
|
||||||
PayloadPassthrough bool
|
PayloadPassthrough bool
|
||||||
InvocationMode string
|
InvocationMode string
|
||||||
|
|
|
@ -8,10 +8,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
|
||||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
|
||||||
envoy_ratelimit "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3"
|
envoy_ratelimit "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3"
|
||||||
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||||
|
@ -26,6 +24,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ratelimit struct {
|
type ratelimit struct {
|
||||||
|
extensioncommon.BasicExtensionAdapter
|
||||||
|
|
||||||
ProxyType string
|
ProxyType string
|
||||||
|
|
||||||
// Token bucket of the rate limit
|
// Token bucket of the rate limit
|
||||||
|
@ -100,16 +100,6 @@ func (p *ratelimit) CanApply(config *extensioncommon.RuntimeConfig) bool {
|
||||||
return string(config.Kind) == p.ProxyType
|
return string(config.Kind) == p.ProxyType
|
||||||
}
|
}
|
||||||
|
|
||||||
// PatchRoute does nothing.
|
|
||||||
func (p ratelimit) PatchRoute(_ *extensioncommon.RuntimeConfig, route *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) {
|
|
||||||
return route, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PatchCluster does nothing.
|
|
||||||
func (p ratelimit) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) {
|
|
||||||
return c, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PatchFilter inserts a http local rate_limit filter at the head of
|
// PatchFilter inserts a http local rate_limit filter at the head of
|
||||||
// envoy.filters.network.http_connection_manager filters
|
// envoy.filters.network.http_connection_manager filters
|
||||||
func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
||||||
|
|
|
@ -7,9 +7,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
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"
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
|
||||||
envoy_lua_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/lua/v3"
|
envoy_lua_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/lua/v3"
|
||||||
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
||||||
|
@ -22,6 +20,8 @@ import (
|
||||||
var _ extensioncommon.BasicExtension = (*lua)(nil)
|
var _ extensioncommon.BasicExtension = (*lua)(nil)
|
||||||
|
|
||||||
type lua struct {
|
type lua struct {
|
||||||
|
extensioncommon.BasicExtensionAdapter
|
||||||
|
|
||||||
ProxyType string
|
ProxyType string
|
||||||
Listener string
|
Listener string
|
||||||
Script string
|
Script string
|
||||||
|
@ -71,16 +71,6 @@ func (l *lua) matchesListenerDirection(isInboundListener bool) bool {
|
||||||
return (!isInboundListener && l.Listener == "outbound") || (isInboundListener && l.Listener == "inbound")
|
return (!isInboundListener && l.Listener == "outbound") || (isInboundListener && l.Listener == "inbound")
|
||||||
}
|
}
|
||||||
|
|
||||||
// PatchRoute does nothing.
|
|
||||||
func (l *lua) PatchRoute(_ *extensioncommon.RuntimeConfig, route *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) {
|
|
||||||
return route, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PatchCluster does nothing.
|
|
||||||
func (l *lua) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) {
|
|
||||||
return c, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PatchFilter inserts a lua filter directly prior to envoy.filters.http.router.
|
// PatchFilter inserts a lua filter directly prior to envoy.filters.http.router.
|
||||||
func (l *lua) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
func (l *lua) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
||||||
// Make sure filter matches extension config.
|
// Make sure filter matches extension config.
|
||||||
|
|
|
@ -7,9 +7,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
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"
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
|
||||||
envoy_http_wasm_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/wasm/v3"
|
envoy_http_wasm_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/wasm/v3"
|
||||||
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
||||||
|
@ -20,6 +18,8 @@ import (
|
||||||
|
|
||||||
// wasm is a built-in Envoy extension that can patch filter chains to insert Wasm plugins.
|
// wasm is a built-in Envoy extension that can patch filter chains to insert Wasm plugins.
|
||||||
type wasm struct {
|
type wasm struct {
|
||||||
|
extensioncommon.BasicExtensionAdapter
|
||||||
|
|
||||||
name string
|
name string
|
||||||
wasmConfig *wasmConfig
|
wasmConfig *wasmConfig
|
||||||
}
|
}
|
||||||
|
@ -77,16 +77,6 @@ func (w wasm) matchesConfigDirection(isInboundListener bool) bool {
|
||||||
return isInboundListener && w.wasmConfig.ListenerType == "inbound"
|
return isInboundListener && w.wasmConfig.ListenerType == "inbound"
|
||||||
}
|
}
|
||||||
|
|
||||||
// PatchRoute does nothing for the WASM extension.
|
|
||||||
func (w wasm) PatchRoute(_ *extensioncommon.RuntimeConfig, r *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) {
|
|
||||||
return r, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PatchCluster does nothing for the WASM extension.
|
|
||||||
func (w wasm) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) {
|
|
||||||
return c, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PatchFilter adds a Wasm filter to the HTTP filter chain.
|
// PatchFilter adds a Wasm filter to the HTTP filter chain.
|
||||||
// TODO (wasm/tcp): Add support for TCP filters.
|
// TODO (wasm/tcp): Add support for TCP filters.
|
||||||
func (w wasm) PatchFilter(cfg *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
func (w wasm) PatchFilter(cfg *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error) {
|
||||||
|
|
|
@ -5,37 +5,73 @@ package extensioncommon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
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"
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||||
envoy_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
|
|
||||||
envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
"google.golang.org/protobuf/types/known/anypb"
|
|
||||||
|
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
// BasicExtension is the interface that each user of BasicEnvoyExtender must implement. It
|
// 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
|
// is responsible for modifying the xDS structures based on only the state of
|
||||||
// the extension.
|
// the extension.
|
||||||
type BasicExtension interface {
|
type BasicExtension interface {
|
||||||
// CanApply determines if the extension can mutate resources for the given xdscommon.ExtensionConfiguration.
|
// CanApply determines if the extension can mutate resources for the given runtime configuration.
|
||||||
CanApply(*RuntimeConfig) bool
|
CanApply(*RuntimeConfig) bool
|
||||||
|
|
||||||
// PatchRoute patches a route to include the custom Envoy configuration
|
// PatchRoute patches a route to include the custom Envoy configuration
|
||||||
// required to integrate with the built in extension template.
|
// required to integrate with the built in extension template.
|
||||||
|
// See also PatchRoutes.
|
||||||
PatchRoute(*RuntimeConfig, *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error)
|
PatchRoute(*RuntimeConfig, *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
// PatchCluster patches a cluster to include the custom Envoy configuration
|
// PatchCluster patches a cluster to include the custom Envoy configuration
|
||||||
// required to integrate with the built in extension template.
|
// required to integrate with the built in extension template.
|
||||||
|
// See also PatchClusters.
|
||||||
PatchCluster(*RuntimeConfig, *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error)
|
PatchCluster(*RuntimeConfig, *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
// PatchFilter patches an Envoy filter to include the custom Envoy
|
// PatchFilter patches an Envoy filter to include the custom Envoy
|
||||||
// configuration required to integrate with the built in extension template.
|
// configuration required to integrate with the built in extension template.
|
||||||
|
// See also PatchFilters.
|
||||||
PatchFilter(cfg *RuntimeConfig, f *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error)
|
PatchFilter(cfg *RuntimeConfig, f *envoy_listener_v3.Filter, isInboundListener bool) (*envoy_listener_v3.Filter, bool, error)
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ EnvoyExtender = (*BasicEnvoyExtender)(nil)
|
var _ EnvoyExtender = (*BasicEnvoyExtender)(nil)
|
||||||
|
@ -46,8 +82,8 @@ type BasicEnvoyExtender struct {
|
||||||
Extension BasicExtension
|
Extension BasicExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BasicEnvoyExtender) Validate(_ *RuntimeConfig) error {
|
func (b *BasicEnvoyExtender) Validate(config *RuntimeConfig) error {
|
||||||
return nil
|
return b.Extension.Validate(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config *RuntimeConfig) (*xdscommon.IndexedResources, error) {
|
func (b *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config *RuntimeConfig) (*xdscommon.IndexedResources, error) {
|
||||||
|
@ -60,6 +96,7 @@ func (b *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedResources, confi
|
||||||
}
|
}
|
||||||
|
|
||||||
switch config.Kind {
|
switch config.Kind {
|
||||||
|
// Currently we only support extensions for terminating gateways and connect proxies.
|
||||||
case api.ServiceKindTerminatingGateway, api.ServiceKindConnectProxy:
|
case api.ServiceKindTerminatingGateway, api.ServiceKindConnectProxy:
|
||||||
default:
|
default:
|
||||||
return resources, nil
|
return resources, nil
|
||||||
|
@ -69,6 +106,10 @@ func (b *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedResources, confi
|
||||||
return resources, nil
|
return resources, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clusters := make(ClusterMap)
|
||||||
|
routes := make(RouteMap)
|
||||||
|
listeners := make(ListenerMap)
|
||||||
|
|
||||||
for _, indexType := range []string{
|
for _, indexType := range []string{
|
||||||
xdscommon.ListenerType,
|
xdscommon.ListenerType,
|
||||||
xdscommon.RouteType,
|
xdscommon.RouteType,
|
||||||
|
@ -77,239 +118,190 @@ func (b *BasicEnvoyExtender) Extend(resources *xdscommon.IndexedResources, confi
|
||||||
for nameOrSNI, msg := range resources.Index[indexType] {
|
for nameOrSNI, msg := range resources.Index[indexType] {
|
||||||
switch resource := msg.(type) {
|
switch resource := msg.(type) {
|
||||||
case *envoy_cluster_v3.Cluster:
|
case *envoy_cluster_v3.Cluster:
|
||||||
newCluster, patched, err := b.Extension.PatchCluster(config, resource)
|
clusters[nameOrSNI] = resource
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching cluster: %w", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if patched {
|
|
||||||
resources.Index[xdscommon.ClusterType][nameOrSNI] = newCluster
|
|
||||||
}
|
|
||||||
|
|
||||||
case *envoy_listener_v3.Listener:
|
case *envoy_listener_v3.Listener:
|
||||||
newListener, patched, err := b.patchListener(config, resource)
|
listeners[nameOrSNI] = resource
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener: %w", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if patched {
|
|
||||||
resources.Index[xdscommon.ListenerType][nameOrSNI] = newListener
|
|
||||||
}
|
|
||||||
|
|
||||||
case *envoy_route_v3.RouteConfiguration:
|
case *envoy_route_v3.RouteConfiguration:
|
||||||
newRoute, patched, err := b.Extension.PatchRoute(config, resource)
|
routes[nameOrSNI] = resource
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching route: %w", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if patched {
|
|
||||||
resources.Index[xdscommon.RouteType][nameOrSNI] = newRoute
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("unsupported type was skipped: %T", resource))
|
resultErr = multierror.Append(resultErr, fmt.Errorf("unsupported type was skipped: %T", resource))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
return resources, resultErr
|
return resources, resultErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BasicEnvoyExtender) patchListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
func (b *BasicEnvoyExtender) patchClusters(config *RuntimeConfig, clusters ClusterMap) (ClusterMap, error) {
|
||||||
|
var resultErr error
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return patchedClusters, resultErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasicEnvoyExtender) patchRoutes(config *RuntimeConfig, routes RouteMap) (RouteMap, error) {
|
||||||
|
var resultErr error
|
||||||
|
|
||||||
|
patchedRoutes, err := b.Extension.PatchRoutes(config, routes)
|
||||||
|
if err != nil {
|
||||||
|
return routes, fmt.Errorf("error patching routes: %w", err)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasicEnvoyExtender) patchListeners(config *RuntimeConfig, m ListenerMap) (ListenerMap, error) {
|
||||||
switch config.Kind {
|
switch config.Kind {
|
||||||
case api.ServiceKindTerminatingGateway:
|
case api.ServiceKindTerminatingGateway:
|
||||||
return b.patchTerminatingGatewayListener(config, l)
|
return b.patchTerminatingGatewayListeners(config, m)
|
||||||
case api.ServiceKindConnectProxy:
|
case api.ServiceKindConnectProxy:
|
||||||
return b.patchConnectProxyListener(config, l)
|
return b.patchConnectProxyListeners(config, m)
|
||||||
}
|
}
|
||||||
return l, false, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BasicEnvoyExtender) patchTerminatingGatewayListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
func (b *BasicEnvoyExtender) patchTerminatingGatewayListeners(config *RuntimeConfig, l ListenerMap) (ListenerMap, error) {
|
||||||
var resultErr error
|
var resultErr error
|
||||||
patched := false
|
for _, listener := range l {
|
||||||
|
for idx, filterChain := range listener.FilterChains {
|
||||||
for _, filterChain := range l.FilterChains {
|
if patchedFilterChain, err := b.patchFilterChain(config, filterChain, IsInboundPublicListener(listener)); err == nil {
|
||||||
var filters []*envoy_listener_v3.Filter
|
listener.FilterChains[idx] = patchedFilterChain
|
||||||
|
|
||||||
for _, filter := range filterChain.Filters {
|
|
||||||
newFilter, ok, err := b.Extension.PatchFilter(config, filter, IsInboundPublicListener(l))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
|
|
||||||
filters = append(filters, filter)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
filters = append(filters, newFilter)
|
|
||||||
patched = true
|
|
||||||
} else {
|
} else {
|
||||||
filters = append(filters, filter)
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching tgw filter chain: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filterChain.Filters = filters
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return l, patched, resultErr
|
return l, resultErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BasicEnvoyExtender) patchConnectProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
func (b *BasicEnvoyExtender) patchConnectProxyListeners(config *RuntimeConfig, l ListenerMap) (ListenerMap, error) {
|
||||||
var resultErr error
|
var resultErr error
|
||||||
patched := false
|
|
||||||
|
|
||||||
if IsOutboundTProxyListener(l) {
|
for nameOrSNI, listener := range l {
|
||||||
return b.patchTProxyListener(config, l)
|
if IsOutboundTProxyListener(listener) {
|
||||||
}
|
patchedListener, err := b.patchTProxyListener(config, listener)
|
||||||
|
if err == nil {
|
||||||
for _, filterChain := range l.FilterChains {
|
l[nameOrSNI] = patchedListener
|
||||||
var filters []*envoy_listener_v3.Filter
|
|
||||||
|
|
||||||
for _, filter := range filterChain.Filters {
|
|
||||||
newFilter, ok, err := b.Extension.PatchFilter(config, filter, IsInboundPublicListener(l))
|
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
|
|
||||||
filters = append(filters, filter)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
filters = append(filters, newFilter)
|
|
||||||
patched = true
|
|
||||||
} else {
|
} else {
|
||||||
filters = append(filters, filter)
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching TProxy listener %q: %w", nameOrSNI, err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
patchedListener, err := b.patchConnectProxyListener(config, listener)
|
||||||
|
if err == nil {
|
||||||
|
l[nameOrSNI] = patchedListener
|
||||||
|
} else {
|
||||||
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching connect proxy listener %q: %w", nameOrSNI, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filterChain.Filters = filters
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return l, patched, resultErr
|
return l, resultErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BasicEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (proto.Message, bool, error) {
|
func (b *BasicEnvoyExtender) patchConnectProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, error) {
|
||||||
|
var resultErr error
|
||||||
|
|
||||||
|
inbound := IsInboundPublicListener(l)
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l, resultErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BasicEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, error) {
|
||||||
var resultErr error
|
var resultErr error
|
||||||
patched := false
|
|
||||||
|
|
||||||
vip := config.Upstreams[config.ServiceName].VIP
|
vip := config.Upstreams[config.ServiceName].VIP
|
||||||
|
inbound := IsInboundPublicListener(l)
|
||||||
|
|
||||||
for _, filterChain := range l.FilterChains {
|
for idx, filterChain := range l.FilterChains {
|
||||||
var filters []*envoy_listener_v3.Filter
|
|
||||||
|
|
||||||
match := filterChainTProxyMatch(vip, filterChain)
|
match := filterChainTProxyMatch(vip, filterChain)
|
||||||
if !match {
|
if !match {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, filter := range filterChain.Filters {
|
if patchedFilterChain, err := b.patchFilterChain(config, filterChain, inbound); err == nil {
|
||||||
newFilter, ok, err := b.Extension.PatchFilter(config, filter, IsInboundPublicListener(l))
|
l.FilterChains[idx] = patchedFilterChain
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filter: %w", err))
|
|
||||||
filters = append(filters, filter)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
filters = append(filters, newFilter)
|
|
||||||
patched = true
|
|
||||||
} else {
|
} else {
|
||||||
filters = append(filters, filter)
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter chain for %q: %w", vip, err))
|
||||||
}
|
|
||||||
}
|
|
||||||
filterChain.Filters = filters
|
|
||||||
}
|
|
||||||
|
|
||||||
return l, patched, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterChainTProxyMatch(vip string, filterChain *envoy_listener_v3.FilterChain) bool {
|
|
||||||
for _, prefixRange := range filterChain.FilterChainMatch.PrefixRanges {
|
|
||||||
// Since we always set the address prefix as the full VIP (rather than a prefix), we can just check if they are
|
|
||||||
// equal to find the matching filter chain.
|
|
||||||
if vip == prefixRange.AddressPrefix {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return l, resultErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func FilterClusterNames(filter *envoy_listener_v3.Filter) map[string]struct{} {
|
func (b *BasicEnvoyExtender) patchFilterChain(config *RuntimeConfig, filterChain *envoy_listener_v3.FilterChain, isInboundListener bool) (*envoy_listener_v3.FilterChain, error) {
|
||||||
clusterNames := make(map[string]struct{})
|
var resultErr error
|
||||||
if filter == nil {
|
patchedFilters, err := b.Extension.PatchFilters(config, filterChain.Filters, isInboundListener)
|
||||||
return clusterNames
|
if err != nil {
|
||||||
|
return filterChain, fmt.Errorf("error patching filters: %w", err)
|
||||||
}
|
}
|
||||||
|
for idx, filter := range patchedFilters {
|
||||||
if config := envoy_resource_v3.GetHTTPConnectionManager(filter); config != nil {
|
patchedFilter, patched, err := b.Extension.PatchFilter(config, filter, isInboundListener)
|
||||||
// If it's using RDS, the cluster names will be in the route, rather than in the http filter's route config, so
|
if err != nil {
|
||||||
// we don't return any cluster names in this case. They can be gathered from the route.
|
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filter: %w", err))
|
||||||
if config.GetRds() != nil {
|
|
||||||
return clusterNames
|
|
||||||
}
|
}
|
||||||
|
if patched {
|
||||||
cfg := config.GetRouteConfig()
|
patchedFilters[idx] = patchedFilter
|
||||||
|
} else {
|
||||||
clusterNames = RouteClusterNames(cfg)
|
patchedFilters[idx] = filter
|
||||||
}
|
|
||||||
|
|
||||||
if config := GetTCPProxy(filter); config != nil {
|
|
||||||
clusterNames[config.GetCluster()] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return clusterNames
|
|
||||||
}
|
|
||||||
|
|
||||||
func RouteClusterNames(route *envoy_route_v3.RouteConfiguration) map[string]struct{} {
|
|
||||||
if route == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
clusterNames := make(map[string]struct{})
|
|
||||||
|
|
||||||
for _, virtualHost := range route.VirtualHosts {
|
|
||||||
for _, route := range virtualHost.Routes {
|
|
||||||
r := route.GetRoute()
|
|
||||||
if r == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c := r.GetCluster(); c != "" {
|
|
||||||
clusterNames[r.GetCluster()] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if wc := r.GetWeightedClusters(); wc != nil {
|
|
||||||
for _, c := range wc.GetClusters() {
|
|
||||||
if c.Name != "" {
|
|
||||||
clusterNames[c.Name] = struct{}{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
filterChain.Filters = patchedFilters
|
||||||
}
|
return filterChain, err
|
||||||
}
|
|
||||||
return clusterNames
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTCPProxy(filter *envoy_listener_v3.Filter) *envoy_tcp_proxy_v3.TcpProxy {
|
|
||||||
if typedConfig := filter.GetTypedConfig(); typedConfig != nil {
|
|
||||||
config := &envoy_tcp_proxy_v3.TcpProxy{}
|
|
||||||
if err := anypb.UnmarshalTo(typedConfig, config, proto.UnmarshalOptions{}); err == nil {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSNI(chain *envoy_listener_v3.FilterChain) string {
|
|
||||||
var sni string
|
|
||||||
|
|
||||||
if chain == nil {
|
|
||||||
return sni
|
|
||||||
}
|
|
||||||
|
|
||||||
if chain.FilterChainMatch == nil {
|
|
||||||
return sni
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(chain.FilterChainMatch.ServerNames) == 0 {
|
|
||||||
return sni
|
|
||||||
}
|
|
||||||
|
|
||||||
return chain.FilterChainMatch.ServerNames[0]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package extensioncommon
|
||||||
|
|
||||||
|
import (
|
||||||
|
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"
|
||||||
|
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BasicExtensionAdapter is an adapter that provides default implementations for all of the EnvoyExtension
|
||||||
|
// interface functions. Extension implementations can extend the adapter and implement only the functions
|
||||||
|
// they are interested in. At a minimum, extensions must override the adapter's CanApply and Validate
|
||||||
|
// functions.
|
||||||
|
type BasicExtensionAdapter struct{}
|
||||||
|
|
||||||
|
// CanApply provides a default implementation of the CanApply interface that always returns false.
|
||||||
|
func (BasicExtensionAdapter) CanApply(_ *RuntimeConfig) bool { return false }
|
||||||
|
|
||||||
|
// PatchCluster provides a default implementation of the PatchCluster interface that does nothing.
|
||||||
|
func (BasicExtensionAdapter) PatchCluster(_ *RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) {
|
||||||
|
return c, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchClusters provides a default implementation of the PatchClusters interface that does nothing.
|
||||||
|
func (BasicExtensionAdapter) PatchClusters(_ *RuntimeConfig, c ClusterMap) (ClusterMap, error) {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchFilter provides a default implementation of the PatchFilter interface that does nothing.
|
||||||
|
func (BasicExtensionAdapter) PatchFilter(_ *RuntimeConfig, f *envoy_listener_v3.Filter, _ bool) (*envoy_listener_v3.Filter, bool, error) {
|
||||||
|
return f, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchFilters provides a default implementation of the PatchFilters interface that does nothing.
|
||||||
|
func (BasicExtensionAdapter) PatchFilters(_ *RuntimeConfig, f []*envoy_listener_v3.Filter, _ bool) ([]*envoy_listener_v3.Filter, error) {
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchRoute provides a default implementation of the PatchRoute interface that does nothing.
|
||||||
|
func (BasicExtensionAdapter) PatchRoute(_ *RuntimeConfig, r *envoy_route_v3.RouteConfiguration) (*envoy_route_v3.RouteConfiguration, bool, error) {
|
||||||
|
return r, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchRoutes provides a default implementation of the PatchRoutes interface that does nothing.
|
||||||
|
func (BasicExtensionAdapter) PatchRoutes(_ *RuntimeConfig, r RouteMap) (RouteMap, error) {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate provides a default implementation of the Validate interface that always returns nil.
|
||||||
|
func (BasicExtensionAdapter) Validate(_ *RuntimeConfig) error { return nil }
|
|
@ -44,20 +44,6 @@ func TestUpstreamConfigSourceLimitations(t *testing.T) {
|
||||||
ok: false,
|
ok: false,
|
||||||
errMsg: fmt.Sprintf("%q extension applied as local config but is sourced from an upstream of the local service", api.BuiltinLuaExtension),
|
errMsg: fmt.Sprintf("%q extension applied as local config but is sourced from an upstream of the local service", api.BuiltinLuaExtension),
|
||||||
},
|
},
|
||||||
"list extender upstream config": {
|
|
||||||
extender: &ListEnvoyExtender{},
|
|
||||||
config: &RuntimeConfig{
|
|
||||||
Kind: api.ServiceKindConnectProxy,
|
|
||||||
ServiceName: api.CompoundServiceName{Name: "api"},
|
|
||||||
Upstreams: map[api.CompoundServiceName]*UpstreamData{},
|
|
||||||
IsSourcedFromUpstream: true,
|
|
||||||
EnvoyExtension: api.EnvoyExtension{
|
|
||||||
Name: api.BuiltinLuaExtension,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ok: false,
|
|
||||||
errMsg: fmt.Sprintf("%q extension applied as local config but is sourced from an upstream of the local service", api.BuiltinLuaExtension),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, tc := range cases {
|
for n, tc := range cases {
|
||||||
|
|
|
@ -1,219 +0,0 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package extensioncommon
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
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"
|
|
||||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
|
||||||
|
|
||||||
"github.com/hashicorp/consul/api"
|
|
||||||
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClusterMap map[string]*envoy_cluster_v3.Cluster
|
|
||||||
type ListenerMap map[string]*envoy_listener_v3.Listener
|
|
||||||
type RouteMap map[string]*envoy_route_v3.RouteConfiguration
|
|
||||||
|
|
||||||
// ListExtension is the interface that each user of ListEnvoyExtender must implement. It
|
|
||||||
// is responsible for modifying the xDS structures based on only the state of the extension.
|
|
||||||
type ListExtension interface {
|
|
||||||
// CanApply determines if the extension can mutate resources for the given runtime configuration.
|
|
||||||
CanApply(*RuntimeConfig) bool
|
|
||||||
|
|
||||||
// PatchRoutes patches routes to include the custom Envoy configuration
|
|
||||||
// required to integrate with the built in extension template.
|
|
||||||
PatchRoutes(*RuntimeConfig, RouteMap) (RouteMap, error)
|
|
||||||
|
|
||||||
// PatchClusters patches clusters to include the custom Envoy configuration
|
|
||||||
// required to integrate with the built in extension template.
|
|
||||||
PatchClusters(*RuntimeConfig, ClusterMap) (ClusterMap, error)
|
|
||||||
|
|
||||||
// PatchFilters patches Envoy filters to include the custom Envoy
|
|
||||||
// configuration required to integrate with the built in extension template.
|
|
||||||
PatchFilters(*RuntimeConfig, []*envoy_listener_v3.Filter) ([]*envoy_listener_v3.Filter, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ EnvoyExtender = (*ListEnvoyExtender)(nil)
|
|
||||||
|
|
||||||
// ListEnvoyExtender provides convenience functions for iterating and applying modifications
|
|
||||||
// to lists of Envoy resources.
|
|
||||||
type ListEnvoyExtender struct {
|
|
||||||
Extension ListExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ListEnvoyExtender) Validate(config *RuntimeConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ListEnvoyExtender) Extend(resources *xdscommon.IndexedResources, config *RuntimeConfig) (*xdscommon.IndexedResources, error) {
|
|
||||||
var resultErr error
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch config.Kind {
|
|
||||||
case api.ServiceKindTerminatingGateway, api.ServiceKindConnectProxy:
|
|
||||||
default:
|
|
||||||
return resources, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !e.Extension.CanApply(config) {
|
|
||||||
return resources, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
clusters := make(ClusterMap)
|
|
||||||
routes := make(RouteMap)
|
|
||||||
listeners := make(ListenerMap)
|
|
||||||
|
|
||||||
for _, indexType := range []string{
|
|
||||||
xdscommon.ListenerType,
|
|
||||||
xdscommon.RouteType,
|
|
||||||
xdscommon.ClusterType,
|
|
||||||
} {
|
|
||||||
for nameOrSNI, msg := range resources.Index[indexType] {
|
|
||||||
switch resource := msg.(type) {
|
|
||||||
case *envoy_cluster_v3.Cluster:
|
|
||||||
clusters[nameOrSNI] = resource
|
|
||||||
|
|
||||||
case *envoy_listener_v3.Listener:
|
|
||||||
listeners[nameOrSNI] = resource
|
|
||||||
|
|
||||||
case *envoy_route_v3.RouteConfiguration:
|
|
||||||
routes[nameOrSNI] = resource
|
|
||||||
|
|
||||||
default:
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("unsupported type was skipped: %T", resource))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedClusters, err := e.Extension.PatchClusters(config, clusters)
|
|
||||||
if err == nil {
|
|
||||||
for nameOrSNI, cluster := range patchedClusters {
|
|
||||||
resources.Index[xdscommon.ClusterType][nameOrSNI] = cluster
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching clusters: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedListeners, err := e.patchListeners(config, listeners)
|
|
||||||
if err == nil {
|
|
||||||
for nameOrSNI, listener := range patchedListeners {
|
|
||||||
resources.Index[xdscommon.ListenerType][nameOrSNI] = listener
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listeners: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedRoutes, err := e.Extension.PatchRoutes(config, routes)
|
|
||||||
if err == nil {
|
|
||||||
for nameOrSNI, route := range patchedRoutes {
|
|
||||||
resources.Index[xdscommon.RouteType][nameOrSNI] = route
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching routes: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return resources, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ListEnvoyExtender) patchListeners(config *RuntimeConfig, m ListenerMap) (ListenerMap, error) {
|
|
||||||
switch config.Kind {
|
|
||||||
case api.ServiceKindTerminatingGateway:
|
|
||||||
return e.patchTerminatingGatewayListeners(config, m)
|
|
||||||
case api.ServiceKindConnectProxy:
|
|
||||||
return e.patchConnectProxyListeners(config, m)
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ListEnvoyExtender) patchTerminatingGatewayListeners(config *RuntimeConfig, l ListenerMap) (ListenerMap, error) {
|
|
||||||
var resultErr error
|
|
||||||
for _, listener := range l {
|
|
||||||
for _, filterChain := range listener.FilterChains {
|
|
||||||
sni := getSNI(filterChain)
|
|
||||||
|
|
||||||
if sni == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedFilters, err := e.Extension.PatchFilters(config, filterChain.Filters)
|
|
||||||
if err == nil {
|
|
||||||
filterChain.Filters = patchedFilters
|
|
||||||
} else {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching listener filters for %q: %w", sni, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return l, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ListEnvoyExtender) patchConnectProxyListeners(config *RuntimeConfig, l ListenerMap) (ListenerMap, error) {
|
|
||||||
var resultErr error
|
|
||||||
|
|
||||||
for nameOrSNI, listener := range l {
|
|
||||||
if IsOutboundTProxyListener(listener) {
|
|
||||||
patchedListener, err := e.patchTProxyListener(config, listener)
|
|
||||||
if err == nil {
|
|
||||||
l[nameOrSNI] = patchedListener
|
|
||||||
} else {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching TProxy listener %q: %w", nameOrSNI, err))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedListener, err := e.patchConnectProxyListener(config, listener)
|
|
||||||
if err == nil {
|
|
||||||
l[nameOrSNI] = patchedListener
|
|
||||||
} else {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching connect proxy listener %q: %w", nameOrSNI, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return l, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ListEnvoyExtender) patchConnectProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, error) {
|
|
||||||
var resultErr error
|
|
||||||
|
|
||||||
for _, filterChain := range l.FilterChains {
|
|
||||||
patchedFilters, err := e.Extension.PatchFilters(config, filterChain.Filters)
|
|
||||||
if err == nil {
|
|
||||||
filterChain.Filters = patchedFilters
|
|
||||||
} else {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filters: %w", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return l, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ListEnvoyExtender) patchTProxyListener(config *RuntimeConfig, l *envoy_listener_v3.Listener) (*envoy_listener_v3.Listener, error) {
|
|
||||||
var resultErr error
|
|
||||||
|
|
||||||
vip := config.Upstreams[config.ServiceName].VIP
|
|
||||||
|
|
||||||
for _, filterChain := range l.FilterChains {
|
|
||||||
match := filterChainTProxyMatch(vip, filterChain)
|
|
||||||
if !match {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedFilters, err := e.Extension.PatchFilters(config, filterChain.Filters)
|
|
||||||
if err == nil {
|
|
||||||
filterChain.Filters = patchedFilters
|
|
||||||
} else {
|
|
||||||
resultErr = multierror.Append(resultErr, fmt.Errorf("error patching filters: %w", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return l, resultErr
|
|
||||||
}
|
|
|
@ -4,16 +4,21 @@
|
||||||
package extensioncommon
|
package extensioncommon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||||
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
|
envoy_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
|
||||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||||
|
envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
||||||
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/anypb"
|
"google.golang.org/protobuf/types/known/anypb"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MakeUpstreamTLSTransportSocket generates an Envoy transport socket for the given TLS context.
|
// MakeUpstreamTLSTransportSocket generates an Envoy transport socket for the given TLS context.
|
||||||
|
@ -100,3 +105,254 @@ func IsInboundPublicListener(l *envoy_listener_v3.Listener) bool {
|
||||||
func IsOutboundTProxyListener(l *envoy_listener_v3.Listener) bool {
|
func IsOutboundTProxyListener(l *envoy_listener_v3.Listener) bool {
|
||||||
return GetListenerEnvoyID(l) == xdscommon.OutboundListenerName
|
return GetListenerEnvoyID(l) == xdscommon.OutboundListenerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterChainTProxyMatch(vip string, filterChain *envoy_listener_v3.FilterChain) bool {
|
||||||
|
for _, prefixRange := range filterChain.FilterChainMatch.PrefixRanges {
|
||||||
|
// Since we always set the address prefix as the full VIP (rather than a prefix), we can just check if they are
|
||||||
|
// equal to find the matching filter chain.
|
||||||
|
if vip == prefixRange.AddressPrefix {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterClusterNames(filter *envoy_listener_v3.Filter) map[string]struct{} {
|
||||||
|
clusterNames := make(map[string]struct{})
|
||||||
|
if filter == nil {
|
||||||
|
return clusterNames
|
||||||
|
}
|
||||||
|
|
||||||
|
if config := envoy_resource_v3.GetHTTPConnectionManager(filter); config != nil {
|
||||||
|
// If it's using RDS, the cluster names will be in the route, rather than in the http filter's route config, so
|
||||||
|
// we don't return any cluster names in this case. They can be gathered from the route.
|
||||||
|
if config.GetRds() != nil {
|
||||||
|
return clusterNames
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.GetRouteConfig()
|
||||||
|
|
||||||
|
clusterNames = RouteClusterNames(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config := GetTCPProxy(filter); config != nil {
|
||||||
|
clusterNames[config.GetCluster()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusterNames
|
||||||
|
}
|
||||||
|
|
||||||
|
func RouteClusterNames(route *envoy_route_v3.RouteConfiguration) map[string]struct{} {
|
||||||
|
if route == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterNames := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, virtualHost := range route.VirtualHosts {
|
||||||
|
for _, route := range virtualHost.Routes {
|
||||||
|
r := route.GetRoute()
|
||||||
|
if r == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c := r.GetCluster(); c != "" {
|
||||||
|
clusterNames[r.GetCluster()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if wc := r.GetWeightedClusters(); wc != nil {
|
||||||
|
for _, c := range wc.GetClusters() {
|
||||||
|
if c.Name != "" {
|
||||||
|
clusterNames[c.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clusterNames
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTCPProxy(filter *envoy_listener_v3.Filter) *envoy_tcp_proxy_v3.TcpProxy {
|
||||||
|
if typedConfig := filter.GetTypedConfig(); typedConfig != nil {
|
||||||
|
config := &envoy_tcp_proxy_v3.TcpProxy{}
|
||||||
|
if err := anypb.UnmarshalTo(typedConfig, config, proto.UnmarshalOptions{}); err == nil {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSNI(chain *envoy_listener_v3.FilterChain) string {
|
||||||
|
var sni string
|
||||||
|
|
||||||
|
if chain == nil {
|
||||||
|
return sni
|
||||||
|
}
|
||||||
|
|
||||||
|
if chain.FilterChainMatch == nil {
|
||||||
|
return sni
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(chain.FilterChainMatch.ServerNames) == 0 {
|
||||||
|
return sni
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain.FilterChainMatch.ServerNames[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHTTPConnectionManager returns the Envoy HttpConnectionManager filter from the list of network filters.
|
||||||
|
// It also returns the index within the list of filters where the connection manager was found in case the caller
|
||||||
|
// needs this information.
|
||||||
|
// It returns a non-nil error if the HttpConnectionManager is not found.
|
||||||
|
func GetHTTPConnectionManager(filters ...*envoy_listener_v3.Filter) (*envoy_http_v3.HttpConnectionManager, int, error) {
|
||||||
|
for idx, filter := range filters {
|
||||||
|
if filter.Name == "envoy.filters.network.http_connection_manager" {
|
||||||
|
if httpConnMgr := envoy_resource_v3.GetHTTPConnectionManager(filter); httpConnMgr != nil {
|
||||||
|
return httpConnMgr, idx, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, 0, errors.New("failed to get HTTP connection manager")
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertLocation indicates where to insert an Envoy resource within a list of resources.
|
||||||
|
type InsertLocation string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// InsertFirst inserts the resource as the first entry in the list.
|
||||||
|
InsertFirst InsertLocation = "First"
|
||||||
|
// InsertLast inserts the resource as the last entry in the list.
|
||||||
|
InsertLast InsertLocation = "Last"
|
||||||
|
// InsertBeforeFirstMatch inserts the resource before the first resource with a matching name.
|
||||||
|
InsertBeforeFirstMatch InsertLocation = "BeforeFirstMatch"
|
||||||
|
// InsertAfterFirstMatch inserts the resource after the first resource with a matching name.
|
||||||
|
InsertAfterFirstMatch InsertLocation = "AfterFirstMatch"
|
||||||
|
// InsertBeforeLastMatch inserts the resource before the last resource with a matching name.
|
||||||
|
InsertBeforeLastMatch InsertLocation = "BeforeLastMatch"
|
||||||
|
// InsertAfterLastMatch inserts the resource after the last resource with a matching name.
|
||||||
|
InsertAfterLastMatch InsertLocation = "AfterLastMatch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InsertOptions controls how and where to insert Envoy resources.
|
||||||
|
type InsertOptions struct {
|
||||||
|
// Location defines where to insert the resource within the list.
|
||||||
|
Location InsertLocation
|
||||||
|
// FilterName indicates the name of the resource to insert relative to.
|
||||||
|
FilterName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertHTTPFilter inserts the given HTTP filter into the HttpConnectionManager's filter chain in the location
|
||||||
|
// determined by the insert options. This list of filters must include the HttpConnectionManager network
|
||||||
|
// filter or the operation will fail.
|
||||||
|
//
|
||||||
|
// It returns the modified list of filters including the updated HttpConnectionManager.
|
||||||
|
// If a matching location is not found to insert the filter, a non-nil error is returned.
|
||||||
|
func InsertHTTPFilter(filters []*envoy_listener_v3.Filter, filter *envoy_http_v3.HttpFilter, opts InsertOptions) ([]*envoy_listener_v3.Filter, error) {
|
||||||
|
httpConnMgr, idx, err := GetHTTPConnectionManager(filters...)
|
||||||
|
if err != nil {
|
||||||
|
return filters, err
|
||||||
|
}
|
||||||
|
|
||||||
|
namedFilters := make([]namedFilter, 0, len(httpConnMgr.HttpFilters)+1)
|
||||||
|
for _, f := range httpConnMgr.HttpFilters {
|
||||||
|
namedFilters = append(namedFilters, f)
|
||||||
|
}
|
||||||
|
insertIdx, err := locateInsertIndex(opts, namedFilters)
|
||||||
|
if err != nil {
|
||||||
|
return filters, fmt.Errorf("failed to insert %q filter: %w", filter.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currIdx := 0
|
||||||
|
newHttpFilters := make([]*envoy_http_v3.HttpFilter, len(httpConnMgr.HttpFilters)+1)
|
||||||
|
for idx, httpFilter := range httpConnMgr.HttpFilters {
|
||||||
|
if idx == insertIdx {
|
||||||
|
newHttpFilters[currIdx] = filter
|
||||||
|
currIdx++
|
||||||
|
}
|
||||||
|
newHttpFilters[currIdx] = httpFilter
|
||||||
|
currIdx++
|
||||||
|
}
|
||||||
|
if currIdx == insertIdx {
|
||||||
|
newHttpFilters[currIdx] = filter
|
||||||
|
}
|
||||||
|
|
||||||
|
httpConnMgr.HttpFilters = newHttpFilters
|
||||||
|
newHttpConMan, err := MakeFilter("envoy.filters.network.http_connection_manager", httpConnMgr)
|
||||||
|
if err != nil {
|
||||||
|
return filters, errors.New("failed to insert new HTTP connection manager filter")
|
||||||
|
}
|
||||||
|
filters[idx] = newHttpConMan
|
||||||
|
|
||||||
|
return filters, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertNetworkFilter inserts the given network filter into the filter chain in the location
|
||||||
|
// determined by the insert options.
|
||||||
|
//
|
||||||
|
// It returns the modified list of filters including the new filter.
|
||||||
|
// If a matching location is not found to insert the filter, a non-nil error is returned.
|
||||||
|
func InsertNetworkFilter(filters []*envoy_listener_v3.Filter, filter *envoy_listener_v3.Filter, opts InsertOptions) ([]*envoy_listener_v3.Filter, error) {
|
||||||
|
namedFilters := make([]namedFilter, 0, len(filters)+1)
|
||||||
|
for _, f := range filters {
|
||||||
|
namedFilters = append(namedFilters, f)
|
||||||
|
}
|
||||||
|
insertIdx, err := locateInsertIndex(opts, namedFilters)
|
||||||
|
if err != nil {
|
||||||
|
return filters, fmt.Errorf("failed to insert %q filter: %w", filter.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currIdx := 0
|
||||||
|
newFilters := make([]*envoy_listener_v3.Filter, len(filters)+1)
|
||||||
|
for idx, f := range filters {
|
||||||
|
if idx == insertIdx {
|
||||||
|
newFilters[currIdx] = filter
|
||||||
|
currIdx++
|
||||||
|
}
|
||||||
|
newFilters[currIdx] = f
|
||||||
|
currIdx++
|
||||||
|
}
|
||||||
|
if currIdx == insertIdx {
|
||||||
|
newFilters[currIdx] = filter
|
||||||
|
}
|
||||||
|
|
||||||
|
return newFilters, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// namedFilter is a convenience interface for locating Envoy filters based on name.
|
||||||
|
type namedFilter interface {
|
||||||
|
GetName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// locateInsertIndex returns the index where a filter should be inserted based on the given
|
||||||
|
// insert options.
|
||||||
|
func locateInsertIndex(opts InsertOptions, filters []namedFilter) (int, error) {
|
||||||
|
idx := 0
|
||||||
|
if opts.Location == InsertFirst {
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
if opts.Location == InsertLast {
|
||||||
|
return len(filters), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
matched := false
|
||||||
|
for currIdx, filter := range filters {
|
||||||
|
if filter.GetName() == opts.FilterName {
|
||||||
|
matched = true
|
||||||
|
switch opts.Location {
|
||||||
|
case InsertBeforeFirstMatch:
|
||||||
|
return currIdx, nil
|
||||||
|
case InsertAfterFirstMatch:
|
||||||
|
return currIdx + 1, nil
|
||||||
|
case InsertBeforeLastMatch:
|
||||||
|
idx = currIdx
|
||||||
|
case InsertAfterLastMatch:
|
||||||
|
idx = currIdx + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
return idx, fmt.Errorf("failed to find insert location %q for %q", opts.Location, opts.FilterName)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package extensioncommon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInsertHTTPFilter(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
inputFilters []*envoy_http_v3.HttpFilter
|
||||||
|
insertOptions InsertOptions
|
||||||
|
filterName string
|
||||||
|
expectedFilters []*envoy_http_v3.HttpFilter
|
||||||
|
errStr string
|
||||||
|
}{
|
||||||
|
"insert first": {
|
||||||
|
inputFilters: makeHttpFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertFirst},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeHttpFilters(t, "test.filter", "a", "b", "b", "b", "c"),
|
||||||
|
},
|
||||||
|
"insert last": {
|
||||||
|
inputFilters: makeHttpFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertLast},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeHttpFilters(t, "a", "b", "b", "b", "c", "test.filter"),
|
||||||
|
},
|
||||||
|
"insert before first match": {
|
||||||
|
inputFilters: makeHttpFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertBeforeFirstMatch, FilterName: "b"},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeHttpFilters(t, "a", "test.filter", "b", "b", "b", "c"),
|
||||||
|
},
|
||||||
|
"insert after first match": {
|
||||||
|
inputFilters: makeHttpFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertAfterFirstMatch, FilterName: "b"},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeHttpFilters(t, "a", "b", "test.filter", "b", "b", "c"),
|
||||||
|
},
|
||||||
|
"insert before last match": {
|
||||||
|
inputFilters: makeHttpFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertBeforeLastMatch, FilterName: "b"},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeHttpFilters(t, "a", "b", "b", "test.filter", "b", "c"),
|
||||||
|
},
|
||||||
|
"insert after last match": {
|
||||||
|
inputFilters: makeHttpFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertAfterLastMatch, FilterName: "b"},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeHttpFilters(t, "a", "b", "b", "b", "test.filter", "c"),
|
||||||
|
},
|
||||||
|
"insert last after last match": {
|
||||||
|
inputFilters: makeHttpFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertAfterLastMatch, FilterName: "c"},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeHttpFilters(t, "a", "b", "b", "b", "c", "test.filter"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
for name, c := range cases {
|
||||||
|
c := c
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
filters := []*envoy_listener_v3.Filter{makeHttpConMgr(t, c.inputFilters)}
|
||||||
|
newFilter := &envoy_http_v3.HttpFilter{Name: c.filterName}
|
||||||
|
obsFilters, err := InsertHTTPFilter(filters, newFilter, c.insertOptions)
|
||||||
|
if c.errStr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), c.errStr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
httpConMgr, idx, err := GetHTTPConnectionManager(obsFilters...)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, httpConMgr)
|
||||||
|
require.Equal(t, 0, idx)
|
||||||
|
require.ElementsMatch(t, c.expectedFilters, httpConMgr.HttpFilters)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertFilter(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
inputFilters []*envoy_listener_v3.Filter
|
||||||
|
filterName string
|
||||||
|
insertOptions InsertOptions
|
||||||
|
expectedFilters []*envoy_listener_v3.Filter
|
||||||
|
errStr string
|
||||||
|
}{
|
||||||
|
"insert first": {
|
||||||
|
inputFilters: makeFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertFirst},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeFilters(t, "test.filter", "a", "b", "b", "b", "c"),
|
||||||
|
},
|
||||||
|
"insert last": {
|
||||||
|
inputFilters: makeFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertLast},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeFilters(t, "a", "b", "b", "b", "c", "test.filter"),
|
||||||
|
},
|
||||||
|
"insert before first match": {
|
||||||
|
inputFilters: makeFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertBeforeFirstMatch, FilterName: "b"},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeFilters(t, "a", "test.filter", "b", "b", "b", "c"),
|
||||||
|
},
|
||||||
|
"insert after first match": {
|
||||||
|
inputFilters: makeFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertAfterFirstMatch, FilterName: "b"},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeFilters(t, "a", "b", "test.filter", "b", "b", "c"),
|
||||||
|
},
|
||||||
|
"insert before last match": {
|
||||||
|
inputFilters: makeFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertBeforeLastMatch, FilterName: "b"},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeFilters(t, "a", "b", "b", "test.filter", "b", "c"),
|
||||||
|
},
|
||||||
|
"insert after last match": {
|
||||||
|
inputFilters: makeFilters(t, "a", "b", "b", "b", "c"),
|
||||||
|
insertOptions: InsertOptions{Location: InsertAfterLastMatch, FilterName: "b"},
|
||||||
|
filterName: "test.filter",
|
||||||
|
expectedFilters: makeFilters(t, "a", "b", "b", "b", "test.filter", "c"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
for name, c := range cases {
|
||||||
|
c := c
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
filter := &envoy_listener_v3.Filter{Name: c.filterName}
|
||||||
|
obsFilters, err := InsertNetworkFilter(c.inputFilters, filter, c.insertOptions)
|
||||||
|
if c.errStr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), c.errStr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.ElementsMatch(t, c.expectedFilters, obsFilters)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHttpConMgr(t *testing.T, filters []*envoy_http_v3.HttpFilter) *envoy_listener_v3.Filter {
|
||||||
|
t.Helper()
|
||||||
|
httpConMgr := &envoy_http_v3.HttpConnectionManager{HttpFilters: filters}
|
||||||
|
filter, err := MakeFilter("envoy.filters.network.http_connection_manager", httpConMgr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHttpFilters(t *testing.T, names ...string) []*envoy_http_v3.HttpFilter {
|
||||||
|
var filters []*envoy_http_v3.HttpFilter
|
||||||
|
for _, name := range names {
|
||||||
|
filters = append(filters, &envoy_http_v3.HttpFilter{Name: name})
|
||||||
|
}
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFilters(t *testing.T, names ...string) []*envoy_listener_v3.Filter {
|
||||||
|
var filters []*envoy_listener_v3.Filter
|
||||||
|
for _, name := range names {
|
||||||
|
filters = append(filters, &envoy_listener_v3.Filter{Name: name})
|
||||||
|
}
|
||||||
|
return filters
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ import (
|
||||||
// Validate contains input information about which proxy resources to validate and output information about resources it
|
// Validate contains input information about which proxy resources to validate and output information about resources it
|
||||||
// has validated.
|
// has validated.
|
||||||
type Validate struct {
|
type Validate struct {
|
||||||
|
extensioncommon.BasicExtensionAdapter
|
||||||
|
|
||||||
// envoyID is an argument to the Validate plugin and identifies which listener to begin the validation with.
|
// envoyID is an argument to the Validate plugin and identifies which listener to begin the validation with.
|
||||||
envoyID string
|
envoyID string
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue