mirror of https://github.com/hashicorp/consul
514 lines
13 KiB
Go
514 lines
13 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package proxycfg
|
|
|
|
import (
|
|
"bytes"
|
|
"text/template"
|
|
|
|
"github.com/mitchellh/go-testing-interface"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/proto/private/pbpeering"
|
|
)
|
|
|
|
func TestConfigSnapshotPeering(t testing.T) *ConfigSnapshot {
|
|
return testConfigSnapshot(t, false, false, nil)
|
|
}
|
|
|
|
func TestConfigSnapshotPeeringWithEscapeOverrides(t testing.T) *ConfigSnapshot {
|
|
return testConfigSnapshot(t, true, false, nil)
|
|
}
|
|
|
|
func TestConfigSnapshotPeeringWithHTTP2(t testing.T, nsFn func(ns *structs.NodeService)) *ConfigSnapshot {
|
|
return testConfigSnapshot(t, false, true, nsFn)
|
|
}
|
|
|
|
func testConfigSnapshot(t testing.T, escapeOverride bool, useHTTP2 bool, nsFn func(ns *structs.NodeService)) *ConfigSnapshot {
|
|
var (
|
|
paymentsUpstream = structs.Upstream{
|
|
DestinationName: "payments",
|
|
DestinationPeer: "cloud",
|
|
LocalBindPort: 9090,
|
|
}
|
|
paymentsUID = NewUpstreamID(&paymentsUpstream)
|
|
|
|
refundsUpstream = structs.Upstream{
|
|
DestinationName: "refunds",
|
|
DestinationPeer: "cloud",
|
|
LocalBindPort: 9090,
|
|
}
|
|
refundsUID = NewUpstreamID(&refundsUpstream)
|
|
)
|
|
|
|
protocol := "tcp"
|
|
if useHTTP2 {
|
|
protocol = "http2"
|
|
}
|
|
|
|
const peerTrustDomain = "1c053652-8512-4373-90cf-5a7f6263a994.consul"
|
|
|
|
return TestConfigSnapshot(t, func(ns *structs.NodeService) {
|
|
ns.Proxy.Upstreams = structs.Upstreams{
|
|
paymentsUpstream,
|
|
refundsUpstream,
|
|
}
|
|
|
|
if escapeOverride {
|
|
if ns.Proxy.Upstreams[0].Config == nil {
|
|
ns.Proxy.Upstreams[0].Config = map[string]interface{}{}
|
|
}
|
|
|
|
uid := NewUpstreamID(&ns.Proxy.Upstreams[0])
|
|
|
|
ns.Proxy.Upstreams[0].Config["envoy_listener_json"] =
|
|
customListenerJSON(t, customListenerJSONOptions{
|
|
Name: uid.EnvoyID() + ":custom-upstream",
|
|
})
|
|
ns.Proxy.Upstreams[0].Config["envoy_cluster_json"] =
|
|
customClusterJSON(t, customClusterJSONOptions{
|
|
Name: uid.EnvoyID() + ":custom-upstream",
|
|
})
|
|
}
|
|
if nsFn != nil {
|
|
nsFn(ns)
|
|
}
|
|
}, []UpdateEvent{
|
|
{
|
|
CorrelationID: peerTrustBundleIDPrefix + "cloud",
|
|
Result: &pbpeering.TrustBundleReadResponse{
|
|
Bundle: TestPeerTrustBundles(t).Bundles[0],
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: upstreamPeerWatchIDPrefix + paymentsUID.String(),
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
Nodes: []structs.CheckServiceNode{
|
|
{
|
|
Node: &structs.Node{
|
|
Address: "85.252.102.31",
|
|
Datacenter: "cloud-dc",
|
|
},
|
|
Service: &structs.NodeService{
|
|
Service: "payments-sidecar-proxy",
|
|
Kind: structs.ServiceKindConnectProxy,
|
|
Port: 443,
|
|
TaggedAddresses: map[string]structs.ServiceAddress{
|
|
structs.TaggedAddressLAN: {
|
|
Address: "85.252.102.31",
|
|
Port: 443,
|
|
},
|
|
structs.TaggedAddressWAN: {
|
|
Address: "123.us-east-1.elb.notaws.com",
|
|
Port: 8443,
|
|
},
|
|
},
|
|
Connect: structs.ServiceConnect{
|
|
PeerMeta: &structs.PeeringServiceMeta{
|
|
SNI: []string{
|
|
"payments.default.default.cloud.external." + peerTrustDomain,
|
|
},
|
|
SpiffeID: []string{
|
|
"spiffe://" + peerTrustDomain + "/ns/default/dc/cloud-dc/svc/payments",
|
|
},
|
|
Protocol: protocol,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: upstreamPeerWatchIDPrefix + refundsUID.String(),
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
Nodes: []structs.CheckServiceNode{
|
|
{
|
|
Node: &structs.Node{
|
|
Address: "106.96.90.233",
|
|
Datacenter: "cloud-dc",
|
|
},
|
|
Service: &structs.NodeService{
|
|
Service: "refunds-sidecar-proxy",
|
|
Kind: structs.ServiceKindConnectProxy,
|
|
Port: 443,
|
|
Connect: structs.ServiceConnect{
|
|
PeerMeta: &structs.PeeringServiceMeta{
|
|
SNI: []string{
|
|
"refunds.default.default.cloud.external." + peerTrustDomain,
|
|
},
|
|
SpiffeID: []string{
|
|
"spiffe://" + peerTrustDomain + "/ns/default/dc/cloud-dc/svc/refunds",
|
|
},
|
|
Protocol: protocol,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestConfigSnapshotPeeringTProxy(t testing.T) *ConfigSnapshot {
|
|
// Test two explicitly defined upstreams api-a and noEndpoints
|
|
// as well as one implicitly inferred upstream db.
|
|
|
|
var (
|
|
noEndpointsUpstream = structs.Upstream{
|
|
DestinationName: "no-endpoints",
|
|
DestinationPeer: "peer-a",
|
|
LocalBindPort: 1234,
|
|
}
|
|
noEndpoints = structs.PeeredServiceName{
|
|
ServiceName: structs.NewServiceName("no-endpoints", nil),
|
|
Peer: "peer-a",
|
|
}
|
|
|
|
apiAUpstream = structs.Upstream{
|
|
DestinationName: "api-a",
|
|
DestinationPeer: "peer-a",
|
|
LocalBindPort: 9090,
|
|
}
|
|
apiA = structs.PeeredServiceName{
|
|
ServiceName: structs.NewServiceName("api-a", nil),
|
|
Peer: "peer-a",
|
|
}
|
|
|
|
db = structs.PeeredServiceName{
|
|
ServiceName: structs.NewServiceName("db", nil),
|
|
Peer: "peer-a",
|
|
}
|
|
)
|
|
|
|
const peerTrustDomain = "1c053652-8512-4373-90cf-5a7f6263a994.consul"
|
|
|
|
return TestConfigSnapshot(t, func(ns *structs.NodeService) {
|
|
ns.Proxy.Mode = structs.ProxyModeTransparent
|
|
ns.Proxy.Upstreams = []structs.Upstream{
|
|
noEndpointsUpstream,
|
|
apiAUpstream,
|
|
}
|
|
}, []UpdateEvent{
|
|
{
|
|
CorrelationID: meshConfigEntryID,
|
|
Result: &structs.ConfigEntryResponse{
|
|
Entry: nil,
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: peeredUpstreamsID,
|
|
Result: &structs.IndexedPeeredServiceList{
|
|
Services: []structs.PeeredServiceName{
|
|
apiA,
|
|
noEndpoints,
|
|
db, // implicitly added here
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: peerTrustBundleIDPrefix + "peer-a",
|
|
Result: &pbpeering.TrustBundleReadResponse{
|
|
Bundle: TestPeerTrustBundles(t).Bundles[0],
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: upstreamPeerWatchIDPrefix + NewUpstreamID(&noEndpointsUpstream).String(),
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
Nodes: []structs.CheckServiceNode{},
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: upstreamPeerWatchIDPrefix + NewUpstreamID(&apiAUpstream).String(),
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
Nodes: structs.CheckServiceNodes{
|
|
{
|
|
Node: &structs.Node{
|
|
Node: "node1",
|
|
Address: "127.0.0.1",
|
|
PeerName: "peer-a",
|
|
},
|
|
Service: &structs.NodeService{
|
|
ID: "api-a-1",
|
|
Service: "api-a",
|
|
PeerName: "peer-a",
|
|
Address: "1.2.3.4",
|
|
TaggedAddresses: map[string]structs.ServiceAddress{
|
|
"virtual": {Address: "10.0.0.1"},
|
|
structs.TaggedAddressVirtualIP: {Address: "240.0.0.1"},
|
|
},
|
|
Connect: structs.ServiceConnect{
|
|
PeerMeta: &structs.PeeringServiceMeta{
|
|
SNI: []string{
|
|
"api-a.default.default.cloud.external." + peerTrustDomain,
|
|
},
|
|
SpiffeID: []string{
|
|
"spiffe://" + peerTrustDomain + "/ns/default/dc/cloud-dc/svc/api-a",
|
|
},
|
|
Protocol: "tcp",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: upstreamPeerWatchIDPrefix + NewUpstreamIDFromPeeredServiceName(db).String(),
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
Nodes: structs.CheckServiceNodes{
|
|
{
|
|
Node: &structs.Node{
|
|
Node: "node1",
|
|
Address: "127.0.0.1",
|
|
PeerName: "peer-a",
|
|
},
|
|
Service: &structs.NodeService{
|
|
ID: "db-1",
|
|
Service: "db",
|
|
PeerName: "peer-a",
|
|
Address: "2.3.4.5", // Expect no endpoint or listener for this address
|
|
TaggedAddresses: map[string]structs.ServiceAddress{
|
|
"virtual": {Address: "10.0.0.2"},
|
|
structs.TaggedAddressVirtualIP: {Address: "240.0.0.2"},
|
|
},
|
|
Connect: structs.ServiceConnect{
|
|
PeerMeta: &structs.PeeringServiceMeta{
|
|
SNI: []string{
|
|
"db.default.default.cloud.external." + peerTrustDomain,
|
|
},
|
|
SpiffeID: []string{
|
|
"spiffe://" + peerTrustDomain + "/ns/default/dc/cloud-dc/svc/db",
|
|
},
|
|
Protocol: "tcp",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestConfigSnapshotPeeringLocalMeshGateway(t testing.T) *ConfigSnapshot {
|
|
var (
|
|
paymentsUpstream = structs.Upstream{
|
|
DestinationName: "payments",
|
|
DestinationPeer: "cloud",
|
|
LocalBindPort: 9090,
|
|
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeRemote},
|
|
}
|
|
paymentsUID = NewUpstreamID(&paymentsUpstream)
|
|
|
|
refundsUpstream = structs.Upstream{
|
|
DestinationName: "refunds",
|
|
DestinationPeer: "cloud",
|
|
LocalBindPort: 9090,
|
|
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeLocal},
|
|
}
|
|
refundsUID = NewUpstreamID(&refundsUpstream)
|
|
)
|
|
|
|
const peerTrustDomain = "1c053652-8512-4373-90cf-5a7f6263a994.consul"
|
|
|
|
return TestConfigSnapshot(t, func(ns *structs.NodeService) {
|
|
ns.Proxy.Upstreams = structs.Upstreams{
|
|
paymentsUpstream,
|
|
refundsUpstream,
|
|
}
|
|
}, []UpdateEvent{
|
|
{
|
|
CorrelationID: peerTrustBundleIDPrefix + "cloud",
|
|
Result: &pbpeering.TrustBundleReadResponse{
|
|
Bundle: TestPeerTrustBundles(t).Bundles[0],
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: upstreamPeerWatchIDPrefix + paymentsUID.String(),
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
Nodes: []structs.CheckServiceNode{
|
|
{
|
|
Node: &structs.Node{
|
|
Address: "85.252.102.31",
|
|
Datacenter: "cloud-dc",
|
|
},
|
|
Service: &structs.NodeService{
|
|
Service: "payments-sidecar-proxy",
|
|
Kind: structs.ServiceKindConnectProxy,
|
|
Port: 443,
|
|
TaggedAddresses: map[string]structs.ServiceAddress{
|
|
structs.TaggedAddressLAN: {
|
|
Address: "85.252.102.31",
|
|
Port: 443,
|
|
},
|
|
structs.TaggedAddressWAN: {
|
|
Address: "123.us-east-1.elb.notaws.com",
|
|
Port: 8443,
|
|
},
|
|
},
|
|
Connect: structs.ServiceConnect{
|
|
PeerMeta: &structs.PeeringServiceMeta{
|
|
SNI: []string{
|
|
"payments.default.default.cloud.external." + peerTrustDomain,
|
|
},
|
|
SpiffeID: []string{
|
|
"spiffe://" + peerTrustDomain + "/ns/default/dc/cloud-dc/svc/payments",
|
|
},
|
|
Protocol: "tcp",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: upstreamPeerWatchIDPrefix + refundsUID.String(),
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
Nodes: []structs.CheckServiceNode{
|
|
{
|
|
Node: &structs.Node{
|
|
Address: "106.96.90.233",
|
|
Datacenter: "cloud-dc",
|
|
},
|
|
Service: &structs.NodeService{
|
|
Service: "refunds-sidecar-proxy",
|
|
Kind: structs.ServiceKindConnectProxy,
|
|
Port: 443,
|
|
Connect: structs.ServiceConnect{
|
|
PeerMeta: &structs.PeeringServiceMeta{
|
|
SNI: []string{
|
|
"refunds.default.default.cloud.external." + peerTrustDomain,
|
|
},
|
|
SpiffeID: []string{
|
|
"spiffe://" + peerTrustDomain + "/ns/default/dc/cloud-dc/svc/refunds",
|
|
},
|
|
Protocol: "tcp",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CorrelationID: "mesh-gateway:dc1",
|
|
Result: &structs.IndexedCheckServiceNodes{
|
|
Nodes: structs.CheckServiceNodes{
|
|
structs.CheckServiceNode{
|
|
Node: &structs.Node{
|
|
ID: "mesh-gateway",
|
|
Node: "mesh-gateway",
|
|
Address: "10.0.0.1",
|
|
Datacenter: "dc1",
|
|
},
|
|
Service: &structs.NodeService{
|
|
Kind: structs.ServiceKindMeshGateway,
|
|
Service: "mesh-gateway",
|
|
Port: 1234,
|
|
TaggedAddresses: map[string]structs.ServiceAddress{
|
|
structs.TaggedAddressWAN: {Address: "172.100.0.14", Port: 8080},
|
|
},
|
|
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
var (
|
|
customListenerJSONTemplate = template.Must(template.New("").Parse(customListenerJSONTpl))
|
|
)
|
|
|
|
func customListenerJSON(t testing.T, opts customListenerJSONOptions) string {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
require.NoError(t, customListenerJSONTemplate.Execute(&buf, opts))
|
|
return buf.String()
|
|
}
|
|
|
|
type customListenerJSONOptions struct {
|
|
Name string
|
|
TLSContext string
|
|
}
|
|
|
|
const customListenerJSONTpl = `{
|
|
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
|
"name": "{{ .Name }}",
|
|
"address": {
|
|
"socketAddress": {
|
|
"address": "11.11.11.11",
|
|
"portValue": 11111
|
|
}
|
|
},
|
|
"filterChains": [
|
|
{
|
|
{{ if .TLSContext -}}
|
|
"transport_socket": {
|
|
"name": "tls",
|
|
"typed_config": {
|
|
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
|
|
{{ .TLSContext }}
|
|
}
|
|
},
|
|
{{- end }}
|
|
"filters": [
|
|
{
|
|
"name": "envoy.filters.network.tcp_proxy",
|
|
"typedConfig": {
|
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
|
"cluster": "random-cluster",
|
|
"statPrefix": "foo-stats"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`
|
|
|
|
type customClusterJSONOptions struct {
|
|
Name string
|
|
TLSContext string
|
|
}
|
|
|
|
var customClusterJSONTpl = `{
|
|
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
|
"name": "{{ .Name }}",
|
|
"connectTimeout": "15s",
|
|
"loadAssignment": {
|
|
"clusterName": "{{ .Name }}",
|
|
"endpoints": [
|
|
{
|
|
"lbEndpoints": [
|
|
{
|
|
"endpoint": {
|
|
"address": {
|
|
"socketAddress": {
|
|
"address": "1.2.3.4",
|
|
"portValue": 8443
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}`
|
|
|
|
var customClusterJSONTemplate = template.Must(template.New("").Parse(customClusterJSONTpl))
|
|
|
|
func customClusterJSON(t testing.T, opts customClusterJSONOptions) string {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
err := customClusterJSONTemplate.Execute(&buf, opts)
|
|
require.NoError(t, err)
|
|
return buf.String()
|
|
}
|