From 28b7dea9731872ac3260d37f9a0aa0538a5918d0 Mon Sep 17 00:00:00 2001 From: Iryna Shustava Date: Wed, 12 Oct 2022 11:53:41 -0600 Subject: [PATCH] cli: Add -node-name flag to redirect-traffic command (#14933) --- .changelog/14933.txt | 3 + .../redirecttraffic/redirect_traffic.go | 31 ++++- .../redirecttraffic/redirect_traffic_test.go | 126 +++++++++++++++++- .../commands/connect/redirect-traffic.mdx | 2 + 4 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 .changelog/14933.txt diff --git a/.changelog/14933.txt b/.changelog/14933.txt new file mode 100644 index 000000000..c0bec8c7f --- /dev/null +++ b/.changelog/14933.txt @@ -0,0 +1,3 @@ +```release-note:feature +cli: Add -node-name flag to redirect-traffic command to support running in environments without client agents. +``` diff --git a/command/connect/redirecttraffic/redirect_traffic.go b/command/connect/redirecttraffic/redirect_traffic.go index 6436c98cc..277c12f69 100644 --- a/command/connect/redirecttraffic/redirect_traffic.go +++ b/command/connect/redirecttraffic/redirect_traffic.go @@ -36,6 +36,7 @@ type cmd struct { client *api.Client // Flags. + nodeName string consulDNSIP string proxyUID string proxyID string @@ -51,6 +52,8 @@ type cmd struct { func (c *cmd) init() { c.flags = flag.NewFlagSet("", flag.ContinueOnError) + c.flags.StringVar(&c.nodeName, "node-name", "", + "The node name where the proxy service is registered. It requires proxy-id to be specified. This is needed if running in an environment without client agents.") c.flags.StringVar(&c.consulDNSIP, "consul-dns-ip", "", "IP used to reach Consul DNS. If provided, DNS queries will be redirected to Consul.") c.flags.StringVar(&c.proxyUID, "proxy-uid", "", "The user ID of the proxy to exclude from traffic redirection.") c.flags.StringVar(&c.proxyID, "proxy-id", "", "The service ID of the proxy service registered with Consul.") @@ -150,9 +153,27 @@ func (c *cmd) generateConfigFromFlags() (iptables.Config, error) { } } - svc, _, err := c.client.Agent().Service(c.proxyID, nil) - if err != nil { - return iptables.Config{}, fmt.Errorf("failed to fetch proxy service from Consul Agent: %s", err) + var svc *api.AgentService + if c.nodeName == "" { + svc, _, err = c.client.Agent().Service(c.proxyID, nil) + if err != nil { + return iptables.Config{}, fmt.Errorf("failed to fetch proxy service from Consul Agent: %s", err) + } + } else { + svcList, _, err := c.client.Catalog().NodeServiceList(c.nodeName, &api.QueryOptions{ + Filter: fmt.Sprintf("ID == %q", c.proxyID), + MergeCentralConfig: true, + }) + if err != nil { + return iptables.Config{}, fmt.Errorf("failed to fetch proxy service from Consul: %s", err) + } + if len(svcList.Services) < 1 { + return iptables.Config{}, fmt.Errorf("proxy service with ID %q not found", c.proxyID) + } + if len(svcList.Services) > 1 { + return iptables.Config{}, fmt.Errorf("expected to find only one proxy service with ID %q, but more were found", c.proxyID) + } + svc = svcList.Services[0] } if svc.Proxy == nil { @@ -205,8 +226,8 @@ func (c *cmd) generateConfigFromFlags() (iptables.Config, error) { } } - // Exclude any exposed health check ports when Proxy.Expose.Checks is true. - if svc.Proxy.Expose.Checks { + // Exclude any exposed health check ports when Proxy.Expose.Checks is true and nodeName is not provided. + if svc.Proxy.Expose.Checks && c.nodeName == "" { // Get the health checks of the destination service. checks, err := c.client.Agent().ChecksWithFilter(fmt.Sprintf("ServiceName == %q", svc.Proxy.DestinationServiceName)) if err != nil { diff --git a/command/connect/redirecttraffic/redirect_traffic_test.go b/command/connect/redirecttraffic/redirect_traffic_test.go index e2ccf1c42..d31b0cffe 100644 --- a/command/connect/redirecttraffic/redirect_traffic_test.go +++ b/command/connect/redirecttraffic/redirect_traffic_test.go @@ -572,6 +572,109 @@ func TestGenerateConfigFromFlags(t *testing.T) { ExcludeInboundPorts: []string{"21500", "21501"}, }, }, + { + name: "skips agent checks when node name is provided", + command: func() cmd { + var c cmd + c.init() + c.proxyUID = "1234" + c.proxyID = "test-proxy-id" + c.nodeName = "test-node" + return c + }, + consulServices: []api.AgentServiceRegistration{ + { + ID: "foo-id", + Name: "foo", + Port: 8080, + Address: "1.1.1.1", + Checks: []*api.AgentServiceCheck{ + { + Name: "http", + HTTP: "1.1.1.1:8080/health", + Interval: "10s", + }, + { + Name: "grpc", + GRPC: "1.1.1.1:8081", + Interval: "10s", + }, + }, + }, + { + Kind: api.ServiceKindConnectProxy, + ID: "test-proxy-id", + Name: "test-proxy", + Port: 20000, + Address: "1.1.1.1", + Proxy: &api.AgentServiceConnectProxyConfig{ + DestinationServiceName: "foo", + DestinationServiceID: "foo-id", + Expose: api.ExposeConfig{ + Checks: true, + }, + }, + }, + }, + expCfg: iptables.Config{ + ProxyUserID: "1234", + ProxyInboundPort: 20000, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + //ExcludeInboundPorts: []string{"21500", "21501"}, + }, + }, + { + name: "proxyID with node name provided", + command: func() cmd { + var c cmd + c.init() + c.proxyUID = "1234" + c.proxyID = "test-proxy-id" + c.nodeName = "test-node" + return c + }, + consulServices: []api.AgentServiceRegistration{ + { + Kind: api.ServiceKindConnectProxy, + ID: "test-proxy-id", + Name: "test-proxy", + Port: 20000, + Address: "1.1.1.1", + Proxy: &api.AgentServiceConnectProxyConfig{ + DestinationServiceName: "foo", + }, + }, + }, + expCfg: iptables.Config{ + ProxyUserID: "1234", + ProxyInboundPort: 20000, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + }, + }, + { + name: "errors if no proxy services are found when proxy ID and node name are provided", + command: func() cmd { + var c cmd + c.init() + c.proxyUID = "1234" + c.proxyID = "test-proxy-id" + c.nodeName = "test-node" + return c + }, + consulServices: []api.AgentServiceRegistration{ + { + Kind: api.ServiceKindConnectProxy, + ID: "some-other-id", + Name: "test-proxy", + Port: 20000, + Address: "1.1.1.1", + Proxy: &api.AgentServiceConnectProxyConfig{ + DestinationServiceName: "foo", + }, + }, + }, + expError: "proxy service with ID \"test-proxy-id\" not found", + }, } for _, c := range cases { @@ -580,7 +683,7 @@ func TestGenerateConfigFromFlags(t *testing.T) { if c.consulServices != nil { testServer, err := testutil.NewTestServerConfigT(t, nil) require.NoError(t, err) - testServer.WaitForLeader(t) + testServer.WaitForSerfCheck(t) defer testServer.Stop() client, err := api.NewClient(&api.Config{Address: testServer.HTTPAddr}) @@ -588,6 +691,27 @@ func TestGenerateConfigFromFlags(t *testing.T) { cmd.client = client for _, service := range c.consulServices { + if cmd.nodeName != "" { + catalogRegistration := &api.CatalogRegistration{ + Node: cmd.nodeName, + Address: "127.0.0.1", + Service: &api.AgentService{ + Kind: service.Kind, + ID: service.ID, + Service: service.Name, + Port: service.Port, + Address: service.Address, + Proxy: service.Proxy, + }, + } + + _, err := client.Catalog().Register(catalogRegistration, nil) + require.NoError(t, err) + } + // We are always registering services with the agent just so we can check that we're not + // trying to fetch agent checks in the case when Proxy.Expose.Checks and -node-name flag is provided. + // This is not a scenario that will happen realistically when running without client agents, + // but this test setup allows us to check the negative case. err = client.Agent().ServiceRegister(&service) require.NoError(t, err) } diff --git a/website/content/commands/connect/redirect-traffic.mdx b/website/content/commands/connect/redirect-traffic.mdx index d50622844..688429bfb 100644 --- a/website/content/commands/connect/redirect-traffic.mdx +++ b/website/content/commands/connect/redirect-traffic.mdx @@ -32,6 +32,8 @@ Usage: `consul connect redirect-traffic [options]` #### Command Options +- `-node-name` - The node name where the proxy service is registered. It requires proxy-id to be specified. This is needed if running in an environment without client agents. + - `-consul-dns-ip` - The IP address of the Consul DNS resolver. If provided, DNS queries will be redirected to the provided IP address for name resolution. - `-proxy-id` - The [proxy service](/docs/connect/registration/service-registration) ID.