consul/agent/xds/builtin_extension_oss_test.go
Nitya Dhanushkodi 8728a4496c
troubleshoot: service to service validation (#16096)
* 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>
2023-01-27 11:43:16 -08:00

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)
})
}
})
}
})
}
}