mirror of https://github.com/status-im/consul.git
Wasm Envoy HTTP extension (#16877)
This commit is contained in:
parent
1794484298
commit
175bb1a303
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
xds: Add a built-in Envoy extension that inserts Wasm HTTP filters.
|
||||
```
|
|
@ -95,7 +95,7 @@ func (a *awsLambda) PatchRoute(r *extensioncommon.RuntimeConfig, route *envoy_ro
|
|||
|
||||
// PatchCluster patches the provided envoy cluster with data required to support an AWS lambda function
|
||||
func (a *awsLambda) PatchCluster(_ *extensioncommon.RuntimeConfig, c *envoy_cluster_v3.Cluster) (*envoy_cluster_v3.Cluster, bool, error) {
|
||||
transportSocket, err := makeUpstreamTLSTransportSocket(&envoy_tls_v3.UpstreamTlsContext{
|
||||
transportSocket, err := extensioncommon.MakeUpstreamTLSTransportSocket(&envoy_tls_v3.UpstreamTlsContext{
|
||||
Sni: "*.amazonaws.com",
|
||||
})
|
||||
|
||||
|
@ -169,7 +169,7 @@ func (a *awsLambda) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_
|
|||
return filter, false, errors.New("error unmarshalling filter")
|
||||
}
|
||||
|
||||
lambdaHttpFilter, err := makeEnvoyHTTPFilter(
|
||||
lambdaHttpFilter, err := extensioncommon.MakeEnvoyHTTPFilter(
|
||||
"envoy.filters.http.aws_lambda",
|
||||
&envoy_lambda_v3.Config{
|
||||
Arn: a.ARN,
|
||||
|
@ -204,7 +204,7 @@ func (a *awsLambda) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_
|
|||
config.StripPortMode = &envoy_http_v3.HttpConnectionManager_StripAnyHostPort{
|
||||
StripAnyHostPort: true,
|
||||
}
|
||||
newFilter, err := makeFilter("envoy.filters.network.http_connection_manager", config)
|
||||
newFilter, err := extensioncommon.MakeFilter("envoy.filters.network.http_connection_manager", config)
|
||||
if err != nil {
|
||||
return filter, false, errors.New("error making new filter")
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ func TestPatchCluster(t *testing.T) {
|
|||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
transportSocket, err := makeUpstreamTLSTransportSocket(&envoy_tls_v3.UpstreamTlsContext{
|
||||
transportSocket, err := extensioncommon.MakeUpstreamTLSTransportSocket(&envoy_tls_v3.UpstreamTlsContext{
|
||||
Sni: "*.amazonaws.com",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package localratelimit
|
||||
|
||||
import (
|
||||
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_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
)
|
||||
|
||||
// This is copied from xds and not put into the shared package because I'm not
|
||||
// convinced it should be shared.
|
||||
|
||||
func makeUpstreamTLSTransportSocket(tlsContext *envoy_tls_v3.UpstreamTlsContext) (*envoy_core_v3.TransportSocket, error) {
|
||||
if tlsContext == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return makeTransportSocket("tls", tlsContext)
|
||||
}
|
||||
|
||||
func makeTransportSocket(name string, config proto.Message) (*envoy_core_v3.TransportSocket, error) {
|
||||
any, err := anypb.New(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &envoy_core_v3.TransportSocket{
|
||||
Name: name,
|
||||
ConfigType: &envoy_core_v3.TransportSocket_TypedConfig{
|
||||
TypedConfig: any,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFilter, error) {
|
||||
any, err := anypb.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &envoy_http_v3.HttpFilter{
|
||||
Name: name,
|
||||
ConfigType: &envoy_http_v3.HttpFilter_TypedConfig{TypedConfig: any},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, error) {
|
||||
any, err := anypb.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &envoy_listener_v3.Filter{
|
||||
Name: name,
|
||||
ConfigType: &envoy_listener_v3.Filter_TypedConfig{TypedConfig: any},
|
||||
}, nil
|
||||
}
|
|
@ -162,7 +162,7 @@ func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_l
|
|||
}
|
||||
}
|
||||
|
||||
ratelimitHttpFilter, err := makeEnvoyHTTPFilter(
|
||||
ratelimitHttpFilter, err := extensioncommon.MakeEnvoyHTTPFilter(
|
||||
"envoy.filters.http.local_ratelimit",
|
||||
&envoy_ratelimit.LocalRateLimit{
|
||||
TokenBucket: &tokenBucket,
|
||||
|
@ -184,7 +184,7 @@ func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_l
|
|||
changedFilters = append(changedFilters, config.HttpFilters...)
|
||||
config.HttpFilters = changedFilters
|
||||
|
||||
newFilter, err := makeFilter("envoy.filters.network.http_connection_manager", config)
|
||||
newFilter, err := extensioncommon.MakeFilter("envoy.filters.network.http_connection_manager", config)
|
||||
if err != nil {
|
||||
return filter, false, errors.New("error making new filter")
|
||||
}
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package lua
|
||||
|
||||
import (
|
||||
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_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
)
|
||||
|
||||
// This is copied from xds and not put into the shared package because I'm not
|
||||
// convinced it should be shared.
|
||||
|
||||
func makeUpstreamTLSTransportSocket(tlsContext *envoy_tls_v3.UpstreamTlsContext) (*envoy_core_v3.TransportSocket, error) {
|
||||
if tlsContext == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return makeTransportSocket("tls", tlsContext)
|
||||
}
|
||||
|
||||
func makeTransportSocket(name string, config proto.Message) (*envoy_core_v3.TransportSocket, error) {
|
||||
any, err := anypb.New(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &envoy_core_v3.TransportSocket{
|
||||
Name: name,
|
||||
ConfigType: &envoy_core_v3.TransportSocket_TypedConfig{
|
||||
TypedConfig: any,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFilter, error) {
|
||||
any, err := anypb.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &envoy_http_v3.HttpFilter{
|
||||
Name: name,
|
||||
ConfigType: &envoy_http_v3.HttpFilter_TypedConfig{TypedConfig: any},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, error) {
|
||||
any, err := anypb.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &envoy_listener_v3.Filter{
|
||||
Name: name,
|
||||
ConfigType: &envoy_listener_v3.Filter_TypedConfig{TypedConfig: any},
|
||||
}, nil
|
||||
}
|
|
@ -94,7 +94,7 @@ func (l *lua) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listen
|
|||
if config == nil {
|
||||
return filter, false, errors.New("error unmarshalling filter")
|
||||
}
|
||||
luaHttpFilter, err := makeEnvoyHTTPFilter(
|
||||
luaHttpFilter, err := extensioncommon.MakeEnvoyHTTPFilter(
|
||||
"envoy.filters.http.lua",
|
||||
&envoy_lua_v3.Lua{
|
||||
InlineCode: l.Script,
|
||||
|
@ -124,7 +124,7 @@ func (l *lua) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listen
|
|||
config.HttpFilters = changedFilters
|
||||
}
|
||||
|
||||
newFilter, err := makeFilter("envoy.filters.network.http_connection_manager", config)
|
||||
newFilter, err := extensioncommon.MakeFilter("envoy.filters.network.http_connection_manager", config)
|
||||
if err != nil {
|
||||
return filter, false, errors.New("error making new filter")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,435 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
envoy_wasm_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/wasm/v3"
|
||||
"github.com/hashicorp/consul/acl"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
// wasmConfig defines the configuration for a Wasm Envoy extension.
|
||||
type wasmConfig struct {
|
||||
// Protocol is the type of Wasm filter to apply, "tcp" or "http".
|
||||
Protocol string
|
||||
// ProxyType identifies the type of Envoy proxy that this extension applies to.
|
||||
// The extension will only be configured for proxies that match this type and
|
||||
// will be ignored for all other proxy types.
|
||||
ProxyType api.ServiceKind
|
||||
// ListenerType identifies the listener type the filter will be applied to.
|
||||
ListenerType string
|
||||
// PluginConfig holds the configuration for the Wasm plugin.
|
||||
PluginConfig pluginConfig
|
||||
}
|
||||
|
||||
// pluginConfig defines a Wasm plugin configuration.
|
||||
type pluginConfig struct {
|
||||
// Name is the unique name for the filter in a VM. For use in identifying the
|
||||
// filter if multiple filters are handled by the same VmID and RootID.
|
||||
// Also used for logging/debugging.
|
||||
Name string
|
||||
// RootID is a unique ID for a set of filters in a VM which will share a
|
||||
// RootContext and Contexts if applicable (e.g. a Wasm HttpFilter and a Wasm AccessLog).
|
||||
// All filters with the same RootID and VmID will share Context(s).
|
||||
RootID string
|
||||
// VmConfig is the configuration for starting or finding the Wasm VM that the
|
||||
// filter will run in.
|
||||
VmConfig vmConfig
|
||||
// Configuration holds the configuration that will be encoded as bytes and passed to
|
||||
// the plugin on startup (proxy_on_configure).
|
||||
Configuration string
|
||||
// CapabilityRestrictionConfiguration controls the Wasm ABI capabilities available
|
||||
// to the filter.
|
||||
CapabilityRestrictionConfiguration capabilityRestrictionConfiguration
|
||||
|
||||
// failOpen controls the behavior when a runtime error occurs during filter
|
||||
// processing.
|
||||
//
|
||||
// If set to false runtime errors will result in a failed request.
|
||||
// For TCP filters, the connection will be closed. For HTTP filters a 503
|
||||
// status is returned.
|
||||
//
|
||||
// If set to true, a runtime error will result in the filter being bypassed.
|
||||
failOpen bool
|
||||
}
|
||||
|
||||
// vmConfig defines a Wasm VM configuration.
|
||||
type vmConfig struct {
|
||||
// VmID is an ID which will be used along with a hash of the Wasm code to
|
||||
// determine which VM will be used for the plugin. All plugins which use
|
||||
// the same VmID and code will use the same VM. May be left blank.
|
||||
VmID string
|
||||
// Runtime is the Wasm runtime type, one of: v8, wasmtime, wamr, or wavm.
|
||||
Runtime string
|
||||
// Code references the Wasm code that will run in the filter.
|
||||
Code dataSource
|
||||
// Configuration holds the configuration that will be encoded as bytes and
|
||||
// passed to the plugin during VM startup (proxy_on_vm_start).
|
||||
Configuration string
|
||||
// EnvironmentVariables specifies environment variables to be injected to
|
||||
// this VM which will be available through WASI’s environ_get and
|
||||
// environ_get_sizes system calls.
|
||||
EnvironmentVariables environmentVariables
|
||||
}
|
||||
|
||||
// dataSource defines a local or remote location where Wasm code will be loaded from.
|
||||
type dataSource struct {
|
||||
// Local supports loading files from a local volume.
|
||||
Local localDataSource
|
||||
// Remote supports loading files from a remote server.
|
||||
Remote remoteDataSource
|
||||
}
|
||||
|
||||
// environmentVariables defines the environment variables that will be made available
|
||||
// to the Wasm filter.
|
||||
type environmentVariables struct {
|
||||
// HostEnvKeys holds the keys of Envoy’s environment variables exposed to this VM.
|
||||
// If a key exists in Envoy’s environment variables, then that key-value pair will
|
||||
// be injected into the Wasm VM. If a key does not exist, it will be ignored.
|
||||
HostEnvKeys []string
|
||||
// KeyValues is a list of key-value pairs to be injected to this VM in the form of "KEY=VALUE".
|
||||
KeyValues map[string]string
|
||||
}
|
||||
|
||||
// localDataSource defines a file from a local file system.
|
||||
type localDataSource struct {
|
||||
// Filename is the path to the Wasm file on the local file system.
|
||||
Filename string
|
||||
}
|
||||
|
||||
// remoteDataSource defines a file from a remote file server.
|
||||
type remoteDataSource struct {
|
||||
// HttpURI
|
||||
HttpURI httpURI
|
||||
// SHA256 of the remote file. Used to validate the remote file.
|
||||
SHA256 string
|
||||
// RetryPolicy determines how retries are handled.
|
||||
RetryPolicy retryPolicy
|
||||
}
|
||||
|
||||
// httpURI defines a remote file using an HTTP URI.
|
||||
type httpURI struct {
|
||||
// Service is the upstream service the Wasm plugin will be fetched from.
|
||||
Service api.CompoundServiceName
|
||||
// URI is the location of the Wasm file on the upstream service.
|
||||
URI string
|
||||
// Timeout sets the maximum duration that a response can take.
|
||||
Timeout string
|
||||
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// retryPolicy defines how to handle retries when fetching remote files.
|
||||
type retryPolicy struct {
|
||||
// RetryBackOff holds parameters that control retry backoff strategy.
|
||||
RetryBackOff retryBackoff
|
||||
// NumRetries specifies the allowed number of retries.
|
||||
NumRetries int
|
||||
}
|
||||
|
||||
// retryBackoff holds parameters that control retry backoff strategy.
|
||||
type retryBackoff struct {
|
||||
// BaseInterval is the base interval to be used for the next back off
|
||||
// computation. It should be greater than zero and less than or equal
|
||||
// to MaxInterval.
|
||||
BaseInterval string
|
||||
// MaxInterval is the maximum interval between retries.
|
||||
MaxInterval string
|
||||
|
||||
baseInterval time.Duration
|
||||
maxInterval time.Duration
|
||||
}
|
||||
|
||||
// capabilityRestrictionConfiguration controls Wasm capabilities available to modules.
|
||||
type capabilityRestrictionConfiguration struct {
|
||||
// AllowedCapabilities specifies the Wasm capabilities which will be allowed.
|
||||
// Capabilities are mapped by name. The value which each capability maps to is
|
||||
// currently ignored and should be left empty.
|
||||
AllowedCapabilities map[string]any
|
||||
}
|
||||
|
||||
// newWasmConfig creates a filterConfig from the given args.
|
||||
// It starts with the default wasm configuration and merges in the config
|
||||
// from the given args.
|
||||
func newWasmConfig(args map[string]any) (*wasmConfig, error) {
|
||||
cfg := &wasmConfig{}
|
||||
if err := mapstructure.Decode(args, cfg); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg.normalize()
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (p *pluginConfig) asyncDataSource(rtCfg *extensioncommon.RuntimeConfig) (*envoy_core_v3.AsyncDataSource, error) {
|
||||
|
||||
// Local data source
|
||||
if filename := p.VmConfig.Code.Local.Filename; filename != "" {
|
||||
return &envoy_core_v3.AsyncDataSource{
|
||||
Specifier: &envoy_core_v3.AsyncDataSource_Local{
|
||||
Local: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_Filename{
|
||||
Filename: filename,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Remote data source
|
||||
// For a remote file, ensure there is an upstream cluster for the host specified in the URL.
|
||||
// Envoy requires an explicit cluster in order to perform the DNS lookup required to actually
|
||||
// fetch the data from the upstream source.
|
||||
remote := &p.VmConfig.Code.Remote
|
||||
clusterSNI := ""
|
||||
for service, upstream := range rtCfg.LocalUpstreams {
|
||||
if service == remote.HttpURI.Service {
|
||||
for sni := range upstream.SNI {
|
||||
clusterSNI = sni
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if clusterSNI == "" {
|
||||
return nil, fmt.Errorf("no upstream found for remote service %q", remote.HttpURI.Service.Name)
|
||||
}
|
||||
|
||||
d := time.Second
|
||||
if remote.HttpURI.timeout > 0 {
|
||||
d = remote.HttpURI.timeout
|
||||
}
|
||||
timeout := &durationpb.Duration{Seconds: int64(d.Seconds())}
|
||||
|
||||
return &envoy_core_v3.AsyncDataSource{
|
||||
Specifier: &envoy_core_v3.AsyncDataSource_Remote{
|
||||
Remote: &envoy_core_v3.RemoteDataSource{
|
||||
Sha256: remote.SHA256,
|
||||
HttpUri: &envoy_core_v3.HttpUri{
|
||||
Uri: remote.HttpURI.URI,
|
||||
HttpUpstreamType: &envoy_core_v3.HttpUri_Cluster{
|
||||
Cluster: clusterSNI,
|
||||
},
|
||||
Timeout: timeout,
|
||||
},
|
||||
RetryPolicy: p.retryPolicy(),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *pluginConfig) capConfig() *envoy_wasm_v3.CapabilityRestrictionConfig {
|
||||
if len(p.CapabilityRestrictionConfiguration.AllowedCapabilities) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
allowedCaps := make(map[string]*envoy_wasm_v3.SanitizationConfig)
|
||||
for key := range p.CapabilityRestrictionConfiguration.AllowedCapabilities {
|
||||
allowedCaps[key] = &envoy_wasm_v3.SanitizationConfig{}
|
||||
}
|
||||
|
||||
return &envoy_wasm_v3.CapabilityRestrictionConfig{
|
||||
AllowedCapabilities: allowedCaps,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pluginConfig) envoyPluginConfig(rtCfg *extensioncommon.RuntimeConfig) (*envoy_wasm_v3.PluginConfig, error) {
|
||||
var err error
|
||||
var pluginCfgData, vmCfgData *anypb.Any
|
||||
|
||||
if p.Configuration != "" {
|
||||
pluginCfgData, err = anypb.New(wrapperspb.String(p.Configuration))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode Wasm plugin configuration: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.VmConfig.Configuration != "" {
|
||||
vmCfgData, err = anypb.New(wrapperspb.String(p.VmConfig.Configuration))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode Wasm VM configuration: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
code, err := p.asyncDataSource(rtCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode async data source configuration: %w", err)
|
||||
}
|
||||
|
||||
var envVars *envoy_wasm_v3.EnvironmentVariables
|
||||
if len(p.VmConfig.EnvironmentVariables.HostEnvKeys) > 0 ||
|
||||
len(p.VmConfig.EnvironmentVariables.KeyValues) > 0 {
|
||||
envVars = &envoy_wasm_v3.EnvironmentVariables{
|
||||
HostEnvKeys: p.VmConfig.EnvironmentVariables.HostEnvKeys,
|
||||
KeyValues: p.VmConfig.EnvironmentVariables.KeyValues,
|
||||
}
|
||||
}
|
||||
|
||||
return &envoy_wasm_v3.PluginConfig{
|
||||
Name: p.Name,
|
||||
RootId: p.RootID,
|
||||
Vm: &envoy_wasm_v3.PluginConfig_VmConfig{
|
||||
VmConfig: &envoy_wasm_v3.VmConfig{
|
||||
VmId: p.VmConfig.VmID,
|
||||
Runtime: fmt.Sprintf("envoy.wasm.runtime.%s", p.VmConfig.Runtime),
|
||||
Code: code,
|
||||
Configuration: vmCfgData,
|
||||
EnvironmentVariables: envVars,
|
||||
},
|
||||
},
|
||||
Configuration: pluginCfgData,
|
||||
CapabilityRestrictionConfig: p.capConfig(),
|
||||
FailOpen: p.failOpen,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *pluginConfig) retryPolicy() *envoy_core_v3.RetryPolicy {
|
||||
remote := &p.VmConfig.Code.Remote
|
||||
if remote.RetryPolicy.NumRetries <= 0 &&
|
||||
remote.RetryPolicy.RetryBackOff.BaseInterval == "" &&
|
||||
remote.RetryPolicy.RetryBackOff.MaxInterval == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
retryPolicy := &envoy_core_v3.RetryPolicy{}
|
||||
|
||||
if remote.RetryPolicy.NumRetries > 0 {
|
||||
retryPolicy.NumRetries = wrapperspb.UInt32(uint32(remote.RetryPolicy.NumRetries))
|
||||
}
|
||||
|
||||
var baseInterval, maxInterval *durationpb.Duration
|
||||
if remote.RetryPolicy.RetryBackOff.baseInterval > 0 {
|
||||
baseInterval = &durationpb.Duration{Seconds: int64(remote.RetryPolicy.RetryBackOff.baseInterval.Seconds())}
|
||||
}
|
||||
if remote.RetryPolicy.RetryBackOff.maxInterval > 0 {
|
||||
maxInterval = &durationpb.Duration{Seconds: int64(remote.RetryPolicy.RetryBackOff.maxInterval.Seconds())}
|
||||
}
|
||||
|
||||
if baseInterval != nil || maxInterval != nil {
|
||||
retryPolicy.RetryBackOff = &envoy_core_v3.BackoffStrategy{
|
||||
BaseInterval: baseInterval,
|
||||
MaxInterval: maxInterval,
|
||||
}
|
||||
}
|
||||
|
||||
return retryPolicy
|
||||
}
|
||||
|
||||
func (w *wasmConfig) normalize() {
|
||||
if w.ProxyType == "" {
|
||||
w.ProxyType = api.ServiceKindConnectProxy
|
||||
}
|
||||
|
||||
if w.PluginConfig.VmConfig.Runtime == "" {
|
||||
w.PluginConfig.VmConfig.Runtime = supportedRuntimes[0]
|
||||
}
|
||||
|
||||
httpURI := &w.PluginConfig.VmConfig.Code.Remote.HttpURI
|
||||
httpURI.Service.Namespace = acl.NamespaceOrDefault(httpURI.Service.Namespace)
|
||||
httpURI.Service.Partition = acl.PartitionOrDefault(httpURI.Service.Partition)
|
||||
if httpURI.timeout <= 0 {
|
||||
httpURI.timeout = time.Second
|
||||
}
|
||||
}
|
||||
|
||||
// validate ensures the filterConfig is valid or it returns an error.
|
||||
// This method must be called before using the configuration.
|
||||
func (w *wasmConfig) validate() error {
|
||||
var err, resultErr error
|
||||
if w.Protocol != "tcp" && w.Protocol != "http" {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf(`unsupported Protocol %q, expected "tcp" or "http"`, w.Protocol))
|
||||
}
|
||||
if w.ProxyType != api.ServiceKindConnectProxy {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("unsupported ProxyType %q, only %q is supported", w.ProxyType, api.ServiceKindConnectProxy))
|
||||
}
|
||||
if w.ListenerType != "inbound" && w.ListenerType != "outbound" {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf(`unsupported ListenerType %q, expected "inbound" or "outbound"`, w.ListenerType))
|
||||
}
|
||||
if err = validateRuntime(w.PluginConfig.VmConfig.Runtime); err != nil {
|
||||
resultErr = multierror.Append(resultErr, err)
|
||||
}
|
||||
|
||||
httpURI := &w.PluginConfig.VmConfig.Code.Remote.HttpURI
|
||||
isLocal := w.PluginConfig.VmConfig.Code.Local.Filename != ""
|
||||
isRemote := httpURI.Service.Name != "" || httpURI.URI != ""
|
||||
if isLocal == isRemote {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("VmConfig.Code must provide exactly one of Local or Remote data source"))
|
||||
}
|
||||
|
||||
// If the data source is Local then validation is complete.
|
||||
if isLocal {
|
||||
return resultErr
|
||||
}
|
||||
|
||||
// Validate the remote data source fields.
|
||||
// Both Service and URI are required inputs for remote data sources.
|
||||
// We could catch this above in the isRemote check; however, we do an explicit check
|
||||
// here for UX to give the user extra feedback in case they only provide one or the other.
|
||||
if httpURI.Service.Name == "" || httpURI.URI == "" {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("both Service and URI are required for Remote data sources"))
|
||||
}
|
||||
if w.PluginConfig.VmConfig.Code.Remote.SHA256 == "" {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("SHA256 checksum is required for Remote data sources"))
|
||||
}
|
||||
if _, err := url.Parse(httpURI.URI); err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("invalid HttpURI.URI: %w", err))
|
||||
}
|
||||
if httpURI.Timeout != "" {
|
||||
httpURI.timeout, err = time.ParseDuration(httpURI.Timeout)
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("failed to parse HttpURI.Timeout %q as a duration: %w", httpURI.Timeout, err))
|
||||
}
|
||||
}
|
||||
|
||||
retryPolicy := &w.PluginConfig.VmConfig.Code.Remote.RetryPolicy
|
||||
if retryPolicy.NumRetries < 0 {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("RetryPolicy.NumRetries must be greater than or equal to 0"))
|
||||
}
|
||||
|
||||
if retryPolicy.RetryBackOff.BaseInterval != "" {
|
||||
retryPolicy.RetryBackOff.baseInterval, err = time.ParseDuration(retryPolicy.RetryBackOff.BaseInterval)
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("failed to parse RetryBackOff.BaseInterval %q: %w", retryPolicy.RetryBackOff.BaseInterval, err))
|
||||
}
|
||||
}
|
||||
if retryPolicy.RetryBackOff.MaxInterval != "" {
|
||||
retryPolicy.RetryBackOff.maxInterval, err = time.ParseDuration(retryPolicy.RetryBackOff.MaxInterval)
|
||||
if err != nil {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("failed to parse RetryBackOff.MaxInterval %q: %w", retryPolicy.RetryBackOff.MaxInterval, err))
|
||||
}
|
||||
}
|
||||
|
||||
if retryPolicy.RetryBackOff.BaseInterval != "" && retryPolicy.RetryBackOff.baseInterval <= 0 {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("RetryBackOff.BaseInterval %q must be greater than zero and less than or equal to RetryBackOff.MaxInterval %q",
|
||||
retryPolicy.RetryBackOff.BaseInterval,
|
||||
retryPolicy.RetryBackOff.MaxInterval),
|
||||
)
|
||||
}
|
||||
if retryPolicy.RetryBackOff.MaxInterval != "" &&
|
||||
retryPolicy.RetryBackOff.maxInterval < retryPolicy.RetryBackOff.baseInterval {
|
||||
resultErr = multierror.Append(resultErr, fmt.Errorf("RetryBackOff.MaxInterval %q must be greater than or equal to RetryBackOff.BaseInterval %q",
|
||||
retryPolicy.RetryBackOff.MaxInterval,
|
||||
retryPolicy.RetryBackOff.BaseInterval),
|
||||
)
|
||||
}
|
||||
return resultErr
|
||||
}
|
||||
|
||||
func validateRuntime(s string) error {
|
||||
for _, rt := range supportedRuntimes {
|
||||
if s == rt {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unsupported runtime %q", s)
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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"
|
||||
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_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
||||
)
|
||||
|
||||
// wasm is a built-in Envoy extension that can patch filter chains to insert Wasm plugins.
|
||||
type wasm struct {
|
||||
name string
|
||||
wasmConfig *wasmConfig
|
||||
}
|
||||
|
||||
var supportedRuntimes = []string{"v8", "wamr", "wavm", "wasmtime"}
|
||||
|
||||
var _ extensioncommon.BasicExtension = (*wasm)(nil)
|
||||
|
||||
func Constructor(ext api.EnvoyExtension) (extensioncommon.EnvoyExtender, error) {
|
||||
w, err := construct(ext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &extensioncommon.BasicEnvoyExtender{
|
||||
Extension: &w,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func construct(ext api.EnvoyExtension) (wasm, error) {
|
||||
w := wasm{name: ext.Name}
|
||||
|
||||
if w.name != api.BuiltinWasmExtension {
|
||||
return w, fmt.Errorf("expected extension name %q but got %q", api.BuiltinWasmExtension, w.name)
|
||||
}
|
||||
|
||||
if err := w.fromArguments(ext.Arguments); err != nil {
|
||||
return w, err
|
||||
}
|
||||
|
||||
// Configure the failure behavior for the filter. If the plugin is required,
|
||||
// then filter runtime errors result in a failed request (fail "closed").
|
||||
// Otherwise, runtime errors result in the filter being skipped (fail "open").
|
||||
w.wasmConfig.PluginConfig.failOpen = !ext.Required
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *wasm) fromArguments(args map[string]any) error {
|
||||
var err error
|
||||
w.wasmConfig, err = newWasmConfig(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding extension arguments: %w", err)
|
||||
}
|
||||
return w.wasmConfig.validate()
|
||||
}
|
||||
|
||||
// CanApply indicates if the WASM extension can be applied to the given extension configuration.
|
||||
// Currently the Wasm extension can be applied if the extension configuration is for an inbound
|
||||
// listener on the a local connect-proxy.
|
||||
// It does not patch extensions for service upstreams.
|
||||
func (w wasm) CanApply(config *extensioncommon.RuntimeConfig) bool {
|
||||
return config.IsLocal() && w.wasmConfig.ListenerType == "inbound" &&
|
||||
config.Kind == w.wasmConfig.ProxyType
|
||||
|
||||
}
|
||||
|
||||
// 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.
|
||||
// TODO (wasm/tcp): Add support for TCP filters.
|
||||
func (w wasm) PatchFilter(cfg *extensioncommon.RuntimeConfig, filter *envoy_listener_v3.Filter) (*envoy_listener_v3.Filter, bool, error) {
|
||||
if filter.Name != "envoy.filters.network.http_connection_manager" {
|
||||
return filter, false, nil
|
||||
}
|
||||
if typedConfig := filter.GetTypedConfig(); typedConfig == nil {
|
||||
return filter, false, errors.New("failed to get typed config for http filter")
|
||||
}
|
||||
|
||||
httpConnMgr := envoy_resource_v3.GetHTTPConnectionManager(filter)
|
||||
if httpConnMgr == nil {
|
||||
return filter, false, errors.New("failed to get HTTP connection manager")
|
||||
}
|
||||
|
||||
wasmPluginConfig, err := w.wasmConfig.PluginConfig.envoyPluginConfig(cfg)
|
||||
if err != nil {
|
||||
return filter, false, fmt.Errorf("failed to encode Envoy Wasm configuration: %w", err)
|
||||
}
|
||||
|
||||
extHttpFilter, err := extensioncommon.MakeEnvoyHTTPFilter(
|
||||
"envoy.filters.http.wasm",
|
||||
&envoy_http_wasm_v3.Wasm{Config: wasmPluginConfig},
|
||||
)
|
||||
if err != nil {
|
||||
return filter, false, err
|
||||
}
|
||||
|
||||
var (
|
||||
changedFilters = make([]*envoy_http_v3.HttpFilter, 0, len(httpConnMgr.HttpFilters)+1)
|
||||
changed bool
|
||||
)
|
||||
|
||||
// We need to be careful about overwriting http filters completely because
|
||||
// http filters validates intentions with the RBAC filter. This inserts the
|
||||
// filter before `envoy.filters.http.router` while keeping everything
|
||||
// else intact.
|
||||
for _, httpFilter := range httpConnMgr.HttpFilters {
|
||||
if httpFilter.Name == "envoy.filters.http.router" {
|
||||
changedFilters = append(changedFilters, extHttpFilter)
|
||||
changed = true
|
||||
}
|
||||
changedFilters = append(changedFilters, httpFilter)
|
||||
}
|
||||
if changed {
|
||||
httpConnMgr.HttpFilters = changedFilters
|
||||
}
|
||||
|
||||
newFilter, err := extensioncommon.MakeFilter("envoy.filters.network.http_connection_manager", httpConnMgr)
|
||||
if err != nil {
|
||||
return filter, false, errors.New("error making new filter")
|
||||
}
|
||||
|
||||
return newFilter, true, nil
|
||||
}
|
|
@ -0,0 +1,639 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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_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_wasm_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/wasm/v3"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
|
||||
"github.com/hashicorp/consul/acl"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
||||
"github.com/hashicorp/consul/proto/private/prototest"
|
||||
)
|
||||
|
||||
func TestHttpWasmExtension(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := map[string]struct {
|
||||
extName string
|
||||
canApply bool
|
||||
args func(bool) map[string]any
|
||||
rtCfg func(bool) *extensioncommon.RuntimeConfig
|
||||
inputFilters func() []*envoy_http_v3.HttpFilter
|
||||
expFilters func(tc testWasmConfig) []*envoy_http_v3.HttpFilter
|
||||
errStr string
|
||||
debug bool
|
||||
}{
|
||||
"http remote file": {
|
||||
extName: api.BuiltinWasmExtension,
|
||||
canApply: true,
|
||||
args: func(ent bool) map[string]any { return makeTestWasmConfig(ent).toMap(t) },
|
||||
rtCfg: func(ent bool) *extensioncommon.RuntimeConfig { return makeTestRuntimeConfig(ent) },
|
||||
inputFilters: makeTestHttpFilters,
|
||||
expFilters: func(tc testWasmConfig) []*envoy_http_v3.HttpFilter {
|
||||
return []*envoy_http_v3.HttpFilter{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
{
|
||||
Name: "envoy.filters.http.wasm",
|
||||
ConfigType: &envoy_http_v3.HttpFilter_TypedConfig{
|
||||
TypedConfig: makeAny(t,
|
||||
&envoy_http_wasm_v3.Wasm{
|
||||
Config: tc.toHttpWasmFilter(t),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{Name: "envoy.filters.http.router"},
|
||||
{Name: "three"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"local file": {
|
||||
extName: api.BuiltinWasmExtension,
|
||||
canApply: true,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.Protocol = "http"
|
||||
cfg.ListenerType = "inbound"
|
||||
cfg.PluginConfig.VmConfig.Code.Local.Filename = "plugin.wasm"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
rtCfg: func(ent bool) *extensioncommon.RuntimeConfig { return makeTestRuntimeConfig(ent) },
|
||||
inputFilters: makeTestHttpFilters,
|
||||
expFilters: func(tc testWasmConfig) []*envoy_http_v3.HttpFilter {
|
||||
return []*envoy_http_v3.HttpFilter{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
{
|
||||
Name: "envoy.filters.http.wasm",
|
||||
ConfigType: &envoy_http_v3.HttpFilter_TypedConfig{
|
||||
TypedConfig: makeAny(t,
|
||||
&envoy_http_wasm_v3.Wasm{
|
||||
Config: tc.toHttpWasmFilter(t),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{Name: "envoy.filters.http.router"},
|
||||
{Name: "three"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"no cluster for remote file": {
|
||||
extName: api.BuiltinWasmExtension,
|
||||
canApply: true,
|
||||
args: func(ent bool) map[string]any { return makeTestWasmConfig(ent).toMap(t) },
|
||||
rtCfg: func(ent bool) *extensioncommon.RuntimeConfig {
|
||||
rt := makeTestRuntimeConfig(ent)
|
||||
rt.LocalUpstreams = nil
|
||||
return rt
|
||||
},
|
||||
inputFilters: makeTestHttpFilters,
|
||||
errStr: "no upstream found for remote service",
|
||||
},
|
||||
}
|
||||
|
||||
for _, enterprise := range []bool{false, true} {
|
||||
|
||||
for name, c := range cases {
|
||||
c := c
|
||||
t.Run(fmt.Sprintf("%s_ent_%t", name, enterprise), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
rtCfg := c.rtCfg(enterprise)
|
||||
rtCfg.EnvoyExtension = api.EnvoyExtension{
|
||||
Name: c.extName,
|
||||
Arguments: c.args(enterprise),
|
||||
}
|
||||
|
||||
w, err := construct(rtCfg.EnvoyExtension)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.canApply, w.CanApply(rtCfg))
|
||||
if !c.canApply {
|
||||
return
|
||||
}
|
||||
|
||||
route, patched, err := w.PatchRoute(c.rtCfg(enterprise), nil)
|
||||
require.Nil(t, route)
|
||||
require.False(t, patched)
|
||||
require.NoError(t, err)
|
||||
|
||||
cluster, patched, err := w.PatchCluster(c.rtCfg(enterprise), nil)
|
||||
require.Nil(t, cluster)
|
||||
require.False(t, patched)
|
||||
require.NoError(t, err)
|
||||
|
||||
inputHttpConMgr := makeHttpConMgr(t, c.inputFilters())
|
||||
obsHttpConMgr, patched, err := w.PatchFilter(c.rtCfg(enterprise), inputHttpConMgr)
|
||||
if c.errStr == "" {
|
||||
require.NoError(t, err)
|
||||
require.True(t, patched)
|
||||
|
||||
cfg := testWasmConfigFromMap(t, c.args(enterprise))
|
||||
expHttpConMgr := makeHttpConMgr(t, c.expFilters(cfg))
|
||||
|
||||
if c.debug {
|
||||
t.Logf("cfg =\n%s\n\n", cfg.toJSON(t))
|
||||
t.Logf("expFilterJSON =\n%s\n\n", protoToJSON(t, expHttpConMgr))
|
||||
t.Logf("obsfilterJSON =\n%s\n\n", protoToJSON(t, obsHttpConMgr))
|
||||
}
|
||||
|
||||
prototest.AssertDeepEqual(t, expHttpConMgr, obsHttpConMgr)
|
||||
} else {
|
||||
require.Contains(t, err.Error(), c.errStr)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWasmConstructor(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := map[string]struct {
|
||||
name string
|
||||
args func(bool) map[string]any
|
||||
errStr string
|
||||
}{
|
||||
"with no arguments": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(_ bool) map[string]any { return nil },
|
||||
errStr: "VmConfig.Code must provide exactly one of Local or Remote data source",
|
||||
},
|
||||
"invalid protocol": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.Protocol = "invalid"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: `unsupported Protocol "invalid", expected "tcp" or "http"`,
|
||||
},
|
||||
"invalid proxy type": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.ProxyType = "invalid"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: "unsupported ProxyType",
|
||||
},
|
||||
"invalid listener type": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.ListenerType = "invalid"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: `unsupported ListenerType "invalid", expected "inbound" or "outbound"`,
|
||||
},
|
||||
"invalid runtime": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Runtime = "invalid"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: "unsupported runtime",
|
||||
},
|
||||
"both local and remote files": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Local.Filename = "plugin.wasm"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Service.Name = "file-server"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.URI = "http://file-server/plugin.wasm"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: "VmConfig.Code must provide exactly one of Local or Remote data source",
|
||||
},
|
||||
"service and uri required for remote files": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Service.Name = "file-server"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: "both Service and URI are required for Remote data sources",
|
||||
},
|
||||
"no sha for remote file": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Service.Name = "file-server"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.URI = "http://file-server/plugin.wasm"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: "SHA256 checksum is required for Remote data sources",
|
||||
},
|
||||
"invalid url for remote file": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Service.Name = "file-server"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.URI = "://bogus.url.com/error"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: `invalid HttpURI.URI: parse "://bogus.url.com/error": missing protocol scheme`,
|
||||
},
|
||||
"decoding error": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
a := makeTestWasmConfig(ent).toMap(t)
|
||||
setField(a, "PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.BaseInterval", 1000)
|
||||
return a
|
||||
},
|
||||
errStr: "got unconvertible type",
|
||||
},
|
||||
"invalid http timeout": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Timeout = "invalid"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: `failed to parse HttpURI.Timeout "invalid" as a duration`,
|
||||
},
|
||||
"invalid num retries": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.NumRetries = -1
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: "RetryPolicy.NumRetries must be greater than or equal to 0",
|
||||
},
|
||||
"invalid base interval": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.BaseInterval = "0s"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: `RetryBackOff.BaseInterval "0s" must be greater than zero and less than or equal to RetryBackOff.MaxInterval`,
|
||||
},
|
||||
"invalid max interval": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.BaseInterval = "10s"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.MaxInterval = "5s"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: `RetryBackOff.MaxInterval "5s" must be greater than or equal to RetryBackOff.BaseInterval "10s"`,
|
||||
},
|
||||
"invalid base interval duration": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.BaseInterval = "invalid"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: `failed to parse RetryBackOff.BaseInterval "invalid"`,
|
||||
},
|
||||
"invalid max interval duration": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any {
|
||||
cfg := newTestWasmConfig(ent)
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.MaxInterval = "invalid"
|
||||
return cfg.toMap(t)
|
||||
},
|
||||
errStr: `failed to parse RetryBackOff.MaxInterval "invalid"`,
|
||||
},
|
||||
"invalid extension name": {
|
||||
name: "invalid",
|
||||
args: func(ent bool) map[string]any { return newTestWasmConfig(ent).toMap(t) },
|
||||
errStr: `expected extension name "builtin/wasm" but got "invalid"`,
|
||||
},
|
||||
"valid configuration": {
|
||||
name: api.BuiltinWasmExtension,
|
||||
args: func(ent bool) map[string]any { return makeTestWasmConfig(ent).toMap(t) },
|
||||
},
|
||||
}
|
||||
for _, enterprise := range []bool{false, true} {
|
||||
for name, c := range cases {
|
||||
c := c
|
||||
t.Run(fmt.Sprintf("%s_ent_%t", name, enterprise), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
svc := api.CompoundServiceName{Name: "svc"}
|
||||
ext := extensioncommon.RuntimeConfig{
|
||||
ServiceName: svc,
|
||||
EnvoyExtension: api.EnvoyExtension{
|
||||
Name: c.name,
|
||||
Arguments: c.args(enterprise),
|
||||
},
|
||||
}
|
||||
|
||||
e, err := Constructor(ext.EnvoyExtension)
|
||||
|
||||
if c.errStr == "" {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, e)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.errStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testWasmConfig struct {
|
||||
Required bool
|
||||
Protocol string
|
||||
ProxyType string
|
||||
ListenerType string
|
||||
PluginConfig struct {
|
||||
Name string
|
||||
RootID string
|
||||
VmConfig struct {
|
||||
VmID string
|
||||
Runtime string
|
||||
Code struct {
|
||||
Local struct {
|
||||
Filename string
|
||||
}
|
||||
Remote struct {
|
||||
HttpURI struct {
|
||||
Service api.CompoundServiceName
|
||||
URI string
|
||||
Timeout string
|
||||
}
|
||||
SHA256 string
|
||||
RetryPolicy struct {
|
||||
RetryBackOff struct {
|
||||
BaseInterval string
|
||||
MaxInterval string
|
||||
}
|
||||
NumRetries int
|
||||
}
|
||||
}
|
||||
}
|
||||
Configuration string
|
||||
EnvironmentVariables struct {
|
||||
HostEnvKeys []string
|
||||
KeyValues map[string]string
|
||||
}
|
||||
}
|
||||
Configuration string
|
||||
CapabilityRestrictionConfiguration struct {
|
||||
AllowedCapabilities map[string]any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testWasmConfigFromMap(t *testing.T, m map[string]any) testWasmConfig {
|
||||
t.Helper()
|
||||
var cfg testWasmConfig
|
||||
require.NoError(t, mapstructure.Decode(m, &cfg))
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (c testWasmConfig) toMap(t *testing.T) map[string]any {
|
||||
t.Helper()
|
||||
var m map[string]any
|
||||
require.NoError(t, json.Unmarshal(c.toJSON(t), &m))
|
||||
return m
|
||||
}
|
||||
|
||||
func (c testWasmConfig) toJSON(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
b, err := json.MarshalIndent(c, "", " ")
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
}
|
||||
|
||||
func (cfg testWasmConfig) toHttpWasmFilter(t *testing.T) *envoy_wasm_v3.PluginConfig {
|
||||
t.Helper()
|
||||
var code *envoy_core_v3.AsyncDataSource
|
||||
if cfg.PluginConfig.VmConfig.Code.Local.Filename != "" {
|
||||
code = &envoy_core_v3.AsyncDataSource{
|
||||
Specifier: &envoy_core_v3.AsyncDataSource_Local{
|
||||
Local: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_Filename{
|
||||
Filename: cfg.PluginConfig.VmConfig.Code.Local.Filename,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
cluster, err := url.Parse(cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.URI)
|
||||
require.NoError(t, err)
|
||||
timeout, err := time.ParseDuration(cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Timeout)
|
||||
require.NoError(t, err)
|
||||
baseInterval, err := time.ParseDuration(cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.BaseInterval)
|
||||
require.NoError(t, err)
|
||||
maxInterval, err := time.ParseDuration(cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.MaxInterval)
|
||||
require.NoError(t, err)
|
||||
|
||||
code = &envoy_core_v3.AsyncDataSource{
|
||||
Specifier: &envoy_core_v3.AsyncDataSource_Remote{
|
||||
Remote: &envoy_core_v3.RemoteDataSource{
|
||||
Sha256: cfg.PluginConfig.VmConfig.Code.Remote.SHA256,
|
||||
HttpUri: &envoy_core_v3.HttpUri{
|
||||
Uri: cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.URI,
|
||||
Timeout: &durationpb.Duration{Seconds: int64(timeout.Seconds())},
|
||||
HttpUpstreamType: &envoy_core_v3.HttpUri_Cluster{
|
||||
Cluster: cluster.Host,
|
||||
},
|
||||
},
|
||||
RetryPolicy: &envoy_core_v3.RetryPolicy{
|
||||
RetryBackOff: &envoy_core_v3.BackoffStrategy{
|
||||
BaseInterval: &durationpb.Duration{Seconds: int64(baseInterval.Seconds())},
|
||||
MaxInterval: &durationpb.Duration{Seconds: int64(maxInterval.Seconds())},
|
||||
},
|
||||
NumRetries: wrapperspb.UInt32(uint32(cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.NumRetries)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var capConfig *envoy_wasm_v3.CapabilityRestrictionConfig
|
||||
if len(cfg.PluginConfig.CapabilityRestrictionConfiguration.AllowedCapabilities) > 0 {
|
||||
caps := make(map[string]*envoy_wasm_v3.SanitizationConfig)
|
||||
for cap := range cfg.PluginConfig.CapabilityRestrictionConfiguration.AllowedCapabilities {
|
||||
caps[cap] = &envoy_wasm_v3.SanitizationConfig{}
|
||||
}
|
||||
capConfig = &envoy_wasm_v3.CapabilityRestrictionConfig{AllowedCapabilities: caps}
|
||||
}
|
||||
|
||||
var vmConfiguration *anypb.Any
|
||||
if cfg.PluginConfig.VmConfig.Configuration != "" {
|
||||
vmConfiguration = makeAny(t, wrapperspb.String(cfg.PluginConfig.VmConfig.Configuration))
|
||||
}
|
||||
|
||||
var envVars *envoy_wasm_v3.EnvironmentVariables
|
||||
if len(cfg.PluginConfig.VmConfig.EnvironmentVariables.HostEnvKeys) > 0 ||
|
||||
len(cfg.PluginConfig.VmConfig.EnvironmentVariables.KeyValues) > 0 {
|
||||
envVars = &envoy_wasm_v3.EnvironmentVariables{
|
||||
HostEnvKeys: cfg.PluginConfig.VmConfig.EnvironmentVariables.HostEnvKeys,
|
||||
KeyValues: cfg.PluginConfig.VmConfig.EnvironmentVariables.KeyValues,
|
||||
}
|
||||
}
|
||||
|
||||
var pluginConfiguration *anypb.Any
|
||||
if cfg.PluginConfig.Configuration != "" {
|
||||
pluginConfiguration = makeAny(t, wrapperspb.String(cfg.PluginConfig.Configuration))
|
||||
}
|
||||
|
||||
rt := cfg.PluginConfig.VmConfig.Runtime
|
||||
if rt == "" {
|
||||
rt = supportedRuntimes[0]
|
||||
}
|
||||
|
||||
return &envoy_wasm_v3.PluginConfig{
|
||||
Name: cfg.PluginConfig.Name,
|
||||
RootId: cfg.PluginConfig.RootID,
|
||||
Vm: &envoy_wasm_v3.PluginConfig_VmConfig{
|
||||
VmConfig: &envoy_wasm_v3.VmConfig{
|
||||
VmId: cfg.PluginConfig.VmConfig.VmID,
|
||||
Runtime: fmt.Sprintf("envoy.wasm.runtime.%s", rt),
|
||||
Code: code,
|
||||
Configuration: vmConfiguration,
|
||||
EnvironmentVariables: envVars,
|
||||
},
|
||||
},
|
||||
Configuration: pluginConfiguration,
|
||||
FailOpen: !cfg.Required,
|
||||
CapabilityRestrictionConfig: capConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func makeAny(t *testing.T, m proto.Message) *anypb.Any {
|
||||
t.Helper()
|
||||
v, err := anypb.New(m)
|
||||
require.NoError(t, err)
|
||||
return v
|
||||
}
|
||||
|
||||
func makeHttpConMgr(t *testing.T, filters []*envoy_http_v3.HttpFilter) *envoy_listener_v3.Filter {
|
||||
t.Helper()
|
||||
return &envoy_listener_v3.Filter{
|
||||
Name: "envoy.filters.network.http_connection_manager",
|
||||
ConfigType: &envoy_listener_v3.Filter_TypedConfig{
|
||||
TypedConfig: makeAny(t, &envoy_http_v3.HttpConnectionManager{
|
||||
HttpFilters: filters,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestHttpFilters() []*envoy_http_v3.HttpFilter {
|
||||
return []*envoy_http_v3.HttpFilter{
|
||||
{Name: "one"},
|
||||
{Name: "two"},
|
||||
{Name: "envoy.filters.http.router"},
|
||||
{Name: "three"},
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestRuntimeConfig(enterprise bool) *extensioncommon.RuntimeConfig {
|
||||
var ns, ap string
|
||||
if enterprise {
|
||||
ns = "ns1"
|
||||
ap = "ap1"
|
||||
}
|
||||
return &extensioncommon.RuntimeConfig{
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
ServiceName: api.CompoundServiceName{Name: "test-service"},
|
||||
LocalUpstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
{
|
||||
Name: "test-file-server",
|
||||
Namespace: acl.NamespaceOrDefault(ns),
|
||||
Partition: acl.PartitionOrDefault(ap),
|
||||
}: {
|
||||
SNI: map[string]struct{}{"test-file-server": {}},
|
||||
EnvoyID: "test-file-server",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestWasmConfig(enterprise bool) *testWasmConfig {
|
||||
cfg := newTestWasmConfig(enterprise)
|
||||
cfg.Required = false
|
||||
cfg.Protocol = "http"
|
||||
cfg.ProxyType = "connect-proxy"
|
||||
cfg.ListenerType = "inbound"
|
||||
cfg.PluginConfig.Name = "test-plugin-name"
|
||||
cfg.PluginConfig.RootID = "test-root-id"
|
||||
cfg.PluginConfig.VmConfig.VmID = "test-vm-id"
|
||||
cfg.PluginConfig.VmConfig.Runtime = "wasmtime"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Service.Name = "test-file-server"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.URI = "https://test-file-server/plugin.wasm"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Timeout = "5s"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.SHA256 = "d05d88b0ce8a8f1d5176481e0af3ae5c65ed82cbfb8c61506c5354b076078545"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.BaseInterval = "3s"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.RetryBackOff.MaxInterval = "15s"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.RetryPolicy.NumRetries = 3
|
||||
cfg.PluginConfig.VmConfig.Configuration = "test-vm-configuration"
|
||||
cfg.PluginConfig.VmConfig.EnvironmentVariables.HostEnvKeys = []string{"PATH"}
|
||||
cfg.PluginConfig.VmConfig.EnvironmentVariables.KeyValues = map[string]string{"TEST_VAR": "TEST_VAL"}
|
||||
cfg.PluginConfig.Configuration = "test-plugin-configuration"
|
||||
cfg.PluginConfig.CapabilityRestrictionConfiguration.AllowedCapabilities = map[string]any{"proxy_on_vm_start": true}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func newTestWasmConfig(enterprise bool) *testWasmConfig {
|
||||
cfg := &testWasmConfig{}
|
||||
if enterprise {
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Service.Namespace = "ns1"
|
||||
cfg.PluginConfig.VmConfig.Code.Remote.HttpURI.Service.Partition = "ap1"
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func protoToJSON(t *testing.T, pb proto.Message) string {
|
||||
t.Helper()
|
||||
m := protojson.MarshalOptions{
|
||||
Indent: " ",
|
||||
}
|
||||
gotJSON, err := m.Marshal(pb)
|
||||
require.NoError(t, err)
|
||||
return string(gotJSON)
|
||||
}
|
||||
|
||||
func setField(m map[string]any, path string, value any) {
|
||||
upsertField(m, path, value, 0)
|
||||
}
|
||||
|
||||
func upsertField(m map[string]any, path string, value any, index int) {
|
||||
keys := strings.Split(path, ".")
|
||||
key := keys[index]
|
||||
|
||||
if val, ok := m[key]; ok {
|
||||
// update the value
|
||||
if index == len(keys)-1 {
|
||||
m[key] = value
|
||||
} else {
|
||||
upsertField(val.(map[string]any), path, value, index+1)
|
||||
}
|
||||
} else {
|
||||
// key does not exist so insert it
|
||||
if index == len(keys)-1 {
|
||||
m[key] = value
|
||||
} else {
|
||||
newMap := make(map[string]any)
|
||||
m[key] = newMap
|
||||
upsertField(newMap, path, value, index+1)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
awslambda "github.com/hashicorp/consul/agent/envoyextensions/builtin/aws-lambda"
|
||||
"github.com/hashicorp/consul/agent/envoyextensions/builtin/http/localratelimit"
|
||||
"github.com/hashicorp/consul/agent/envoyextensions/builtin/lua"
|
||||
"github.com/hashicorp/consul/agent/envoyextensions/builtin/wasm"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
@ -20,6 +21,7 @@ var extensionConstructors = map[string]extensionConstructor{
|
|||
api.BuiltinLuaExtension: lua.Constructor,
|
||||
api.BuiltinAWSLambdaExtension: awslambda.Constructor,
|
||||
api.BuiltinLocalRatelimitExtension: localratelimit.Constructor,
|
||||
api.BuiltinWasmExtension: wasm.Constructor,
|
||||
}
|
||||
|
||||
// ConstructExtension attempts to lookup and build an extension from the registry with the
|
||||
|
|
|
@ -232,6 +232,66 @@ end`,
|
|||
}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wasm-http-local-file",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
|
||||
ns.Proxy.Config["protocol"] = "http"
|
||||
ns.Proxy.EnvoyExtensions = []structs.EnvoyExtension{
|
||||
{
|
||||
Name: api.BuiltinWasmExtension,
|
||||
Arguments: map[string]interface{}{
|
||||
"Protocol": "http",
|
||||
"ListenerType": "inbound",
|
||||
"PluginConfig": map[string]interface{}{
|
||||
"VmConfig": map[string]interface{}{
|
||||
"Code": map[string]interface{}{
|
||||
"Local": map[string]interface{}{
|
||||
"Filename": "/path/to/extension.wasm",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Configuration": `{"foo": "bar"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wasm-http-remote-file",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
|
||||
ns.Proxy.Config["protocol"] = "http"
|
||||
ns.Proxy.EnvoyExtensions = []structs.EnvoyExtension{
|
||||
{
|
||||
Name: api.BuiltinWasmExtension,
|
||||
Arguments: map[string]interface{}{
|
||||
"Protocol": "http",
|
||||
"ListenerType": "inbound",
|
||||
"PluginConfig": map[string]interface{}{
|
||||
"VmConfig": map[string]interface{}{
|
||||
"Code": map[string]interface{}{
|
||||
"Remote": map[string]interface{}{
|
||||
"HttpURI": map[string]interface{}{
|
||||
"Service": map[string]interface{}{
|
||||
"Name": "db",
|
||||
},
|
||||
"URI": "https://db/plugin.wasm",
|
||||
},
|
||||
"SHA256": "d05d88b0ce8a8f1d5176481e0af3ae5c65ed82cbfb8c61506c5354b076078545",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Configuration": `{"foo": "bar"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}, nil)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
latestEnvoyVersion := xdscommon.EnvoyVersions[0]
|
||||
|
|
|
@ -90,6 +90,7 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound
|
|||
ServiceName: localSvc,
|
||||
// Upstreams is nil to signify this extension is not being applied to an upstream service, but rather to the local service.
|
||||
Upstreams: nil,
|
||||
LocalUpstreams: upstreamMap,
|
||||
Kind: kind,
|
||||
}
|
||||
extensionConfigurationsMap[localSvc] = append(extensionConfigurationsMap[localSvc], extCfg)
|
||||
|
|
|
@ -256,8 +256,17 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
ServiceName: webService,
|
||||
Upstreams: nil,
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
Upstreams: nil,
|
||||
LocalUpstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
dbService: {
|
||||
SNI: map[string]struct{}{
|
||||
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
|
||||
},
|
||||
EnvoyID: "db",
|
||||
OutgoingProxyKind: "connect-proxy",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
EnvoyExtension: api.EnvoyExtension{
|
||||
|
@ -268,8 +277,17 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
ServiceName: webService,
|
||||
Upstreams: nil,
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
Upstreams: nil,
|
||||
LocalUpstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{
|
||||
dbService: {
|
||||
SNI: map[string]struct{}{
|
||||
"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul": {},
|
||||
},
|
||||
EnvoyID: "db",
|
||||
OutgoingProxyKind: "connect-proxy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
145
agent/xds/testdata/builtin_extension/clusters/wasm-http-local-file.latest.golden
vendored
Normal file
145
agent/xds/testdata/builtin_extension/clusters/wasm-http-local-file.latest.golden
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"type": "EDS",
|
||||
"edsClusterConfig": {
|
||||
"edsConfig": {
|
||||
"ads": {
|
||||
|
||||
},
|
||||
"resourceApiVersion": "V3"
|
||||
}
|
||||
},
|
||||
"connectTimeout": "5s",
|
||||
"circuitBreakers": {
|
||||
|
||||
},
|
||||
"outlierDetection": {
|
||||
|
||||
},
|
||||
"commonLbConfig": {
|
||||
"healthyPanicThreshold": {
|
||||
|
||||
}
|
||||
},
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {
|
||||
|
||||
},
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationContext": {
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"matchSubjectAltNames": [
|
||||
{
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"type": "EDS",
|
||||
"edsClusterConfig": {
|
||||
"edsConfig": {
|
||||
"ads": {
|
||||
|
||||
},
|
||||
"resourceApiVersion": "V3"
|
||||
}
|
||||
},
|
||||
"connectTimeout": "5s",
|
||||
"circuitBreakers": {
|
||||
|
||||
},
|
||||
"outlierDetection": {
|
||||
|
||||
},
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {
|
||||
|
||||
},
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationContext": {
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"matchSubjectAltNames": [
|
||||
{
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target"
|
||||
},
|
||||
{
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"name": "local_app",
|
||||
"type": "STATIC",
|
||||
"connectTimeout": "5s",
|
||||
"loadAssignment": {
|
||||
"clusterName": "local_app",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.0.0.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"nonce": "00000001"
|
||||
}
|
145
agent/xds/testdata/builtin_extension/clusters/wasm-http-remote-file.latest.golden
vendored
Normal file
145
agent/xds/testdata/builtin_extension/clusters/wasm-http-remote-file.latest.golden
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"type": "EDS",
|
||||
"edsClusterConfig": {
|
||||
"edsConfig": {
|
||||
"ads": {
|
||||
|
||||
},
|
||||
"resourceApiVersion": "V3"
|
||||
}
|
||||
},
|
||||
"connectTimeout": "5s",
|
||||
"circuitBreakers": {
|
||||
|
||||
},
|
||||
"outlierDetection": {
|
||||
|
||||
},
|
||||
"commonLbConfig": {
|
||||
"healthyPanicThreshold": {
|
||||
|
||||
}
|
||||
},
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {
|
||||
|
||||
},
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationContext": {
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"matchSubjectAltNames": [
|
||||
{
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"type": "EDS",
|
||||
"edsClusterConfig": {
|
||||
"edsConfig": {
|
||||
"ads": {
|
||||
|
||||
},
|
||||
"resourceApiVersion": "V3"
|
||||
}
|
||||
},
|
||||
"connectTimeout": "5s",
|
||||
"circuitBreakers": {
|
||||
|
||||
},
|
||||
"outlierDetection": {
|
||||
|
||||
},
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {
|
||||
|
||||
},
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationContext": {
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"matchSubjectAltNames": [
|
||||
{
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target"
|
||||
},
|
||||
{
|
||||
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"name": "local_app",
|
||||
"type": "STATIC",
|
||||
"connectTimeout": "5s",
|
||||
"loadAssignment": {
|
||||
"clusterName": "local_app",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.0.0.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
"nonce": "00000001"
|
||||
}
|
75
agent/xds/testdata/builtin_extension/endpoints/wasm-http-local-file.latest.golden
vendored
Normal file
75
agent/xds/testdata/builtin_extension/endpoints/wasm-http-local-file.latest.golden
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
},
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.2",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
},
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.20.1.2",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"nonce": "00000001"
|
||||
}
|
75
agent/xds/testdata/builtin_extension/endpoints/wasm-http-remote-file.latest.golden
vendored
Normal file
75
agent/xds/testdata/builtin_extension/endpoints/wasm-http-remote-file.latest.golden
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"clusterName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
},
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.2",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"clusterName": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
|
||||
"endpoints": [
|
||||
{
|
||||
"lbEndpoints": [
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.10.1.1",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
},
|
||||
{
|
||||
"endpoint": {
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "10.20.1.2",
|
||||
"portValue": 8080
|
||||
}
|
||||
}
|
||||
},
|
||||
"healthStatus": "HEALTHY",
|
||||
"loadBalancingWeight": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
||||
"nonce": "00000001"
|
||||
}
|
271
agent/xds/testdata/builtin_extension/listeners/wasm-http-local-file.latest.golden
vendored
Normal file
271
agent/xds/testdata/builtin_extension/listeners/wasm-http-local-file.latest.golden
vendored
Normal file
|
@ -0,0 +1,271 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "db:127.0.0.1:9191",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.0.0.1",
|
||||
"portValue": 9191
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"statPrefix": "upstream.db.default.default.dc1",
|
||||
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.10.10.10",
|
||||
"portValue": 8181
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"statPrefix": "upstream.prepared_query_geo-cache",
|
||||
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "public_listener:0.0.0.0:9999",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "0.0.0.0",
|
||||
"portValue": 9999
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.http_connection_manager",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
"statPrefix": "public_listener",
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"name": "public_listener",
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "local_app"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.rbac",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
|
||||
"rules": {
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.header_to_metadata",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.header_to_metadata.v3.Config",
|
||||
"requestRules": [
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "trust-domain",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "partition",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "namespace",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "datacenter",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\4"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "service",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\5"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.wasm",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm",
|
||||
"config": {
|
||||
"vmConfig": {
|
||||
"runtime": "envoy.wasm.runtime.v8",
|
||||
"code": {
|
||||
"local": {
|
||||
"filename": "/path/to/extension.wasm"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"@type": "type.googleapis.com/google.protobuf.StringValue",
|
||||
"value": "{\"foo\": \"bar\"}"
|
||||
},
|
||||
"failOpen": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {
|
||||
|
||||
}
|
||||
},
|
||||
"forwardClientCertDetails": "APPEND_FORWARD",
|
||||
"setCurrentClientCertDetails": {
|
||||
"subject": true,
|
||||
"cert": true,
|
||||
"chain": true,
|
||||
"dns": true,
|
||||
"uri": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {
|
||||
|
||||
},
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationContext": {
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
},
|
||||
"alpnProtocols": [
|
||||
"http/1.1"
|
||||
]
|
||||
},
|
||||
"requireClientCertificate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"trafficDirection": "INBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
276
agent/xds/testdata/builtin_extension/listeners/wasm-http-remote-file.latest.golden
vendored
Normal file
276
agent/xds/testdata/builtin_extension/listeners/wasm-http-remote-file.latest.golden
vendored
Normal file
|
@ -0,0 +1,276 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"resources": [
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "db:127.0.0.1:9191",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.0.0.1",
|
||||
"portValue": 9191
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"statPrefix": "upstream.db.default.default.dc1",
|
||||
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "127.10.10.10",
|
||||
"portValue": 8181
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.tcp_proxy",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||
"statPrefix": "upstream.prepared_query_geo-cache",
|
||||
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"trafficDirection": "OUTBOUND"
|
||||
},
|
||||
{
|
||||
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"name": "public_listener:0.0.0.0:9999",
|
||||
"address": {
|
||||
"socketAddress": {
|
||||
"address": "0.0.0.0",
|
||||
"portValue": 9999
|
||||
}
|
||||
},
|
||||
"filterChains": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"name": "envoy.filters.network.http_connection_manager",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
"statPrefix": "public_listener",
|
||||
"routeConfig": {
|
||||
"name": "public_listener",
|
||||
"virtualHosts": [
|
||||
{
|
||||
"name": "public_listener",
|
||||
"domains": [
|
||||
"*"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": {
|
||||
"prefix": "/"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "local_app"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"httpFilters": [
|
||||
{
|
||||
"name": "envoy.filters.http.rbac",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
|
||||
"rules": {
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.header_to_metadata",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.header_to_metadata.v3.Config",
|
||||
"requestRules": [
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "trust-domain",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "partition",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "namespace",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "datacenter",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\4"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": "x-forwarded-client-cert",
|
||||
"onHeaderPresent": {
|
||||
"metadataNamespace": "consul",
|
||||
"key": "service",
|
||||
"regexValueRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {
|
||||
|
||||
},
|
||||
"regex": ".*URI=spiffe://([^/]+.[^/]+)(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/;,]+).*"
|
||||
},
|
||||
"substitution": "\\5"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.wasm",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm",
|
||||
"config": {
|
||||
"vmConfig": {
|
||||
"runtime": "envoy.wasm.runtime.v8",
|
||||
"code": {
|
||||
"remote": {
|
||||
"httpUri": {
|
||||
"uri": "https://db/plugin.wasm",
|
||||
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
||||
"timeout": "1s"
|
||||
},
|
||||
"sha256": "d05d88b0ce8a8f1d5176481e0af3ae5c65ed82cbfb8c61506c5354b076078545"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"@type": "type.googleapis.com/google.protobuf.StringValue",
|
||||
"value": "{\"foo\": \"bar\"}"
|
||||
},
|
||||
"failOpen": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "envoy.filters.http.router",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tracing": {
|
||||
"randomSampling": {
|
||||
|
||||
}
|
||||
},
|
||||
"forwardClientCertDetails": "APPEND_FORWARD",
|
||||
"setCurrentClientCertDetails": {
|
||||
"subject": true,
|
||||
"cert": true,
|
||||
"chain": true,
|
||||
"dns": true,
|
||||
"uri": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"transportSocket": {
|
||||
"name": "tls",
|
||||
"typedConfig": {
|
||||
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||
"commonTlsContext": {
|
||||
"tlsParams": {
|
||||
|
||||
},
|
||||
"tlsCertificates": [
|
||||
{
|
||||
"certificateChain": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||
},
|
||||
"privateKey": {
|
||||
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"validationContext": {
|
||||
"trustedCa": {
|
||||
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
},
|
||||
"alpnProtocols": [
|
||||
"http/1.1"
|
||||
]
|
||||
},
|
||||
"requireClientCertificate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"trafficDirection": "INBOUND"
|
||||
}
|
||||
],
|
||||
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
"nonce": "00000001"
|
||||
}
|
5
agent/xds/testdata/builtin_extension/routes/wasm-http-local-file.latest.golden
vendored
Normal file
5
agent/xds/testdata/builtin_extension/routes/wasm-http-local-file.latest.golden
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
|
||||
"nonce": "00000001"
|
||||
}
|
5
agent/xds/testdata/builtin_extension/routes/wasm-http-remote-file.latest.golden
vendored
Normal file
5
agent/xds/testdata/builtin_extension/routes/wasm-http-remote-file.latest.golden
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"versionInfo": "00000001",
|
||||
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
|
||||
"nonce": "00000001"
|
||||
}
|
|
@ -41,6 +41,7 @@ const (
|
|||
BuiltinAWSLambdaExtension string = "builtin/aws/lambda"
|
||||
BuiltinLuaExtension string = "builtin/lua"
|
||||
BuiltinLocalRatelimitExtension string = "builtin/http/localratelimit"
|
||||
BuiltinWasmExtension string = "builtin/wasm"
|
||||
)
|
||||
|
||||
type ConfigEntry interface {
|
||||
|
|
|
@ -1,29 +1,27 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package awslambda
|
||||
package extensioncommon
|
||||
|
||||
import (
|
||||
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_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
)
|
||||
|
||||
// This is copied from xds and not put into the shared package because I'm not
|
||||
// convinced it should be shared.
|
||||
|
||||
func makeUpstreamTLSTransportSocket(tlsContext *envoy_tls_v3.UpstreamTlsContext) (*envoy_core_v3.TransportSocket, error) {
|
||||
// MakeUpstreamTLSTransportSocket generates an Envoy transport socket for the given TLS context.
|
||||
func MakeUpstreamTLSTransportSocket(tlsContext *envoy_tls_v3.UpstreamTlsContext) (*envoy_core_v3.TransportSocket, error) {
|
||||
if tlsContext == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return makeTransportSocket("tls", tlsContext)
|
||||
return MakeTransportSocket("tls", tlsContext)
|
||||
}
|
||||
|
||||
func makeTransportSocket(name string, config proto.Message) (*envoy_core_v3.TransportSocket, error) {
|
||||
// MakeTransportSocket generates an Envoy transport socket from the given proto message.
|
||||
func MakeTransportSocket(name string, config proto.Message) (*envoy_core_v3.TransportSocket, error) {
|
||||
any, err := anypb.New(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -36,7 +34,8 @@ func makeTransportSocket(name string, config proto.Message) (*envoy_core_v3.Tran
|
|||
}, nil
|
||||
}
|
||||
|
||||
func makeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFilter, error) {
|
||||
// MakeEnvoyHTTPFilter generates an Envoy HTTP filter from the given proto message.
|
||||
func MakeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFilter, error) {
|
||||
any, err := anypb.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -48,7 +47,8 @@ func makeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFil
|
|||
}, nil
|
||||
}
|
||||
|
||||
func makeFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, error) {
|
||||
// MakeFilter generates an Envoy listener filter from the given proto message.
|
||||
func MakeFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, error) {
|
||||
any, err := anypb.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
|
@ -23,10 +23,9 @@ type UpstreamData struct {
|
|||
VIP string
|
||||
}
|
||||
|
||||
// RuntimeConfig is the configuration for an extension attached to a service on the local proxy. Currently, it
|
||||
// is only created for the local proxy's upstream service if the upstream service has an extension configured. In the
|
||||
// future it will also include information about the service local to the local proxy as well. It should depend on the
|
||||
// API client rather than the structs package because the API client is meant to be public.
|
||||
// RuntimeConfig is the configuration for an extension attached to a service on the local proxy.
|
||||
// It should depend on the API client rather than the structs package because the API client is
|
||||
// meant to be public.
|
||||
type RuntimeConfig struct {
|
||||
// EnvoyExtension is the extension that will patch Envoy resources.
|
||||
EnvoyExtension api.EnvoyExtension
|
||||
|
@ -39,27 +38,40 @@ type RuntimeConfig struct {
|
|||
// If there are no Upstreams, then EnvoyExtension is being applied to the local service's resources.
|
||||
Upstreams map[api.CompoundServiceName]*UpstreamData
|
||||
|
||||
// LocalUpstreams will only be configured if the EnvoyExtension is being applied to the local service.
|
||||
LocalUpstreams map[api.CompoundServiceName]*UpstreamData
|
||||
|
||||
// Kind is mode the local Envoy proxy is running in. For now, only connect proxy and
|
||||
// terminating gateways are supported.
|
||||
Kind api.ServiceKind
|
||||
}
|
||||
|
||||
// IsLocal indicates if the extension configuration is for the proxy's local service.
|
||||
func (ec RuntimeConfig) IsLocal() bool {
|
||||
return !ec.IsUpstream()
|
||||
}
|
||||
|
||||
// IsUpstream indicates if the extension configuration is for an upstream service.
|
||||
func (ec RuntimeConfig) IsUpstream() bool {
|
||||
_, ok := ec.Upstreams[ec.ServiceName]
|
||||
return ok
|
||||
}
|
||||
|
||||
// MatchesUpstreamServiceSNI indicates if the extension configuration is for an upstream service
|
||||
// that matches the given SNI.
|
||||
func (ec RuntimeConfig) MatchesUpstreamServiceSNI(sni string) bool {
|
||||
u := ec.Upstreams[ec.ServiceName]
|
||||
_, match := u.SNI[sni]
|
||||
return match
|
||||
}
|
||||
|
||||
// EnvoyID returns the unique Envoy identifier of the upstream service.
|
||||
func (ec RuntimeConfig) EnvoyID() string {
|
||||
u := ec.Upstreams[ec.ServiceName]
|
||||
return u.EnvoyID
|
||||
}
|
||||
|
||||
// OutgoingProxyKind returns the service kind for the outgoing listener of an upstream service.
|
||||
func (ec RuntimeConfig) OutgoingProxyKind() api.ServiceKind {
|
||||
u := ec.Upstreams[ec.ServiceName]
|
||||
return u.OutgoingProxyKind
|
||||
|
|
|
@ -7,7 +7,7 @@ load helpers
|
|||
}
|
||||
|
||||
@test "s1 has lambda http filter for l1" {
|
||||
assert_lambda_envoy_dynamic_http_filter_exists localhost:19000 l1 $AWS_LAMBDA_ARN
|
||||
assert_lambda_envoy_dynamic_http_filter_exists localhost:19000 $AWS_LAMBDA_ARN
|
||||
}
|
||||
|
||||
@test "terminating gateway has lambda cluster for l2" {
|
||||
|
@ -15,7 +15,7 @@ load helpers
|
|||
}
|
||||
|
||||
@test "terminating gateway has lambda http filter for l2" {
|
||||
assert_lambda_envoy_dynamic_http_filter_exists localhost:20000 l2 $AWS_LAMBDA_ARN
|
||||
assert_lambda_envoy_dynamic_http_filter_exists localhost:20000 $AWS_LAMBDA_ARN
|
||||
}
|
||||
|
||||
@test "s1 can call l1 through its sidecar-proxy" {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
snapshot_envoy_admin localhost:19000 s1 primary || true
|
||||
snapshot_envoy_admin localhost:19001 s2 primary || true
|
Binary file not shown.
|
@ -0,0 +1,16 @@
|
|||
services {
|
||||
name = "s1"
|
||||
port = 8080
|
||||
connect {
|
||||
sidecar_service {
|
||||
proxy {
|
||||
upstreams = [
|
||||
{
|
||||
destination_name = "s2"
|
||||
local_bind_port = 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
services {
|
||||
name = "s2"
|
||||
port = 8181
|
||||
connect { sidecar_service {} }
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eEuo pipefail
|
||||
|
||||
upsert_config_entry primary '
|
||||
Kind = "service-defaults"
|
||||
Name = "s2"
|
||||
Protocol = "http"
|
||||
EnvoyExtensions = [
|
||||
{
|
||||
Name = "builtin/wasm"
|
||||
Arguments = {
|
||||
Protocol = "http"
|
||||
ListenerType = "inbound"
|
||||
PluginConfig = {
|
||||
VmConfig = {
|
||||
Code = {
|
||||
Local = {
|
||||
Filename = "/workdir/primary/data/dummy.wasm"
|
||||
}
|
||||
}
|
||||
}
|
||||
Configuration = "plugin configuration"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
'
|
||||
|
||||
register_services primary
|
||||
|
||||
gen_envoy_bootstrap s1 19000 primary
|
||||
gen_envoy_bootstrap s2 19001 primary
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
export REQUIRED_SERVICES="s1 s1-sidecar-proxy s2 s2-sidecar-proxy"
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load helpers
|
||||
|
||||
@test "s1 proxy admin is up on :19000" {
|
||||
retry_default curl -f -s localhost:19000/stats -o /dev/null
|
||||
}
|
||||
|
||||
@test "s2 proxy admin is up on :19001" {
|
||||
retry_default curl -f -s localhost:19001/stats -o /dev/null
|
||||
}
|
||||
|
||||
@test "s1 proxy listener should be up and have right cert" {
|
||||
assert_proxy_presents_cert_uri localhost:21000 s1
|
||||
}
|
||||
|
||||
@test "s2 proxy is configured with a wasm http filter" {
|
||||
run get_envoy_http_filter localhost:19001 envoy.filters.http.wasm
|
||||
[ "$status" == 0 ]
|
||||
|
||||
[ "$(echo "$output" | jq -r '.typed_config.config.vm_config.runtime')" == "envoy.wasm.runtime.v8" ]
|
||||
[ "$(echo "$output" | jq -r '.typed_config.config.vm_config.code.local.filename')" == "/workdir/primary/data/dummy.wasm" ]
|
||||
[ "$(echo "$output" | jq -r '.typed_config.config.configuration.value')" == "plugin configuration" ]
|
||||
[ "$(echo "$output" | jq -r '.typed_config.config.fail_open')" == "true" ]
|
||||
}
|
|
@ -260,6 +260,14 @@ function get_envoy_network_rbac_once {
|
|||
echo "$output" | jq --raw-output '.configs[2].dynamic_listeners[].active_state.listener.filter_chains[0].filters[] | select(.name == "envoy.filters.network.rbac") | .typed_config'
|
||||
}
|
||||
|
||||
function get_envoy_http_filter {
|
||||
local HOSTPORT=$1
|
||||
local FILTER_NAME=$2
|
||||
run retry_default curl -s -f $HOSTPORT/config_dump
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | jq --raw-output ".configs[2].dynamic_listeners[] | .active_state.listener.filter_chains[].filters[] | select(.name == \"envoy.filters.network.http_connection_manager\") | .typed_config.http_filters[] | select(.name == \"${FILTER_NAME}\")"
|
||||
}
|
||||
|
||||
function get_envoy_listener_filters {
|
||||
local HOSTPORT=$1
|
||||
run retry_default curl -s -f $HOSTPORT/config_dump
|
||||
|
@ -1078,15 +1086,6 @@ function assert_service_has_imported {
|
|||
fi
|
||||
}
|
||||
|
||||
function get_lambda_envoy_http_filter {
|
||||
local HOSTPORT=$1
|
||||
local NAME_PREFIX=$2
|
||||
run retry_default curl -s -f $HOSTPORT/config_dump
|
||||
[ "$status" -eq 0 ]
|
||||
# get the full http filter object so the individual fields can be validated.
|
||||
echo "$output" | jq --raw-output ".configs[2].dynamic_listeners[] | .active_state.listener.filter_chains[].filters[] | select(.name == \"envoy.filters.network.http_connection_manager\") | .typed_config.http_filters[] | select(.name == \"envoy.filters.http.aws_lambda\") | .typed_config"
|
||||
}
|
||||
|
||||
function register_lambdas {
|
||||
local DC=${1:-primary}
|
||||
# register lambdas to the catalog
|
||||
|
@ -1114,13 +1113,12 @@ function assert_lambda_envoy_dynamic_cluster_exists {
|
|||
|
||||
function assert_lambda_envoy_dynamic_http_filter_exists {
|
||||
local HOSTPORT=$1
|
||||
local NAME_PREFIX=$2
|
||||
local ARN=$3
|
||||
local ARN=$2
|
||||
|
||||
local FILTER=$(get_lambda_envoy_http_filter $HOSTPORT $NAME_PREFIX)
|
||||
local FILTER=$(get_envoy_http_filter $HOSTPORT 'envoy.filters.http.aws_lambda')
|
||||
[ -n "$FILTER" ]
|
||||
|
||||
[ "$(echo $FILTER | jq -r '.arn')" == "$ARN" ]
|
||||
[ "$(echo $FILTER | jq -r '.typed_config | .arn')" == "$ARN" ]
|
||||
}
|
||||
|
||||
function varsub {
|
||||
|
|
Loading…
Reference in New Issue