mirror of
https://github.com/status-im/consul.git
synced 2025-01-11 06:16:08 +00:00
8728a4496c
* Add Tproxy support to Envoy Extensions (this is needed for service to service validation) * Add validation for Envoy configuration for an upstream service * Use both /config_dump and /cluster to validate Envoy configuration This is because of a bug in Envoy where the EndpointsConfigDump does not include a cluster_name, making it impossible to match an endpoint to verify it exists. This removes endpoints support for builtin extensions since only the validate plugin was using it, and it is no longer used. It also removes test cases for endpoint validation. Endpoints validation now only occurs in the top level test from config_dump and clusters json files. Co-authored-by: Eric <eric@haberkorn.co>
289 lines
9.0 KiB
Go
289 lines
9.0 KiB
Go
//go:build !consulent
|
|
// +build !consulent
|
|
|
|
package xds
|
|
|
|
import (
|
|
"path/filepath"
|
|
"sort"
|
|
"testing"
|
|
|
|
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
|
envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/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"
|
|
testinf "github.com/mitchellh/go-testing-interface"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/hashicorp/consul/agent/proxycfg"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/agent/xds/proxysupport"
|
|
"github.com/hashicorp/consul/agent/xds/xdscommon"
|
|
"github.com/hashicorp/consul/api"
|
|
"github.com/hashicorp/consul/sdk/testutil"
|
|
)
|
|
|
|
func TestBuiltinExtensionsFromSnapshot(t *testing.T) {
|
|
// If opposite is true, the returned service defaults config entry will have
|
|
// payload-passthrough=true and invocation-mode=asynchronous.
|
|
// Otherwise payload-passthrough=false and invocation-mode=synchronous.
|
|
// This is used to test all the permutations.
|
|
makeLambdaServiceDefaults := func(opposite bool) *structs.ServiceConfigEntry {
|
|
payloadPassthrough := true
|
|
if opposite {
|
|
payloadPassthrough = false
|
|
}
|
|
|
|
invocationMode := "synchronous"
|
|
if opposite {
|
|
invocationMode = "asynchronous"
|
|
}
|
|
|
|
return &structs.ServiceConfigEntry{
|
|
Kind: structs.ServiceDefaults,
|
|
Name: "db",
|
|
Protocol: "http",
|
|
EnvoyExtensions: []structs.EnvoyExtension{
|
|
{
|
|
Name: api.BuiltinAWSLambdaExtension,
|
|
Arguments: map[string]interface{}{
|
|
"ARN": "arn:aws:lambda:us-east-1:111111111111:function:lambda-1234",
|
|
"PayloadPassthrough": payloadPassthrough,
|
|
"InvocationMode": invocationMode,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
makeLuaServiceDefaults := func(inbound bool) *structs.ServiceConfigEntry {
|
|
listener := "inbound"
|
|
if !inbound {
|
|
listener = "outbound"
|
|
}
|
|
|
|
return &structs.ServiceConfigEntry{
|
|
Kind: structs.ServiceDefaults,
|
|
Name: "db",
|
|
Protocol: "http",
|
|
EnvoyExtensions: []structs.EnvoyExtension{
|
|
{
|
|
Name: api.BuiltinLuaExtension,
|
|
Arguments: map[string]interface{}{
|
|
"ProxyType": "connect-proxy",
|
|
"Listener": listener,
|
|
"Script": `
|
|
function envoy_on_request(request_handle)
|
|
request_handle:headers():add("test", "test")
|
|
end`,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
create func(t testinf.T) *proxycfg.ConfigSnapshot
|
|
}{
|
|
{
|
|
name: "lambda-connect-proxy",
|
|
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
|
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeLambdaServiceDefaults(false))
|
|
},
|
|
},
|
|
{
|
|
name: "lambda-connect-proxy-tproxy",
|
|
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
|
extra := makeLambdaServiceDefaults(false)
|
|
extra.Name = "google"
|
|
return proxycfg.TestConfigSnapshotTransparentProxyHTTPUpstream(t, extra)
|
|
},
|
|
},
|
|
// Make sure that if the upstream type is different from ExtensionConfiguration.Kind is, that the resources are not patched.
|
|
{
|
|
name: "lambda-connect-proxy-with-terminating-gateway-upstream",
|
|
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
|
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", nil, nil, makeLambdaServiceDefaults(false))
|
|
},
|
|
},
|
|
{
|
|
name: "lambda-connect-proxy-opposite-meta",
|
|
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
|
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeLambdaServiceDefaults(true))
|
|
},
|
|
},
|
|
{
|
|
name: "lambda-terminating-gateway",
|
|
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
|
return proxycfg.TestConfigSnapshotTerminatingGatewayWithLambdaService(t)
|
|
},
|
|
},
|
|
{
|
|
name: "lambda-terminating-gateway-with-service-resolvers",
|
|
create: proxycfg.TestConfigSnapshotTerminatingGatewayWithLambdaServiceAndServiceResolvers,
|
|
},
|
|
{
|
|
name: "lua-outbound-applies-to-upstreams",
|
|
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
|
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeLuaServiceDefaults(false))
|
|
},
|
|
},
|
|
{
|
|
name: "lua-inbound-doesnt-applies-to-upstreams",
|
|
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
|
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeLuaServiceDefaults(true))
|
|
},
|
|
},
|
|
{
|
|
name: "lua-inbound-applies-to-inbound",
|
|
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.BuiltinLuaExtension,
|
|
Arguments: map[string]interface{}{
|
|
"ProxyType": "connect-proxy",
|
|
"Listener": "inbound",
|
|
"Script": `
|
|
function envoy_on_request(request_handle)
|
|
request_handle:headers():add("test", "test")
|
|
end`,
|
|
},
|
|
},
|
|
}
|
|
}, nil)
|
|
},
|
|
},
|
|
{
|
|
name: "lua-outbound-doesnt-apply-to-inbound",
|
|
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.BuiltinLuaExtension,
|
|
Arguments: map[string]interface{}{
|
|
"ProxyType": "connect-proxy",
|
|
"Listener": "outbound",
|
|
"Script": `
|
|
function envoy_on_request(request_handle)
|
|
request_handle:headers():add("test", "test")
|
|
end`,
|
|
},
|
|
},
|
|
}
|
|
}, nil)
|
|
},
|
|
},
|
|
{
|
|
name: "lua-connect-proxy-with-terminating-gateway-upstream",
|
|
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
|
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", nil, nil, makeLambdaServiceDefaults(false))
|
|
},
|
|
},
|
|
}
|
|
|
|
latestEnvoyVersion := proxysupport.EnvoyVersions[0]
|
|
for _, envoyVersion := range proxysupport.EnvoyVersions {
|
|
sf, err := determineSupportedProxyFeaturesFromString(envoyVersion)
|
|
require.NoError(t, err)
|
|
t.Run("envoy-"+envoyVersion, func(t *testing.T) {
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Sanity check default with no overrides first
|
|
snap := tt.create(t)
|
|
|
|
// We need to replace the TLS certs with deterministic ones to make golden
|
|
// files workable. Note we don't update these otherwise they'd change
|
|
// golden files for every test case and so not be any use!
|
|
setupTLSRootsAndLeaf(t, snap)
|
|
|
|
g := newResourceGenerator(testutil.Logger(t), nil, false)
|
|
g.ProxyFeatures = sf
|
|
|
|
res, err := g.allResourcesFromSnapshot(snap)
|
|
require.NoError(t, err)
|
|
|
|
indexedResources := indexResources(g.Logger, res)
|
|
cfgs := xdscommon.GetExtensionConfigurations(snap)
|
|
for _, extensions := range cfgs {
|
|
for _, ext := range extensions {
|
|
builtInExtension, ok := GetBuiltInExtension(ext)
|
|
require.True(t, ok)
|
|
err = builtInExtension.Validate(ext)
|
|
require.NoError(t, err)
|
|
indexedResources, err = builtInExtension.Extend(indexedResources, ext)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
entities := []struct {
|
|
name string
|
|
key string
|
|
sorter func([]proto.Message) func(int, int) bool
|
|
}{
|
|
{
|
|
name: "clusters",
|
|
key: xdscommon.ClusterType,
|
|
sorter: func(msgs []proto.Message) func(int, int) bool {
|
|
return func(i, j int) bool {
|
|
return msgs[i].(*envoy_cluster_v3.Cluster).Name < msgs[j].(*envoy_cluster_v3.Cluster).Name
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "listeners",
|
|
key: xdscommon.ListenerType,
|
|
sorter: func(msgs []proto.Message) func(int, int) bool {
|
|
return func(i, j int) bool {
|
|
return msgs[i].(*envoy_listener_v3.Listener).Name < msgs[j].(*envoy_listener_v3.Listener).Name
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "routes",
|
|
key: xdscommon.RouteType,
|
|
sorter: func(msgs []proto.Message) func(int, int) bool {
|
|
return func(i, j int) bool {
|
|
return msgs[i].(*envoy_route_v3.RouteConfiguration).Name < msgs[j].(*envoy_route_v3.RouteConfiguration).Name
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "endpoints",
|
|
key: xdscommon.EndpointType,
|
|
sorter: func(msgs []proto.Message) func(int, int) bool {
|
|
return func(i, j int) bool {
|
|
return msgs[i].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName < msgs[j].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, entity := range entities {
|
|
var msgs []proto.Message
|
|
for _, e := range indexedResources.Index[entity.key] {
|
|
msgs = append(msgs, e)
|
|
}
|
|
|
|
sort.Slice(msgs, entity.sorter(msgs))
|
|
r, err := createResponse(entity.key, "00000001", "00000001", msgs)
|
|
require.NoError(t, err)
|
|
|
|
t.Run(entity.name, func(t *testing.T) {
|
|
gotJSON := protoToJSON(t, r)
|
|
|
|
require.JSONEq(t, goldenEnvoy(t,
|
|
filepath.Join("builtin_extension", entity.name, tt.name),
|
|
envoyVersion, latestEnvoyVersion, gotJSON), gotJSON)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|