1package xds
2
3import (
4	"bytes"
5	"path/filepath"
6	"sort"
7	"testing"
8	"text/template"
9	"time"
10
11	envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
12
13	testinf "github.com/mitchellh/go-testing-interface"
14	"github.com/stretchr/testify/require"
15
16	"github.com/hashicorp/consul/agent/connect"
17	"github.com/hashicorp/consul/agent/consul/discoverychain"
18	"github.com/hashicorp/consul/agent/proxycfg"
19	"github.com/hashicorp/consul/agent/structs"
20	"github.com/hashicorp/consul/agent/xds/proxysupport"
21	"github.com/hashicorp/consul/lib/stringslice"
22	"github.com/hashicorp/consul/sdk/testutil"
23	"github.com/hashicorp/consul/types"
24)
25
26func TestListenersFromSnapshot(t *testing.T) {
27	if testing.Short() {
28		t.Skip("too slow for testing.Short")
29	}
30
31	tests := []struct {
32		name   string
33		create func(t testinf.T) *proxycfg.ConfigSnapshot
34		// Setup is called before the test starts. It is passed the snapshot from
35		// TestConfigSnapshot and is allowed to modify it in any way to setup the
36		// test input.
37		setup              func(snap *proxycfg.ConfigSnapshot)
38		overrideGoldenName string
39		generatorSetup     func(*ResourceGenerator)
40	}{
41		{
42			name:   "defaults",
43			create: proxycfg.TestConfigSnapshot,
44			setup:  nil, // Default snapshot
45		},
46		{
47			name:   "listener-bind-address",
48			create: proxycfg.TestConfigSnapshot,
49			setup: func(snap *proxycfg.ConfigSnapshot) {
50				snap.Proxy.Config["bind_address"] = "127.0.0.2"
51			},
52		},
53		{
54			name:   "listener-bind-port",
55			create: proxycfg.TestConfigSnapshot,
56			setup: func(snap *proxycfg.ConfigSnapshot) {
57				snap.Proxy.Config["bind_port"] = 8888
58			},
59		},
60		{
61			name:   "listener-bind-address-port",
62			create: proxycfg.TestConfigSnapshot,
63			setup: func(snap *proxycfg.ConfigSnapshot) {
64				snap.Proxy.Config["bind_address"] = "127.0.0.2"
65				snap.Proxy.Config["bind_port"] = 8888
66			},
67		},
68		{
69			name:   "listener-unix-domain-socket",
70			create: proxycfg.TestConfigSnapshot,
71			setup: func(snap *proxycfg.ConfigSnapshot) {
72				snap.Proxy.Upstreams[0].LocalBindAddress = ""
73				snap.Proxy.Upstreams[0].LocalBindPort = 0
74				snap.Proxy.Upstreams[0].LocalBindSocketPath = "/tmp/service-mesh/client-1/grpc-employee-server"
75				snap.Proxy.Upstreams[0].LocalBindSocketMode = "0640"
76			},
77		},
78		{
79			name:   "http-public-listener",
80			create: proxycfg.TestConfigSnapshot,
81			setup: func(snap *proxycfg.ConfigSnapshot) {
82				snap.Proxy.Config["protocol"] = "http"
83			},
84		},
85		{
86			name:   "http-listener-with-timeouts",
87			create: proxycfg.TestConfigSnapshot,
88			setup: func(snap *proxycfg.ConfigSnapshot) {
89				snap.Proxy.Config["protocol"] = "http"
90				snap.Proxy.Config["local_connect_timeout_ms"] = 1234
91				snap.Proxy.Config["local_request_timeout_ms"] = 2345
92			},
93		},
94		{
95			name:   "http-upstream",
96			create: proxycfg.TestConfigSnapshot,
97			setup: func(snap *proxycfg.ConfigSnapshot) {
98				snap.Proxy.Upstreams[0].Config["protocol"] = "http"
99			},
100		},
101		{
102			name:   "custom-public-listener",
103			create: proxycfg.TestConfigSnapshot,
104			setup: func(snap *proxycfg.ConfigSnapshot) {
105				snap.Proxy.Config["envoy_public_listener_json"] =
106					customListenerJSON(t, customListenerJSONOptions{
107						Name: "custom-public-listen",
108					})
109			},
110		},
111		{
112			name:   "custom-public-listener-http",
113			create: proxycfg.TestConfigSnapshot,
114			setup: func(snap *proxycfg.ConfigSnapshot) {
115				snap.Proxy.Config["protocol"] = "http"
116				snap.Proxy.Config["envoy_public_listener_json"] =
117					customHTTPListenerJSON(t, customHTTPListenerJSONOptions{
118						Name: "custom-public-listen",
119					})
120			},
121		},
122		{
123			name:   "custom-public-listener-http-2",
124			create: proxycfg.TestConfigSnapshot,
125			setup: func(snap *proxycfg.ConfigSnapshot) {
126				snap.Proxy.Config["protocol"] = "http"
127				snap.Proxy.Config["envoy_public_listener_json"] =
128					customHTTPListenerJSON(t, customHTTPListenerJSONOptions{
129						Name:                      "custom-public-listen",
130						HTTPConnectionManagerName: httpConnectionManagerNewName,
131					})
132			},
133		},
134		{
135			name:   "custom-public-listener-http-missing",
136			create: proxycfg.TestConfigSnapshot,
137			setup: func(snap *proxycfg.ConfigSnapshot) {
138				snap.Proxy.Config["protocol"] = "http"
139				snap.Proxy.Config["envoy_public_listener_json"] =
140					customListenerJSON(t, customListenerJSONOptions{
141						Name: "custom-public-listen",
142					})
143			},
144		},
145		{
146			name:               "custom-public-listener-ignores-tls",
147			create:             proxycfg.TestConfigSnapshot,
148			overrideGoldenName: "custom-public-listener", // should be the same
149			setup: func(snap *proxycfg.ConfigSnapshot) {
150				snap.Proxy.Config["envoy_public_listener_json"] =
151					customListenerJSON(t, customListenerJSONOptions{
152						Name: "custom-public-listen",
153						// Attempt to override the TLS context should be ignored
154						TLSContext: `"allowRenegotiation": false`,
155					})
156			},
157		},
158		{
159			name:   "custom-upstream",
160			create: proxycfg.TestConfigSnapshot,
161			setup: func(snap *proxycfg.ConfigSnapshot) {
162				snap.Proxy.Upstreams[0].Config["envoy_listener_json"] =
163					customListenerJSON(t, customListenerJSONOptions{
164						Name: "custom-upstream",
165					})
166			},
167		},
168		{
169			name:   "custom-upstream-ignored-with-disco-chain",
170			create: proxycfg.TestConfigSnapshotDiscoveryChainWithFailover,
171			setup: func(snap *proxycfg.ConfigSnapshot) {
172				snap.Proxy.Upstreams[0].Config["envoy_listener_json"] =
173					customListenerJSON(t, customListenerJSONOptions{
174						Name: "custom-upstream",
175					})
176			},
177		},
178		{
179			name:   "splitter-with-resolver-redirect",
180			create: proxycfg.TestConfigSnapshotDiscoveryChain_SplitterWithResolverRedirectMultiDC,
181			setup:  nil,
182		},
183		{
184			name:   "connect-proxy-with-tcp-chain",
185			create: proxycfg.TestConfigSnapshotDiscoveryChain,
186			setup:  nil,
187		},
188		{
189			name: "connect-proxy-with-http-chain",
190			create: func(t testinf.T) *proxycfg.ConfigSnapshot {
191				return proxycfg.TestConfigSnapshotDiscoveryChainWithEntries(t,
192					&structs.ProxyConfigEntry{
193						Kind: structs.ProxyDefaults,
194						Name: structs.ProxyConfigGlobal,
195						Config: map[string]interface{}{
196							"protocol": "http",
197						},
198					},
199				)
200			},
201			setup: nil,
202		},
203		{
204			name: "connect-proxy-with-http2-chain",
205			create: func(t testinf.T) *proxycfg.ConfigSnapshot {
206				return proxycfg.TestConfigSnapshotDiscoveryChainWithEntries(t,
207					&structs.ProxyConfigEntry{
208						Kind: structs.ProxyDefaults,
209						Name: structs.ProxyConfigGlobal,
210						Config: map[string]interface{}{
211							"protocol": "http2",
212						},
213					},
214				)
215			},
216			setup: nil,
217		},
218		{
219			name: "connect-proxy-with-grpc-chain",
220			create: func(t testinf.T) *proxycfg.ConfigSnapshot {
221				return proxycfg.TestConfigSnapshotDiscoveryChainWithEntries(t,
222					&structs.ProxyConfigEntry{
223						Kind: structs.ProxyDefaults,
224						Name: structs.ProxyConfigGlobal,
225						Config: map[string]interface{}{
226							"protocol": "grpc",
227						},
228					},
229				)
230			},
231			setup: nil,
232		},
233		{
234			name:   "connect-proxy-with-chain-external-sni",
235			create: proxycfg.TestConfigSnapshotDiscoveryChainExternalSNI,
236			setup:  nil,
237		},
238		{
239			name:   "connect-proxy-with-chain-and-overrides",
240			create: proxycfg.TestConfigSnapshotDiscoveryChainWithOverrides,
241			setup:  nil,
242		},
243		{
244			name:   "connect-proxy-with-tcp-chain-failover-through-remote-gateway",
245			create: proxycfg.TestConfigSnapshotDiscoveryChainWithFailoverThroughRemoteGateway,
246			setup:  nil,
247		},
248		{
249			name:   "connect-proxy-with-tcp-chain-failover-through-local-gateway",
250			create: proxycfg.TestConfigSnapshotDiscoveryChainWithFailoverThroughLocalGateway,
251			setup:  nil,
252		},
253		{
254			name:   "expose-paths-local-app-paths",
255			create: proxycfg.TestConfigSnapshotExposeConfig,
256		},
257		{
258			name:   "expose-paths-new-cluster-http2",
259			create: proxycfg.TestConfigSnapshotExposeConfig,
260			setup: func(snap *proxycfg.ConfigSnapshot) {
261				snap.Proxy.Expose.Paths[1] = structs.ExposePath{
262					LocalPathPort: 9090,
263					Path:          "/grpc.health.v1.Health/Check",
264					ListenerPort:  21501,
265					Protocol:      "http2",
266				}
267			},
268		},
269		{
270			// NOTE: if IPv6 is not supported in the kernel per
271			// kernelSupportsIPv6() then this test will fail because the golden
272			// files were generated assuming ipv6 support was present
273			name:   "expose-checks",
274			create: proxycfg.TestConfigSnapshotExposeConfig,
275			setup: func(snap *proxycfg.ConfigSnapshot) {
276				snap.Proxy.Expose = structs.ExposeConfig{
277					Checks: true,
278				}
279			},
280			generatorSetup: func(s *ResourceGenerator) {
281				s.CfgFetcher = configFetcherFunc(func() string {
282					return "192.0.2.1"
283				})
284
285				s.CheckFetcher = httpCheckFetcherFunc(func(sid structs.ServiceID) []structs.CheckType {
286					if sid != structs.NewServiceID("web", nil) {
287						return nil
288					}
289					return []structs.CheckType{{
290						CheckID:   types.CheckID("http"),
291						Name:      "http",
292						HTTP:      "http://127.0.0.1:8181/debug",
293						ProxyHTTP: "http://:21500/debug",
294						Method:    "GET",
295						Interval:  10 * time.Second,
296						Timeout:   1 * time.Second,
297					}}
298				})
299			},
300		},
301		{
302			name:   "mesh-gateway",
303			create: proxycfg.TestConfigSnapshotMeshGateway,
304		},
305		{
306			name:   "mesh-gateway-using-federation-states",
307			create: proxycfg.TestConfigSnapshotMeshGatewayUsingFederationStates,
308		},
309		{
310			name:   "mesh-gateway-no-services",
311			create: proxycfg.TestConfigSnapshotMeshGatewayNoServices,
312		},
313		{
314			name:   "mesh-gateway-tagged-addresses",
315			create: proxycfg.TestConfigSnapshotMeshGateway,
316			setup: func(snap *proxycfg.ConfigSnapshot) {
317				snap.Proxy.Config = map[string]interface{}{
318					"envoy_mesh_gateway_no_default_bind":       true,
319					"envoy_mesh_gateway_bind_tagged_addresses": true,
320				}
321			},
322		},
323		{
324			name:   "mesh-gateway-custom-addresses",
325			create: proxycfg.TestConfigSnapshotMeshGateway,
326			setup: func(snap *proxycfg.ConfigSnapshot) {
327				snap.Proxy.Config = map[string]interface{}{
328					"envoy_mesh_gateway_bind_addresses": map[string]structs.ServiceAddress{
329						"foo": {
330							Address: "198.17.2.3",
331							Port:    8080,
332						},
333						"bar": {
334							Address: "2001:db8::ff",
335							Port:    9999,
336						},
337						"baz": {
338							Address: "127.0.0.1",
339							Port:    8765,
340						},
341					},
342				}
343			},
344		},
345		{
346			name:   "ingress-gateway",
347			create: proxycfg.TestConfigSnapshotIngressGateway,
348			setup:  nil,
349		},
350		{
351			name:   "ingress-gateway-bind-addrs",
352			create: proxycfg.TestConfigSnapshotIngressGateway,
353			setup: func(snap *proxycfg.ConfigSnapshot) {
354				snap.TaggedAddresses = map[string]structs.ServiceAddress{
355					"lan": {Address: "10.0.0.1"},
356					"wan": {Address: "172.16.0.1"},
357				}
358				snap.Proxy.Config = map[string]interface{}{
359					"envoy_gateway_no_default_bind":       true,
360					"envoy_gateway_bind_tagged_addresses": true,
361					"envoy_gateway_bind_addresses": map[string]structs.ServiceAddress{
362						"foo": {Address: "8.8.8.8"},
363					},
364				}
365			},
366		},
367		{
368			name:   "ingress-gateway-no-services",
369			create: proxycfg.TestConfigSnapshotIngressGatewayNoServices,
370			setup:  nil,
371		},
372		{
373			name:   "ingress-with-chain-external-sni",
374			create: proxycfg.TestConfigSnapshotIngressExternalSNI,
375			setup:  nil,
376		},
377		{
378			name:   "ingress-with-chain-and-overrides",
379			create: proxycfg.TestConfigSnapshotIngressWithOverrides,
380			setup:  nil,
381		},
382		{
383			name:   "ingress-with-tcp-chain-failover-through-remote-gateway",
384			create: proxycfg.TestConfigSnapshotIngressWithFailoverThroughRemoteGateway,
385			setup:  nil,
386		},
387		{
388			name:   "ingress-with-tcp-chain-failover-through-local-gateway",
389			create: proxycfg.TestConfigSnapshotIngressWithFailoverThroughLocalGateway,
390			setup:  nil,
391		},
392		{
393			name:   "ingress-splitter-with-resolver-redirect",
394			create: proxycfg.TestConfigSnapshotIngress_SplitterWithResolverRedirectMultiDC,
395			setup:  nil,
396		},
397		{
398			name:   "terminating-gateway",
399			create: proxycfg.TestConfigSnapshotTerminatingGateway,
400			setup:  nil,
401		},
402		{
403			name:   "terminating-gateway-no-services",
404			create: proxycfg.TestConfigSnapshotTerminatingGatewayNoServices,
405			setup:  nil,
406		},
407		{
408			name:   "terminating-gateway-custom-and-tagged-addresses",
409			create: proxycfg.TestConfigSnapshotTerminatingGateway,
410			setup: func(snap *proxycfg.ConfigSnapshot) {
411				snap.Proxy.Config = map[string]interface{}{
412					"envoy_gateway_no_default_bind":       true,
413					"envoy_gateway_bind_tagged_addresses": true,
414					"envoy_gateway_bind_addresses": map[string]structs.ServiceAddress{
415						// This bind address should not get a listener due to deduplication and it sorts to the end
416						"z-duplicate-of-tagged-wan-addr": {
417							Address: "198.18.0.1",
418							Port:    443,
419						},
420						"foo": {
421							Address: "198.17.2.3",
422							Port:    8080,
423						},
424					},
425				}
426			},
427		},
428		{
429			name:   "terminating-gateway-service-subsets",
430			create: proxycfg.TestConfigSnapshotTerminatingGateway,
431			setup: func(snap *proxycfg.ConfigSnapshot) {
432				snap.TerminatingGateway.ServiceResolvers = map[structs.ServiceName]*structs.ServiceResolverConfigEntry{
433					structs.NewServiceName("web", nil): {
434						Kind: structs.ServiceResolver,
435						Name: "web",
436						Subsets: map[string]structs.ServiceResolverSubset{
437							"v1": {
438								Filter: "Service.Meta.version == 1",
439							},
440							"v2": {
441								Filter:      "Service.Meta.version == 2",
442								OnlyPassing: true,
443							},
444						},
445					},
446				}
447				snap.TerminatingGateway.ServiceConfigs[structs.NewServiceName("web", nil)] = &structs.ServiceConfigResponse{
448					ProxyConfig: map[string]interface{}{"protocol": "http"},
449				}
450			},
451		},
452		{
453			name:   "ingress-http-multiple-services",
454			create: proxycfg.TestConfigSnapshotIngress_HTTPMultipleServices,
455			setup: func(snap *proxycfg.ConfigSnapshot) {
456				snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{
457					{Protocol: "http", Port: 8080}: {
458						{
459							DestinationName: "foo",
460							LocalBindPort:   8080,
461						},
462						{
463							DestinationName: "bar",
464							LocalBindPort:   8080,
465						},
466					},
467					{Protocol: "http", Port: 443}: {
468						{
469							DestinationName: "baz",
470							LocalBindPort:   443,
471						},
472						{
473							DestinationName: "qux",
474							LocalBindPort:   443,
475						},
476					},
477				}
478			},
479		},
480		{
481			name:   "terminating-gateway-no-api-cert",
482			create: proxycfg.TestConfigSnapshotTerminatingGateway,
483			setup: func(snap *proxycfg.ConfigSnapshot) {
484				snap.TerminatingGateway.ServiceLeaves[structs.NewServiceName("api", nil)] = nil
485			},
486		},
487		{
488			name:   "ingress-with-tls-listener",
489			create: proxycfg.TestConfigSnapshotIngressWithTLSListener,
490			setup:  nil,
491		},
492		{
493			name:   "transparent-proxy",
494			create: proxycfg.TestConfigSnapshot,
495			setup: func(snap *proxycfg.ConfigSnapshot) {
496				snap.Proxy.Mode = structs.ProxyModeTransparent
497
498				snap.ConnectProxy.MeshConfigSet = true
499
500				// DiscoveryChain without an UpstreamConfig should yield a filter chain when in transparent proxy mode
501				snap.ConnectProxy.DiscoveryChain["google"] = discoverychain.TestCompileConfigEntries(
502					t, "google", "default", "dc1",
503					connect.TestClusterID+".consul", "dc1", nil)
504				snap.ConnectProxy.WatchedUpstreamEndpoints["google"] = map[string]structs.CheckServiceNodes{
505					"google.default.dc1": {
506						structs.CheckServiceNode{
507							Node: &structs.Node{
508								Address:    "8.8.8.8",
509								Datacenter: "dc1",
510							},
511							Service: &structs.NodeService{
512								Service: "google",
513								Address: "9.9.9.9",
514								Port:    9090,
515								TaggedAddresses: map[string]structs.ServiceAddress{
516									"virtual": {Address: "10.0.0.1"},
517								},
518							},
519						},
520					},
521					// Other targets of the discovery chain should be ignored.
522					// We only match on the upstream's virtual IP, not the IPs of other targets.
523					"google-v2.default.dc1": {
524						structs.CheckServiceNode{
525							Node: &structs.Node{
526								Address:    "7.7.7.7",
527								Datacenter: "dc1",
528							},
529							Service: &structs.NodeService{
530								Service: "google-v2",
531								TaggedAddresses: map[string]structs.ServiceAddress{
532									"virtual": {Address: "10.10.10.10"},
533								},
534							},
535						},
536					},
537				}
538
539				// DiscoveryChains without endpoints do not get a filter chain because there are no addresses to match on.
540				snap.ConnectProxy.DiscoveryChain["no-endpoints"] = discoverychain.TestCompileConfigEntries(
541					t, "no-endpoints", "default", "dc1",
542					connect.TestClusterID+".consul", "dc1", nil)
543			},
544		},
545		{
546			name:   "transparent-proxy-catalog-destinations-only",
547			create: proxycfg.TestConfigSnapshot,
548			setup: func(snap *proxycfg.ConfigSnapshot) {
549				snap.Proxy.Mode = structs.ProxyModeTransparent
550
551				snap.ConnectProxy.MeshConfigSet = true
552				snap.ConnectProxy.MeshConfig = &structs.MeshConfigEntry{
553					TransparentProxy: structs.TransparentProxyMeshConfig{
554						MeshDestinationsOnly: true,
555					},
556				}
557
558				// DiscoveryChain without an UpstreamConfig should yield a filter chain when in transparent proxy mode
559				snap.ConnectProxy.DiscoveryChain["google"] = discoverychain.TestCompileConfigEntries(
560					t, "google", "default", "dc1",
561					connect.TestClusterID+".consul", "dc1", nil)
562				snap.ConnectProxy.WatchedUpstreamEndpoints["google"] = map[string]structs.CheckServiceNodes{
563					"google.default.dc1": {
564						structs.CheckServiceNode{
565							Node: &structs.Node{
566								Address:    "8.8.8.8",
567								Datacenter: "dc1",
568							},
569							Service: &structs.NodeService{
570								Service: "google",
571								Address: "9.9.9.9",
572								Port:    9090,
573								TaggedAddresses: map[string]structs.ServiceAddress{
574									"virtual": {Address: "10.0.0.1"},
575								},
576							},
577						},
578					},
579				}
580
581				// DiscoveryChains without endpoints do not get a filter chain because there are no addresses to match on.
582				snap.ConnectProxy.DiscoveryChain["no-endpoints"] = discoverychain.TestCompileConfigEntries(
583					t, "no-endpoints", "default", "dc1",
584					connect.TestClusterID+".consul", "dc1", nil)
585			},
586		},
587		{
588			name:   "transparent-proxy-dial-instances-directly",
589			create: proxycfg.TestConfigSnapshot,
590			setup: func(snap *proxycfg.ConfigSnapshot) {
591				snap.Proxy.Mode = structs.ProxyModeTransparent
592
593				snap.ConnectProxy.DiscoveryChain["mongo"] = discoverychain.TestCompileConfigEntries(
594					t, "mongo", "default", "dc1",
595					connect.TestClusterID+".consul", "dc1", nil)
596
597				snap.ConnectProxy.DiscoveryChain["kafka"] = discoverychain.TestCompileConfigEntries(
598					t, "kafka", "default", "dc1",
599					connect.TestClusterID+".consul", "dc1", nil)
600
601				kafka := structs.NewServiceName("kafka", structs.DefaultEnterpriseMeta())
602				mongo := structs.NewServiceName("mongo", structs.DefaultEnterpriseMeta())
603
604				// We add a filter chains for each passthrough service name.
605				// The filter chain will route to a cluster with the same SNI name.
606				snap.ConnectProxy.PassthroughUpstreams = map[string]proxycfg.ServicePassthroughAddrs{
607					kafka.String(): {
608						SNI: "kafka.default.dc1.internal.e5b08d03-bfc3-c870-1833-baddb116e648.consul",
609						Addrs: map[string]struct{}{
610							"9.9.9.9": {},
611						},
612					},
613					mongo.String(): {
614						SNI: "mongo.default.dc1.internal.e5b08d03-bfc3-c870-1833-baddb116e648.consul",
615						Addrs: map[string]struct{}{
616							"10.10.10.10": {},
617							"10.10.10.12": {},
618						},
619					},
620				}
621
622				// There should still be a filter chain for mongo's virtual address
623				snap.ConnectProxy.WatchedUpstreamEndpoints["mongo"] = map[string]structs.CheckServiceNodes{
624					"mongo.default.dc1": {
625						structs.CheckServiceNode{
626							Node: &structs.Node{
627								Datacenter: "dc1",
628							},
629							Service: &structs.NodeService{
630								Service: "mongo",
631								Address: "7.7.7.7",
632								Port:    27017,
633								TaggedAddresses: map[string]structs.ServiceAddress{
634									"virtual": {Address: "6.6.6.6"},
635								},
636							},
637						},
638					},
639				}
640			},
641		},
642	}
643
644	latestEnvoyVersion := proxysupport.EnvoyVersions[0]
645	latestEnvoyVersion_v2 := proxysupport.EnvoyVersionsV2[0]
646	for _, envoyVersion := range proxysupport.EnvoyVersions {
647		sf, err := determineSupportedProxyFeaturesFromString(envoyVersion)
648		require.NoError(t, err)
649		t.Run("envoy-"+envoyVersion, func(t *testing.T) {
650			for _, tt := range tests {
651				t.Run(tt.name, func(t *testing.T) {
652					// Sanity check default with no overrides first
653					snap := tt.create(t)
654
655					// We need to replace the TLS certs with deterministic ones to make golden
656					// files workable. Note we don't update these otherwise they'd change
657					// golder files for every test case and so not be any use!
658					setupTLSRootsAndLeaf(t, snap)
659
660					if tt.setup != nil {
661						tt.setup(snap)
662					}
663
664					// Need server just for logger dependency
665					g := newResourceGenerator(testutil.Logger(t), nil, nil, false)
666					g.ProxyFeatures = sf
667					if tt.generatorSetup != nil {
668						tt.generatorSetup(g)
669					}
670
671					listeners, err := g.listenersFromSnapshot(snap)
672					require.NoError(t, err)
673
674					// The order of listeners returned via LDS isn't relevant, so it's safe
675					// to sort these for the purposes of test comparisons.
676					sort.Slice(listeners, func(i, j int) bool {
677						return listeners[i].(*envoy_listener_v3.Listener).Name < listeners[j].(*envoy_listener_v3.Listener).Name
678					})
679
680					r, err := createResponse(ListenerType, "00000001", "00000001", listeners)
681					require.NoError(t, err)
682
683					t.Run("current", func(t *testing.T) {
684						gotJSON := protoToJSON(t, r)
685
686						gName := tt.name
687						if tt.overrideGoldenName != "" {
688							gName = tt.overrideGoldenName
689						}
690
691						require.JSONEq(t, goldenEnvoy(t, filepath.Join("listeners", gName), envoyVersion, latestEnvoyVersion, gotJSON), gotJSON)
692					})
693
694					t.Run("v2-compat", func(t *testing.T) {
695						if !stringslice.Contains(proxysupport.EnvoyVersionsV2, envoyVersion) {
696							t.Skip()
697						}
698						respV2, err := convertDiscoveryResponseToV2(r)
699						require.NoError(t, err)
700
701						gotJSON := protoToJSON(t, respV2)
702
703						gName := tt.name
704						if tt.overrideGoldenName != "" {
705							gName = tt.overrideGoldenName
706						}
707
708						gName += ".v2compat"
709
710						require.JSONEq(t, goldenEnvoy(t, filepath.Join("listeners", gName), envoyVersion, latestEnvoyVersion_v2, gotJSON), gotJSON)
711					})
712				})
713			}
714		})
715	}
716}
717
718type customListenerJSONOptions struct {
719	Name       string
720	TLSContext string
721}
722
723const customListenerJSONTpl = `{
724	"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
725	"name": "{{ .Name }}",
726	"address": {
727		"socketAddress": {
728			"address": "11.11.11.11",
729			"portValue": 11111
730		}
731	},
732	"filterChains": [
733		{
734			{{ if .TLSContext -}}
735			"transport_socket": {
736				"name": "tls",
737				"typed_config": {
738					"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
739					{{ .TLSContext }}
740				}
741			},
742			{{- end }}
743			"filters": [
744				{
745					"name": "envoy.filters.network.tcp_proxy",
746					"typedConfig": {
747						"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
748							"cluster": "random-cluster",
749							"statPrefix": "foo-stats"
750						}
751				}
752			]
753		}
754	]
755}`
756
757type customHTTPListenerJSONOptions struct {
758	Name                      string
759	HTTPConnectionManagerName string
760}
761
762const customHTTPListenerJSONTpl = `{
763	"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
764	"name": "{{ .Name }}",
765	"address": {
766		"socketAddress": {
767			"address": "11.11.11.11",
768			"portValue": 11111
769		}
770	},
771	"filterChains": [
772		{
773			"filters": [
774				{
775					"name": "{{ .HTTPConnectionManagerName }}",
776					"typedConfig": {
777						"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
778						"http_filters": [
779							{
780								"name": "envoy.filters.http.router"
781							}
782						],
783						"route_config": {
784							"name": "public_listener",
785							"virtual_hosts": [
786								{
787									"domains": [
788										"*"
789									],
790									"name": "public_listener",
791									"routes": [
792										{
793											"match": {
794												"prefix": "/"
795											},
796											"route": {
797												"cluster": "random-cluster"
798											}
799										}
800									]
801								}
802							]
803						}
804					}
805				}
806			]
807		}
808	]
809}`
810
811var (
812	customListenerJSONTemplate     = template.Must(template.New("").Parse(customListenerJSONTpl))
813	customHTTPListenerJSONTemplate = template.Must(template.New("").Parse(customHTTPListenerJSONTpl))
814)
815
816func customListenerJSON(t *testing.T, opts customListenerJSONOptions) string {
817	t.Helper()
818	var buf bytes.Buffer
819	require.NoError(t, customListenerJSONTemplate.Execute(&buf, opts))
820	return buf.String()
821}
822
823func customHTTPListenerJSON(t *testing.T, opts customHTTPListenerJSONOptions) string {
824	t.Helper()
825	if opts.HTTPConnectionManagerName == "" {
826		opts.HTTPConnectionManagerName = httpConnectionManagerNewName
827	}
828	var buf bytes.Buffer
829	require.NoError(t, customHTTPListenerJSONTemplate.Execute(&buf, opts))
830	return buf.String()
831}
832
833type httpCheckFetcherFunc func(serviceID structs.ServiceID) []structs.CheckType
834
835var _ HTTPCheckFetcher = (httpCheckFetcherFunc)(nil)
836
837func (f httpCheckFetcherFunc) ServiceHTTPBasedChecks(serviceID structs.ServiceID) []structs.CheckType {
838	return f(serviceID)
839}
840
841type configFetcherFunc func() string
842
843var _ ConfigFetcher = (configFetcherFunc)(nil)
844
845func (f configFetcherFunc) AdvertiseAddrLAN() string {
846	return f()
847}
848