// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package envoy import ( "encoding/json" "flag" "fmt" "net" "net/http" "net/http/httptest" "os" "path/filepath" "strconv" "strings" "testing" "github.com/mitchellh/cli" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent" "github.com/hashicorp/consul/agent/xds" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/envoyextensions/xdscommon" "github.com/hashicorp/consul/sdk/testutil" ) var update = flag.Bool("update", false, "update golden files") func TestEnvoyCommand_noTabs(t *testing.T) { t.Parallel() if strings.ContainsRune(New(nil).Help(), '\t') { t.Fatal("help has tabs") } } func TestEnvoyGateway_Validation(t *testing.T) { t.Parallel() cases := []struct { name string args []string output string }{ { "-register for non-gateway", []string{"-register", "-proxy-id", "not-a-gateway"}, "Auto-Registration can only be used for gateways", }, { "-mesh-gateway and -gateway cannot be combined", []string{"-register", "-mesh-gateway", "-gateway", "mesh"}, "The mesh-gateway flag is deprecated and cannot be used alongside the gateway flag", }, { "no proxy registration specified nor discovered", []string{""}, "No proxy ID specified", }, { "-register with nodename", []string{"-register", "-proxy-id", "gw-svc-id", "-node-name", "gw-node"}, "'-register' cannot be used with '-node-name'", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ui := cli.NewMockUi() c := New(ui) c.init() code := c.Run(tc.args) if code == 0 { t.Errorf("%s: expected non-zero exit", tc.name) } output := ui.ErrorWriter.String() if !strings.Contains(output, tc.output) { t.Errorf("expected %q to contain %q", output, tc.output) } }) } } // testSetAndResetEnv sets the env vars passed as KEY=value strings in the // current ENV and returns a func() that will undo it's work at the end of the // test for use with defer. func testSetAndResetEnv(t *testing.T, env []string) func() { old := make(map[string]*string) for _, e := range env { pair := strings.SplitN(e, "=", 2) current := os.Getenv(pair[0]) if current != "" { old[pair[0]] = ¤t } else { // save it as a nil so we know to remove again old[pair[0]] = nil } require.NoError(t, os.Setenv(pair[0], pair[1])) } // Return a func that will reset to old values return func() { for k, v := range old { if v == nil { os.Unsetenv(k) } else { os.Setenv(k, *v) } } } } type generateConfigTestCase struct { Name string TLSServer bool ACLEnabled bool Flags []string Env []string Files map[string]string ProxyConfig map[string]interface{} ProxyDefaults api.ProxyConfigEntry NamespacesEnabled bool XDSPorts agent.GRPCPorts // used to mock an agent's configured gRPC ports. Plaintext defaults to 8502 and TLS defaults to 8503. AgentSelf110 bool // fake the agent API from versions v1.10 and earlier GRPCDisabled bool WantArgs BootstrapTplArgs WantErr string WantWarn string } // This tests the args we use to generate the template directly because they // encapsulate all the argument and default handling code which is where most of // the logic is. We also allow generating golden files but only for cases that // pass the test of having their template args generated as expected. func TestGenerateConfig(t *testing.T) { b, err := os.ReadFile("../../../test/ca/root.cer") require.NoError(t, err) rootPEM := string(b) rootPEM = strings.Replace(rootPEM, "\n", "\\n", -1) b, err = os.ReadFile("../../../test/ca_path/cert1.crt") require.NoError(t, err) pathPEM := string(b) b, err = os.ReadFile("../../../test/ca_path/cert2.crt") require.NoError(t, err) pathPEM += string(b) pathPEM = strings.Replace(pathPEM, "\n", "\\n", -1) cases := []generateConfigTestCase{ { Name: "no-args", Flags: []string{}, Env: []string{}, WantErr: "No proxy ID specified", }, { Name: "node-name without proxy-id", Flags: []string{"-node-name", "test-node"}, WantErr: "'-node-name' requires '-proxy-id'", }, { Name: "gRPC disabled", Flags: []string{"-proxy-id", "test-proxy"}, GRPCDisabled: true, WantErr: "agent has grpc disabled", }, { Name: "defaults", Flags: []string{"-proxy-id", "test-proxy"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "", PrometheusScrapePath: "/metrics", }, }, { Name: "defaults-nodemeta", Flags: []string{"-proxy-id", "test-proxy", "-node-name", "test-node"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", NodeName: "test-node", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "", PrometheusScrapePath: "/metrics", }, }, { Name: "telemetry-collector", Flags: []string{"-proxy-id", "test-proxy"}, ProxyConfig: map[string]interface{}{ "envoy_telemetry_collector_bind_socket_dir": "/tmp/consul/telemetry-collector", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "prometheus-metrics", Flags: []string{"-proxy-id", "test-proxy", "-prometheus-backend-port", "20100", "-prometheus-scrape-path", "/scrape-path"}, ProxyConfig: map[string]interface{}{ // When envoy_prometheus_bind_addr is set, if // PrometheusBackendPort is set, there will be a // "prometheus_backend" cluster in the Envoy configuration. "envoy_prometheus_bind_addr": "0.0.0.0:9000", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "20100", PrometheusScrapePath: "/scrape-path", }, }, { Name: "prometheus-metrics-tls-ca-file", Flags: []string{"-proxy-id", "test-proxy", "-prometheus-backend-port", "20100", "-prometheus-scrape-path", "/scrape-path", "-prometheus-ca-file", "../../../test/key/ourdomain.cer", "-prometheus-cert-file", "../../../test/key/ourdomain_server.cer", "-prometheus-key-file", "../../../test/key/ourdomain_server.key"}, ProxyConfig: map[string]interface{}{ // When envoy_prometheus_bind_addr is set, if // PrometheusBackendPort is set, there will be a // "prometheus_backend" cluster in the Envoy configuration. "envoy_prometheus_bind_addr": "0.0.0.0:9000", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "20100", PrometheusScrapePath: "/scrape-path", PrometheusCAFile: "../../../test/key/ourdomain.cer", PrometheusCertFile: "../../../test/key/ourdomain_server.cer", PrometheusKeyFile: "../../../test/key/ourdomain_server.key", }, }, { Name: "prometheus-metrics-tls-ca-path", Flags: []string{"-proxy-id", "test-proxy", "-prometheus-backend-port", "20100", "-prometheus-scrape-path", "/scrape-path", "-prometheus-ca-path", "../../../test/ca_path", "-prometheus-cert-file", "../../../test/key/ourdomain_server.cer", "-prometheus-key-file", "../../../test/key/ourdomain_server.key"}, ProxyConfig: map[string]interface{}{ // When envoy_prometheus_bind_addr is set, if // PrometheusBackendPort is set, there will be a // "prometheus_backend" cluster in the Envoy configuration. "envoy_prometheus_bind_addr": "0.0.0.0:9000", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "20100", PrometheusScrapePath: "/scrape-path", PrometheusCAPath: "../../../test/ca_path", PrometheusCertFile: "../../../test/key/ourdomain_server.cer", PrometheusKeyFile: "../../../test/key/ourdomain_server.key", }, }, { Name: "token-arg", Flags: []string{"-proxy-id", "test-proxy", "-token", "c9a52720-bf6c-4aa6-b8bc-66881a5ade95"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, Token: "c9a52720-bf6c-4aa6-b8bc-66881a5ade95", PrometheusScrapePath: "/metrics", }, }, { Name: "token-env", Flags: []string{"-proxy-id", "test-proxy"}, Env: []string{ "CONSUL_HTTP_TOKEN=c9a52720-bf6c-4aa6-b8bc-66881a5ade95", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, Token: "c9a52720-bf6c-4aa6-b8bc-66881a5ade95", PrometheusScrapePath: "/metrics", }, }, { Name: "token-file-arg", Flags: []string{"-proxy-id", "test-proxy", "-token-file", "@@TEMPDIR@@token.txt", }, Files: map[string]string{ "token.txt": "c9a52720-bf6c-4aa6-b8bc-66881a5ade95", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, Token: "c9a52720-bf6c-4aa6-b8bc-66881a5ade95", PrometheusScrapePath: "/metrics", }, }, { Name: "token-file-env", Flags: []string{"-proxy-id", "test-proxy"}, Env: []string{ "CONSUL_HTTP_TOKEN_FILE=@@TEMPDIR@@token.txt", }, Files: map[string]string{ "token.txt": "c9a52720-bf6c-4aa6-b8bc-66881a5ade95", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, Token: "c9a52720-bf6c-4aa6-b8bc-66881a5ade95", PrometheusScrapePath: "/metrics", }, }, { Name: "grpc-addr-flag", Flags: []string{"-proxy-id", "test-proxy", "-grpc-addr", "localhost:9999"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "9999", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "grpc-addr-env", Flags: []string{"-proxy-id", "test-proxy"}, Env: []string{ "CONSUL_GRPC_ADDR=localhost:9999", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "9999", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "grpc-addr-unix", Flags: []string{"-proxy-id", "test-proxy", "-grpc-addr", "unix:///var/run/consul.sock"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentSocket: "/var/run/consul.sock", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "grpc-addr-unix-with-tls", Flags: []string{"-proxy-id", "test-proxy", "-grpc-ca-file", "../../../test/ca/root.cer", "-grpc-addr", "unix:///var/run/consul.sock"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", GRPC: GRPC{ AgentSocket: "/var/run/consul.sock", AgentTLS: true, }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", AgentCAPEM: rootPEM, LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "xds-addr-config", Flags: []string{"-proxy-id", "test-proxy"}, XDSPorts: agent.GRPCPorts{Plaintext: 9999, TLS: 0}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "9999", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "grpc-tls-addr-config", Flags: []string{"-proxy-id", "test-proxy"}, XDSPorts: agent.GRPCPorts{Plaintext: 9997, TLS: 9998}, AgentSelf110: false, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "9998", AgentTLS: true, }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "deprecated-grpc-addr-config", Flags: []string{"-proxy-id", "test-proxy"}, XDSPorts: agent.GRPCPorts{Plaintext: 9999, TLS: 0}, AgentSelf110: true, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "9999", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "access-log-path", Flags: []string{"-proxy-id", "test-proxy", "-admin-access-log-path", "/some/path/access.log"}, WantWarn: "-admin-access-log-path is deprecated", WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/some/path/access.log", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "missing-ca-file", Flags: []string{"-proxy-id", "test-proxy", "-ca-file", "some/path"}, WantErr: "Error loading CA File: open some/path: no such file or directory", }, { Name: "existing-ca-file", TLSServer: true, Flags: []string{"-proxy-id", "test-proxy", "-grpc-ca-file", "../../../test/ca/root.cer"}, Env: []string{"CONSUL_GRPC_ADDR=https://127.0.0.1:8502"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", AgentTLS: true, }, AgentCAPEM: rootPEM, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "missing-ca-path", Flags: []string{"-proxy-id", "test-proxy", "-ca-path", "some/path"}, WantErr: "lstat some/path: no such file or directory", }, { Name: "existing-ca-path", TLSServer: true, Flags: []string{"-proxy-id", "test-proxy", "-grpc-ca-path", "../../../test/ca_path/"}, Env: []string{"CONSUL_GRPC_ADDR=https://127.0.0.1:8502"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", AgentTLS: true, }, AgentCAPEM: pathPEM, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "custom-bootstrap", Flags: []string{"-proxy-id", "test-proxy"}, ProxyConfig: map[string]interface{}{ // Add a completely custom bootstrap template. Never mind if this is // invalid envoy config just as long as it works and gets the variables // interplated. "envoy_bootstrap_json_tpl": ` { "admin": { "access_log_path": "/dev/null", "address": { "socket_address": { "address": "{{ .AdminBindAddress }}", "port_value": {{ .AdminBindPort }} } } }, "node": { "cluster": "{{ .ProxyCluster }}", "id": "{{ .ProxyID }}" }, "custom_field": "foo" }`, }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "extra_-single", Flags: []string{"-proxy-id", "test-proxy"}, ProxyConfig: map[string]interface{}{ // Add a custom sections with interpolated variables. These are all // invalid config syntax too but we are just testing they have the right // effect. "envoy_extra_static_clusters_json": ` { "name": "fake_cluster_1" }`, "envoy_extra_static_listeners_json": ` { "name": "fake_listener_1" }`, "envoy_extra_stats_sinks_json": ` { "name": "fake_sink_1" }`, }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "extra_-multiple", Flags: []string{"-proxy-id", "test-proxy"}, ProxyConfig: map[string]interface{}{ // Add a custom sections with interpolated variables. These are all // invalid config syntax too but we are just testing they have the right // effect. "envoy_extra_static_clusters_json": ` { "name": "fake_cluster_1" }, { "name": "fake_cluster_2" }`, "envoy_extra_static_listeners_json": ` { "name": "fake_listener_1" },{ "name": "fake_listener_2" }`, "envoy_extra_stats_sinks_json": ` { "name": "fake_sink_1" } , { "name": "fake_sink_2" }`, }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "stats-config-override", Flags: []string{"-proxy-id", "test-proxy"}, ProxyConfig: map[string]interface{}{ // Add a custom sections with interpolated variables. These are all // invalid config syntax too but we are just testing they have the right // effect. "envoy_stats_config_json": ` { "name": "fake_config" }`, }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "zipkin-tracing-config", Flags: []string{"-proxy-id", "test-proxy"}, ProxyConfig: map[string]interface{}{ // Add a custom sections with interpolated variables. These are all // invalid config syntax too but we are just testing they have the right // effect. "envoy_tracing_json": `{ "http": { "name": "envoy.zipkin", "config": { "collector_cluster": "zipkin", "collector_endpoint": "/api/v1/spans" } } }`, // Need to setup the cluster to send that too as well "envoy_extra_static_clusters_json": `{ "name": "zipkin", "type": "STRICT_DNS", "connect_timeout": "5s", "load_assignment": { "cluster_name": "zipkin", "endpoints": [ { "lb_endpoints": [ { "endpoint": { "address": { "socket_address": { "address": "zipkin.service.consul", "port_value": 9411 } } } } ] } ] } }`, }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "CONSUL_HTTP_ADDR-with-https-scheme-does-not-affect-grpc-tls", Flags: []string{"-proxy-id", "test-proxy", "-ca-file", "../../../test/ca/root.cer"}, Env: []string{"CONSUL_HTTP_ADDR=https://127.0.0.1:8500"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", AgentTLS: false, }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "CONSUL_GRPC_ADDR-with-https-scheme-enables-tls", Flags: []string{"-proxy-id", "test-proxy", "-ca-file", "../../../test/ca/root.cer"}, Env: []string{"CONSUL_GRPC_ADDR=https://127.0.0.1:8502"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", AgentTLS: true, }, AgentCAPEM: rootPEM, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "both-CONSUL_HTTP_ADDR-TLS-and-CONSUL_GRPC_ADDR-PLAIN-is-plain", Flags: []string{"-proxy-id", "test-proxy", "-ca-file", "../../../test/ca/root.cer"}, Env: []string{ "CONSUL_HTTP_ADDR=https://127.0.0.1:8500", "CONSUL_GRPC_ADDR=http://127.0.0.1:8502", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", AgentTLS: false, }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "both-CONSUL_HTTP_ADDR-PLAIN-and-CONSUL_GRPC_ADDR-TLS-is-tls", Flags: []string{"-proxy-id", "test-proxy", "-ca-file", "../../../test/ca/root.cer"}, Env: []string{ "CONSUL_HTTP_ADDR=http://127.0.0.1:8500", "CONSUL_GRPC_ADDR=https://127.0.0.1:8502", }, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", // Should resolve IP, note this might not resolve the same way // everywhere which might make this test brittle but not sure what else // to do. GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", AgentTLS: true, }, AgentCAPEM: rootPEM, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "ingress-gateway", Flags: []string{"-proxy-id", "ingress-gateway-1", "-gateway", "ingress"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "ingress-gateway", ProxyID: "ingress-gateway-1", ProxySourceService: "ingress-gateway", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "ingress-gateway-nodemeta", Flags: []string{"-proxy-id", "ingress-gateway-1", "-node-name", "test-node"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "ingress-gateway-1", ProxyID: "ingress-gateway-1", NodeName: "test-node", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "envoy-readiness-probe", Flags: []string{"-proxy-id", "test-proxy", "-envoy-ready-bind-address", "127.0.0.1", "-envoy-ready-bind-port", "21000"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "", PrometheusScrapePath: "/metrics", }, }, { Name: "ingress-gateway-address-specified", Flags: []string{"-proxy-id", "ingress-gateway", "-gateway", "ingress", "-address", "1.2.3.4:7777"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "ingress-gateway", ProxyID: "ingress-gateway", ProxySourceService: "ingress-gateway", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "ingress-gateway-register-with-service-without-proxy-id", Flags: []string{"-gateway", "ingress", "-register", "-service", "my-gateway", "-address", "127.0.0.1:7777"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "my-gateway", ProxyID: "my-gateway", ProxySourceService: "my-gateway", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "ingress-gateway-register-with-service-and-proxy-id", Flags: []string{"-gateway", "ingress", "-register", "-service", "my-gateway", "-proxy-id", "my-gateway-123", "-address", "127.0.0.1:7777"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "my-gateway", ProxyID: "my-gateway-123", ProxySourceService: "my-gateway", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "ingress-gateway-no-auto-register", Flags: []string{"-gateway", "ingress", "-address", "127.0.0.1:7777"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "ingress-gateway", ProxyID: "ingress-gateway", ProxySourceService: "ingress-gateway", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusScrapePath: "/metrics", }, }, { Name: "access-logs-enabled", Flags: []string{"-proxy-id", "test-proxy"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "", PrometheusScrapePath: "/metrics", }, ProxyDefaults: api.ProxyConfigEntry{ AccessLogs: &api.AccessLogsConfig{ Enabled: true, }, }, }, { Name: "access-logs-enabled-custom", Flags: []string{"-proxy-id", "test-proxy"}, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "", PrometheusScrapePath: "/metrics", }, ProxyDefaults: api.ProxyConfigEntry{ AccessLogs: &api.AccessLogsConfig{ Enabled: true, DisableListenerLogs: true, // Should have no effect here Type: api.FileLogSinkType, Path: "/var/log/consul.log", TextFormat: "MY START TIME %START_TIME%", }, }, }, { Name: "acl-enabled-but-no-token", Flags: []string{"-proxy-id", "test-proxy"}, ACLEnabled: true, WantWarn: "No ACL token was provided to Envoy.", WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "", PrometheusScrapePath: "/metrics", }, }, { Name: "acl-enabled-and-token", Flags: []string{"-proxy-id", "test-proxy", "-token", "foo"}, ACLEnabled: true, WantArgs: BootstrapTplArgs{ ProxyCluster: "test-proxy", ProxyID: "test-proxy", // We don't know this til after the lookup so it will be empty in the // initial args call we are testing here. ProxySourceService: "", GRPC: GRPC{ AgentAddress: "127.0.0.1", AgentPort: "8502", // Note this is the gRPC port }, AdminAccessLogPath: "/dev/null", AdminBindAddress: "127.0.0.1", AdminBindPort: "19000", Token: "foo", LocalAgentClusterName: xds.LocalAgentClusterName, PrometheusBackendPort: "", PrometheusScrapePath: "/metrics", }, }, } cases = append(cases, enterpriseGenerateConfigTestCases()...) copyAndReplaceAll := func(s []string, old, new string) []string { out := make([]string, len(s)) for i, v := range s { out[i] = strings.ReplaceAll(v, old, new) } return out } for _, tc := range cases { t.Run(tc.Name, func(t *testing.T) { testDir := testutil.TempDir(t, "envoytest") if len(tc.Files) > 0 { for fn, fv := range tc.Files { fullname := filepath.Join(testDir, fn) require.NoError(t, os.WriteFile(fullname, []byte(fv), 0600)) } } // Default the ports if tc.XDSPorts.TLS == 0 && tc.XDSPorts.Plaintext == 0 { tc.XDSPorts.Plaintext = 8502 } // Run a mock agent API that just always returns the proxy config in the // test. var srv *httptest.Server if tc.TLSServer { srv = httptest.NewTLSServer(testMockAgent(tc)) } else { srv = httptest.NewServer(testMockAgent(tc)) } defer srv.Close() testDirPrefix := testDir + string(filepath.Separator) myEnv := copyAndReplaceAll(tc.Env, "@@TEMPDIR@@", testDirPrefix) defer testSetAndResetEnv(t, myEnv)() client, err := api.NewClient(&api.Config{Address: srv.URL, TLSConfig: api.TLSConfig{InsecureSkipVerify: true}}) require.NoError(t, err) ui := cli.NewMockUi() c := New(ui) // explicitly set the client to one which can connect to the httptest.Server c.client = client c.dialFunc = func(_, _ string) (net.Conn, error) { return nil, nil } // Run the command myFlags := copyAndReplaceAll(tc.Flags, "@@TEMPDIR@@", testDirPrefix) args := append([]string{"-bootstrap"}, myFlags...) require.NoError(t, c.flags.Parse(args)) code := c.run(c.flags.Args()) if tc.WantErr != "" { require.Equal(t, 1, code, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), tc.WantErr) return } else if tc.WantWarn != "" { require.Equal(t, 0, code, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), tc.WantWarn) } else { require.Equal(t, 0, code, ui.ErrorWriter.String()) require.Empty(t, ui.ErrorWriter.String()) } // Verify we handled the env and flags right first to get correct template // args. got, err := c.templateArgs() require.NoError(t, err) // Error cases should have returned above require.Equal(t, &tc.WantArgs, got) actual := ui.OutputWriter.Bytes() // If we got the arg handling write, verify output golden := filepath.Join("testdata", tc.Name+".golden") if *update { os.WriteFile(golden, actual, 0644) } expected, err := os.ReadFile(golden) require.NoError(t, err) require.Equal(t, string(expected), string(actual)) }) } } func TestEnvoy_GatewayRegistration(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := agent.NewTestAgent(t, ``) defer a.Shutdown() client := a.Client() tt := []struct { name string args []string kind api.ServiceKind id string service string }{ { name: "register gateway with proxy-id and name", args: []string{ "-http-addr=" + a.HTTPAddr(), "-register", "-bootstrap", "-gateway", "ingress", "-service", "us-ingress", "-proxy-id", "us-ingress-1", }, kind: api.ServiceKindIngressGateway, id: "us-ingress-1", service: "us-ingress", }, { name: "register gateway without proxy-id with name", args: []string{ "-http-addr=" + a.HTTPAddr(), "-register", "-bootstrap", "-gateway", "ingress", "-service", "us-ingress", }, kind: api.ServiceKindIngressGateway, id: "us-ingress", service: "us-ingress", }, { name: "register gateway without proxy-id and without name", args: []string{ "-http-addr=" + a.HTTPAddr(), "-register", "-bootstrap", "-gateway", "ingress", }, kind: api.ServiceKindIngressGateway, id: "ingress-gateway", service: "ingress-gateway", }, { name: "register gateway with proxy-id without name", args: []string{ "-http-addr=" + a.HTTPAddr(), "-register", "-bootstrap", "-gateway", "ingress", "-proxy-id", "us-ingress-1", }, kind: api.ServiceKindIngressGateway, id: "us-ingress-1", service: "ingress-gateway", }, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { ui := cli.NewMockUi() c := New(ui) code := c.Run(tc.args) if code != 0 { t.Fatalf("bad exit code: %d. %#v", code, ui.ErrorWriter.String()) } data, _, err := client.Agent().Service(tc.id, nil) assert.NoError(t, err) assert.NotNil(t, data) assert.Equal(t, tc.kind, data.Kind) assert.Equal(t, tc.id, data.ID) assert.Equal(t, tc.service, data.Service) assert.Equal(t, defaultGatewayPort, data.Port) }) } } func TestEnvoy_proxyRegistration(t *testing.T) { t.Parallel() type args struct { svcForProxy api.AgentService cmdFn func(*cmd) } cases := []struct { name string args args testFn func(*testing.T, args, *api.AgentServiceRegistration) }{ { "locality is inherited from proxied service if configured and using sidecarFor", args{ svcForProxy: api.AgentService{ ID: "my-svc", Locality: &api.Locality{ Region: "us-east-1", Zone: "us-east-1a", }, }, cmdFn: func(c *cmd) { c.sidecarFor = "my-svc" }, }, func(t *testing.T, args args, r *api.AgentServiceRegistration) { assert.NotNil(t, r.Locality) assert.Equal(t, args.svcForProxy.Locality, r.Locality) }, }, { "locality is not inherited if not using sidecarFor", args{ svcForProxy: api.AgentService{ ID: "my-svc", Locality: &api.Locality{ Region: "us-east-1", Zone: "us-east-1a", }, }, }, func(t *testing.T, args args, r *api.AgentServiceRegistration) { assert.Nil(t, r.Locality) }, }, { "locality is not set if not configured for proxied service", args{ svcForProxy: api.AgentService{}, cmdFn: func(c *cmd) { c.sidecarFor = "my-svc" }, }, func(t *testing.T, args args, r *api.AgentServiceRegistration) { assert.Nil(t, r.Locality) }, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ui := cli.NewMockUi() c := New(ui) if tc.args.cmdFn != nil { tc.args.cmdFn(c) } result, err := c.proxyRegistration(&tc.args.svcForProxy) assert.NoError(t, err) tc.testFn(t, tc.args, result) }) } } // testMockAgent combines testMockAgentProxyConfig and testMockAgentSelf, // routing /agent/service/... requests to testMockAgentProxyConfig, // routing /catalog/node-services/... requests to testMockCatalogNodeServiceList // routing /agent/self requests to testMockAgentSelf. func testMockAgent(tc generateConfigTestCase) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { switch { case strings.Contains(r.URL.Path, "/agent/services"): testMockAgentGatewayConfig(tc.NamespacesEnabled)(w, r) case strings.Contains(r.URL.Path, "/agent/service"): testMockAgentProxyConfig(tc.ProxyConfig, tc.NamespacesEnabled)(w, r) case strings.Contains(r.URL.Path, "/agent/self"): testMockAgentSelf(tc.XDSPorts, tc.AgentSelf110, tc.GRPCDisabled)(w, r) case strings.Contains(r.URL.Path, "/catalog/node-services"): testMockCatalogNodeServiceList()(w, r) case strings.Contains(r.URL.Path, "/config/proxy-defaults/global"): testMockConfigProxyDefaults(tc.ProxyDefaults)(w, r) case strings.Contains(r.URL.Path, "/acl/token/self"): testMockTokenReadSelf(tc.ACLEnabled, tc.Flags)(w, r) default: http.NotFound(w, r) } } } func testMockAgentGatewayConfig(namespacesEnabled bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Parse the proxy-id from the end of the URL (blindly assuming it's correct // format) params := r.URL.Query() filter := params["filter"][0] var kind api.ServiceKind switch { case strings.Contains(filter, string(api.ServiceKindTerminatingGateway)): kind = api.ServiceKindTerminatingGateway case strings.Contains(filter, string(api.ServiceKindIngressGateway)): kind = api.ServiceKindIngressGateway } svc := map[string]*api.AgentService{ string(kind): { Kind: kind, ID: string(kind), Service: string(kind), Datacenter: "dc1", }, } if namespacesEnabled { svc[string(kind)].Namespace = namespaceFromQuery(r) svc[string(kind)].Partition = partitionFromQuery(r) } cfgJSON, err := json.Marshal(svc) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } w.Write(cfgJSON) } } func namespaceFromQuery(r *http.Request) string { // Use the namespace in the request if there is one, otherwise // use-default. if queryNamespace := r.URL.Query().Get("namespace"); queryNamespace != "" { return queryNamespace } if queryNs := r.URL.Query().Get("ns"); queryNs != "" { return queryNs } return "default" } func partitionFromQuery(r *http.Request) string { // Use the partition in the request if there is one, otherwise // use-default. if queryPartition := r.URL.Query().Get("partition"); queryPartition != "" { return queryPartition } if queryAp := r.URL.Query().Get("ap"); queryAp != "" { return queryAp } return "default" } func testMockAgentProxyConfig(cfg map[string]interface{}, namespacesEnabled bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Parse the proxy-id from the end of the URL (blindly assuming it's correct // format) proxyID := strings.TrimPrefix(r.URL.Path, "/v1/agent/service/") serviceID := strings.TrimSuffix(proxyID, "-proxy") svc := api.AgentService{ Kind: api.ServiceKindConnectProxy, ID: proxyID, Service: proxyID, Proxy: &api.AgentServiceConnectProxyConfig{ DestinationServiceName: serviceID, DestinationServiceID: serviceID, Config: cfg, }, Datacenter: "dc1", } if namespacesEnabled { svc.Namespace = namespaceFromQuery(r) svc.Partition = partitionFromQuery(r) } cfgJSON, err := json.Marshal(svc) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } w.Write(cfgJSON) } } func testMockCatalogNodeServiceList() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { quotedProxyID := strings.TrimPrefix(r.URL.Query().Get("filter"), "ID == ") proxyID := quotedProxyID[1 : len(quotedProxyID)-1] serviceID := strings.TrimSuffix(proxyID, "-proxy") var svcKind api.ServiceKind if strings.Contains(proxyID, "ingress-gateway") { svcKind = api.ServiceKindIngressGateway } else { svcKind = api.ServiceKindConnectProxy } var svcProxy api.AgentServiceConnectProxyConfig if svcKind == api.ServiceKindConnectProxy { svcProxy = api.AgentServiceConnectProxyConfig{ DestinationServiceName: serviceID, DestinationServiceID: serviceID, } } svc := api.AgentService{ Kind: svcKind, ID: proxyID, Service: proxyID, Proxy: &svcProxy, } nodeSvc := api.CatalogNodeServiceList{ Node: &api.Node{Datacenter: "dc1"}, Services: []*api.AgentService{&svc}, } cfgJSON, err := json.Marshal(nodeSvc) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } w.Write(cfgJSON) } } func testMockConfigProxyDefaults(entry api.ProxyConfigEntry) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cfgJSON, err := json.Marshal(entry) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } w.Write(cfgJSON) } } func testMockTokenReadSelf(aclEnabled bool, flags []string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if aclEnabled { for _, f := range flags { if f == "-token" { w.WriteHeader(200) return } } w.WriteHeader(403) w.Write([]byte(acl.ErrNotFound.Error())) return } } } func TestEnvoyCommand_canBindInternal(t *testing.T) { t.Parallel() type testCheck struct { expected bool addr string } type testCase struct { ifAddrs []net.Addr checks map[string]testCheck } parseIPNets := func(t *testing.T, in ...string) []net.Addr { var out []net.Addr for _, addr := range in { ip := net.ParseIP(addr) require.NotNil(t, ip) out = append(out, &net.IPNet{IP: ip}) } return out } parseIPs := func(t *testing.T, in ...string) []net.Addr { var out []net.Addr for _, addr := range in { ip := net.ParseIP(addr) require.NotNil(t, ip) out = append(out, &net.IPAddr{IP: ip}) } return out } cases := map[string]testCase{ "IPNet": { parseIPNets(t, "10.3.0.2", "198.18.0.1", "2001:db8:a0b:12f0::1"), map[string]testCheck{ "ipv4": { true, "10.3.0.2", }, "secondary ipv4": { true, "198.18.0.1", }, "ipv6": { true, "2001:db8:a0b:12f0::1", }, "ipv4 not found": { false, "1.2.3.4", }, "ipv6 not found": { false, "::ffff:192.168.0.1", }, }, }, "IPAddr": { parseIPs(t, "10.3.0.2", "198.18.0.1", "2001:db8:a0b:12f0::1"), map[string]testCheck{ "ipv4": { true, "10.3.0.2", }, "secondary ipv4": { true, "198.18.0.1", }, "ipv6": { true, "2001:db8:a0b:12f0::1", }, "ipv4 not found": { false, "1.2.3.4", }, "ipv6 not found": { false, "::ffff:192.168.0.1", }, }, }, } for name, tcase := range cases { t.Run(name, func(t *testing.T) { for checkName, check := range tcase.checks { t.Run(checkName, func(t *testing.T) { require.Equal(t, check.expected, canBindInternal(check.addr, tcase.ifAddrs)) }) } }) } } // testMockAgentSelf returns an empty /v1/agent/self response except GRPC // port is filled in to match the given wantXDSPort argument. func testMockAgentSelf( wantXDSPorts agent.GRPCPorts, agentSelf110 bool, grpcDisabled bool, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { resp := agent.Self{ Config: map[string]interface{}{ "Datacenter": "dc1", }, } if agentSelf110 { resp.DebugConfig = map[string]interface{}{ "GRPCPort": wantXDSPorts.Plaintext, } } else if grpcDisabled { resp.DebugConfig = map[string]interface{}{ "GRPCPort": -1, } // the real agent does not populate XDS if grpc or // grpc-tls ports are < 0 } else { resp.XDS = &agent.XDSSelf{ // The deprecated Port field should default to TLS if it's available. Port: wantXDSPorts.TLS, Ports: wantXDSPorts, } if wantXDSPorts.TLS <= 0 { resp.XDS.Port = wantXDSPorts.Plaintext } } selfJSON, err := json.Marshal(resp) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } w.Write(selfJSON) } } func TestCheckEnvoyVersionCompatibility(t *testing.T) { tests := []struct { name string envoyVersion string unsupportedList []string expectedCompat envoyCompat isErrorExpected bool }{ { name: "supported-using-proxy-support-defined", envoyVersion: xdscommon.EnvoyVersions[1], unsupportedList: xdscommon.UnsupportedEnvoyVersions, expectedCompat: envoyCompat{ isCompatible: true, }, }, { name: "supported-at-max", envoyVersion: xdscommon.GetMaxEnvoyMinorVersion(), unsupportedList: xdscommon.UnsupportedEnvoyVersions, expectedCompat: envoyCompat{ isCompatible: true, }, }, { name: "supported-patch-higher", envoyVersion: addNPatchVersion(xdscommon.EnvoyVersions[0], 1), unsupportedList: xdscommon.UnsupportedEnvoyVersions, expectedCompat: envoyCompat{ isCompatible: true, }, }, { name: "not-supported-minor-higher", envoyVersion: addNMinorVersion(xdscommon.EnvoyVersions[0], 1), unsupportedList: xdscommon.UnsupportedEnvoyVersions, expectedCompat: envoyCompat{ isCompatible: false, versionIncompatible: replacePatchVersionWithX(addNMinorVersion(xdscommon.EnvoyVersions[0], 1)), }, }, { name: "not-supported-minor-lower", envoyVersion: addNMinorVersion(xdscommon.EnvoyVersions[len(xdscommon.EnvoyVersions)-1], -1), unsupportedList: xdscommon.UnsupportedEnvoyVersions, expectedCompat: envoyCompat{ isCompatible: false, versionIncompatible: replacePatchVersionWithX(addNMinorVersion(xdscommon.EnvoyVersions[len(xdscommon.EnvoyVersions)-1], -1)), }, }, { name: "not-supported-explicitly-unsupported-version", envoyVersion: addNPatchVersion(xdscommon.EnvoyVersions[0], 1), unsupportedList: []string{"1.23.1", addNPatchVersion(xdscommon.EnvoyVersions[0], 1)}, expectedCompat: envoyCompat{ isCompatible: false, versionIncompatible: addNPatchVersion(xdscommon.EnvoyVersions[0], 1), }, }, { name: "error-bad-input", envoyVersion: "1.abc.3", unsupportedList: xdscommon.UnsupportedEnvoyVersions, expectedCompat: envoyCompat{}, isErrorExpected: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { actual, err := checkEnvoyVersionCompatibility(tc.envoyVersion, tc.unsupportedList) if tc.isErrorExpected { assert.Error(t, err) } else { assert.NoError(t, err) } assert.Equal(t, tc.expectedCompat, actual) }) } } func addNPatchVersion(s string, n int) string { splitS := strings.Split(s, ".") minor, _ := strconv.Atoi(splitS[2]) minor += n return fmt.Sprintf("%s.%s.%d", splitS[0], splitS[1], minor) } func addNMinorVersion(s string, n int) string { splitS := strings.Split(s, ".") major, _ := strconv.Atoi(splitS[1]) major += n return fmt.Sprintf("%s.%d.%s", splitS[0], major, splitS[2]) }