Wasm Envoy HTTP extension (#16877)
This commit is contained in:
parent
2b0a5b52c2
commit
f9126b6c3a
|
@ -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
|
// 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) {
|
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",
|
Sni: "*.amazonaws.com",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ func (a *awsLambda) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_
|
||||||
return filter, false, errors.New("error unmarshalling filter")
|
return filter, false, errors.New("error unmarshalling filter")
|
||||||
}
|
}
|
||||||
|
|
||||||
lambdaHttpFilter, err := makeEnvoyHTTPFilter(
|
lambdaHttpFilter, err := extensioncommon.MakeEnvoyHTTPFilter(
|
||||||
"envoy.filters.http.aws_lambda",
|
"envoy.filters.http.aws_lambda",
|
||||||
&envoy_lambda_v3.Config{
|
&envoy_lambda_v3.Config{
|
||||||
Arn: a.ARN,
|
Arn: a.ARN,
|
||||||
|
@ -204,7 +204,7 @@ func (a *awsLambda) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_
|
||||||
config.StripPortMode = &envoy_http_v3.HttpConnectionManager_StripAnyHostPort{
|
config.StripPortMode = &envoy_http_v3.HttpConnectionManager_StripAnyHostPort{
|
||||||
StripAnyHostPort: true,
|
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 {
|
if err != nil {
|
||||||
return filter, false, errors.New("error making new filter")
|
return filter, false, errors.New("error making new filter")
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,7 @@ func TestPatchCluster(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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",
|
Sni: "*.amazonaws.com",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
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.filters.http.local_ratelimit",
|
||||||
&envoy_ratelimit.LocalRateLimit{
|
&envoy_ratelimit.LocalRateLimit{
|
||||||
TokenBucket: &tokenBucket,
|
TokenBucket: &tokenBucket,
|
||||||
|
@ -184,7 +184,7 @@ func (p ratelimit) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_l
|
||||||
changedFilters = append(changedFilters, config.HttpFilters...)
|
changedFilters = append(changedFilters, config.HttpFilters...)
|
||||||
config.HttpFilters = changedFilters
|
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 {
|
if err != nil {
|
||||||
return filter, false, errors.New("error making new filter")
|
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 {
|
if config == nil {
|
||||||
return filter, false, errors.New("error unmarshalling filter")
|
return filter, false, errors.New("error unmarshalling filter")
|
||||||
}
|
}
|
||||||
luaHttpFilter, err := makeEnvoyHTTPFilter(
|
luaHttpFilter, err := extensioncommon.MakeEnvoyHTTPFilter(
|
||||||
"envoy.filters.http.lua",
|
"envoy.filters.http.lua",
|
||||||
&envoy_lua_v3.Lua{
|
&envoy_lua_v3.Lua{
|
||||||
InlineCode: l.Script,
|
InlineCode: l.Script,
|
||||||
|
@ -124,7 +124,7 @@ func (l *lua) PatchFilter(_ *extensioncommon.RuntimeConfig, filter *envoy_listen
|
||||||
config.HttpFilters = changedFilters
|
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 {
|
if err != nil {
|
||||||
return filter, false, errors.New("error making new filter")
|
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"
|
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/http/localratelimit"
|
||||||
"github.com/hashicorp/consul/agent/envoyextensions/builtin/lua"
|
"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/api"
|
||||||
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
|
@ -20,6 +21,7 @@ var extensionConstructors = map[string]extensionConstructor{
|
||||||
api.BuiltinLuaExtension: lua.Constructor,
|
api.BuiltinLuaExtension: lua.Constructor,
|
||||||
api.BuiltinAWSLambdaExtension: awslambda.Constructor,
|
api.BuiltinAWSLambdaExtension: awslambda.Constructor,
|
||||||
api.BuiltinLocalRatelimitExtension: localratelimit.Constructor,
|
api.BuiltinLocalRatelimitExtension: localratelimit.Constructor,
|
||||||
|
api.BuiltinWasmExtension: wasm.Constructor,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConstructExtension attempts to lookup and build an extension from the registry with the
|
// ConstructExtension attempts to lookup and build an extension from the registry with the
|
||||||
|
|
|
@ -232,6 +232,66 @@ end`,
|
||||||
}, nil)
|
}, 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]
|
latestEnvoyVersion := xdscommon.EnvoyVersions[0]
|
||||||
|
|
|
@ -89,8 +89,9 @@ func GetRuntimeConfigurations(cfgSnap *proxycfg.ConfigSnapshot) map[api.Compound
|
||||||
EnvoyExtension: ext,
|
EnvoyExtension: ext,
|
||||||
ServiceName: localSvc,
|
ServiceName: localSvc,
|
||||||
// Upstreams is nil to signify this extension is not being applied to an upstream service, but rather to the local service.
|
// Upstreams is nil to signify this extension is not being applied to an upstream service, but rather to the local service.
|
||||||
Upstreams: nil,
|
Upstreams: nil,
|
||||||
Kind: kind,
|
LocalUpstreams: upstreamMap,
|
||||||
|
Kind: kind,
|
||||||
}
|
}
|
||||||
extensionConfigurationsMap[localSvc] = append(extensionConfigurationsMap[localSvc], extCfg)
|
extensionConfigurationsMap[localSvc] = append(extensionConfigurationsMap[localSvc], extCfg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,8 +256,17 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ServiceName: webService,
|
ServiceName: webService,
|
||||||
Upstreams: nil,
|
|
||||||
Kind: api.ServiceKindConnectProxy,
|
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{
|
EnvoyExtension: api.EnvoyExtension{
|
||||||
|
@ -268,8 +277,17 @@ func TestGetRuntimeConfigurations_ConnectProxy(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ServiceName: webService,
|
ServiceName: webService,
|
||||||
Upstreams: nil,
|
|
||||||
Kind: api.ServiceKindConnectProxy,
|
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"
|
BuiltinAWSLambdaExtension string = "builtin/aws/lambda"
|
||||||
BuiltinLuaExtension string = "builtin/lua"
|
BuiltinLuaExtension string = "builtin/lua"
|
||||||
BuiltinLocalRatelimitExtension string = "builtin/http/localratelimit"
|
BuiltinLocalRatelimitExtension string = "builtin/http/localratelimit"
|
||||||
|
BuiltinWasmExtension string = "builtin/wasm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigEntry interface {
|
type ConfigEntry interface {
|
||||||
|
|
|
@ -1,29 +1,27 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
// Copyright (c) HashiCorp, Inc.
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package awslambda
|
package extensioncommon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||||
"google.golang.org/protobuf/types/known/anypb"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"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
|
// MakeUpstreamTLSTransportSocket generates an Envoy transport socket for the given TLS context.
|
||||||
// convinced it should be shared.
|
func MakeUpstreamTLSTransportSocket(tlsContext *envoy_tls_v3.UpstreamTlsContext) (*envoy_core_v3.TransportSocket, error) {
|
||||||
|
|
||||||
func makeUpstreamTLSTransportSocket(tlsContext *envoy_tls_v3.UpstreamTlsContext) (*envoy_core_v3.TransportSocket, error) {
|
|
||||||
if tlsContext == nil {
|
if tlsContext == nil {
|
||||||
return nil, 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)
|
any, err := anypb.New(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -36,7 +34,8 @@ func makeTransportSocket(name string, config proto.Message) (*envoy_core_v3.Tran
|
||||||
}, nil
|
}, 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)
|
any, err := anypb.New(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -48,7 +47,8 @@ func makeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFil
|
||||||
}, nil
|
}, 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)
|
any, err := anypb.New(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
|
@ -23,10 +23,9 @@ type UpstreamData struct {
|
||||||
VIP string
|
VIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuntimeConfig is the configuration for an extension attached to a service on the local proxy. Currently, it
|
// RuntimeConfig is the configuration for an extension attached to a service on the local proxy.
|
||||||
// is only created for the local proxy's upstream service if the upstream service has an extension configured. In the
|
// It should depend on the API client rather than the structs package because the API client is
|
||||||
// future it will also include information about the service local to the local proxy as well. It should depend on the
|
// meant to be public.
|
||||||
// API client rather than the structs package because the API client is meant to be public.
|
|
||||||
type RuntimeConfig struct {
|
type RuntimeConfig struct {
|
||||||
// EnvoyExtension is the extension that will patch Envoy resources.
|
// EnvoyExtension is the extension that will patch Envoy resources.
|
||||||
EnvoyExtension api.EnvoyExtension
|
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.
|
// If there are no Upstreams, then EnvoyExtension is being applied to the local service's resources.
|
||||||
Upstreams map[api.CompoundServiceName]*UpstreamData
|
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
|
// Kind is mode the local Envoy proxy is running in. For now, only connect proxy and
|
||||||
// terminating gateways are supported.
|
// terminating gateways are supported.
|
||||||
Kind api.ServiceKind
|
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 {
|
func (ec RuntimeConfig) IsUpstream() bool {
|
||||||
_, ok := ec.Upstreams[ec.ServiceName]
|
_, ok := ec.Upstreams[ec.ServiceName]
|
||||||
return ok
|
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 {
|
func (ec RuntimeConfig) MatchesUpstreamServiceSNI(sni string) bool {
|
||||||
u := ec.Upstreams[ec.ServiceName]
|
u := ec.Upstreams[ec.ServiceName]
|
||||||
_, match := u.SNI[sni]
|
_, match := u.SNI[sni]
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnvoyID returns the unique Envoy identifier of the upstream service.
|
||||||
func (ec RuntimeConfig) EnvoyID() string {
|
func (ec RuntimeConfig) EnvoyID() string {
|
||||||
u := ec.Upstreams[ec.ServiceName]
|
u := ec.Upstreams[ec.ServiceName]
|
||||||
return u.EnvoyID
|
return u.EnvoyID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OutgoingProxyKind returns the service kind for the outgoing listener of an upstream service.
|
||||||
func (ec RuntimeConfig) OutgoingProxyKind() api.ServiceKind {
|
func (ec RuntimeConfig) OutgoingProxyKind() api.ServiceKind {
|
||||||
u := ec.Upstreams[ec.ServiceName]
|
u := ec.Upstreams[ec.ServiceName]
|
||||||
return u.OutgoingProxyKind
|
return u.OutgoingProxyKind
|
||||||
|
|
|
@ -7,7 +7,7 @@ load helpers
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "s1 has lambda http filter for l1" {
|
@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" {
|
@test "terminating gateway has lambda cluster for l2" {
|
||||||
|
@ -15,7 +15,7 @@ load helpers
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "terminating gateway has lambda http filter for l2" {
|
@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" {
|
@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'
|
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 {
|
function get_envoy_listener_filters {
|
||||||
local HOSTPORT=$1
|
local HOSTPORT=$1
|
||||||
run retry_default curl -s -f $HOSTPORT/config_dump
|
run retry_default curl -s -f $HOSTPORT/config_dump
|
||||||
|
@ -1078,15 +1086,6 @@ function assert_service_has_imported {
|
||||||
fi
|
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 {
|
function register_lambdas {
|
||||||
local DC=${1:-primary}
|
local DC=${1:-primary}
|
||||||
# register lambdas to the catalog
|
# register lambdas to the catalog
|
||||||
|
@ -1114,13 +1113,12 @@ function assert_lambda_envoy_dynamic_cluster_exists {
|
||||||
|
|
||||||
function assert_lambda_envoy_dynamic_http_filter_exists {
|
function assert_lambda_envoy_dynamic_http_filter_exists {
|
||||||
local HOSTPORT=$1
|
local HOSTPORT=$1
|
||||||
local NAME_PREFIX=$2
|
local ARN=$2
|
||||||
local ARN=$3
|
|
||||||
|
|
||||||
local FILTER=$(get_lambda_envoy_http_filter $HOSTPORT $NAME_PREFIX)
|
local FILTER=$(get_envoy_http_filter $HOSTPORT 'envoy.filters.http.aws_lambda')
|
||||||
[ -n "$FILTER" ]
|
[ -n "$FILTER" ]
|
||||||
|
|
||||||
[ "$(echo $FILTER | jq -r '.arn')" == "$ARN" ]
|
[ "$(echo $FILTER | jq -r '.typed_config | .arn')" == "$ARN" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
function varsub {
|
function varsub {
|
||||||
|
|
Loading…
Reference in New Issue