cli: Add -node-name flag to redirect-traffic command (#14933)

This commit is contained in:
Iryna Shustava 2022-10-12 11:53:41 -06:00 committed by GitHub
parent eb26a7dee9
commit 4bc4ef135c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 156 additions and 6 deletions

3
.changelog/14933.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
cli: Add -node-name flag to redirect-traffic command to support running in environments without client agents.
```

View File

@ -36,6 +36,7 @@ type cmd struct {
client *api.Client client *api.Client
// Flags. // Flags.
nodeName string
consulDNSIP string consulDNSIP string
proxyUID string proxyUID string
proxyID string proxyID string
@ -51,6 +52,8 @@ type cmd struct {
func (c *cmd) init() { func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError) 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.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.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.") c.flags.StringVar(&c.proxyID, "proxy-id", "", "The service ID of the proxy service registered with Consul.")
@ -150,10 +153,28 @@ func (c *cmd) generateConfigFromFlags() (iptables.Config, error) {
} }
} }
svc, _, err := c.client.Agent().Service(c.proxyID, nil) var svc *api.AgentService
if c.nodeName == "" {
svc, _, err = c.client.Agent().Service(c.proxyID, nil)
if err != nil { if err != nil {
return iptables.Config{}, fmt.Errorf("failed to fetch proxy service from Consul Agent: %s", err) 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 { if svc.Proxy == nil {
return iptables.Config{}, fmt.Errorf("service %s is not a proxy service", c.proxyID) return iptables.Config{}, fmt.Errorf("service %s is not a proxy service", c.proxyID)
@ -205,8 +226,8 @@ func (c *cmd) generateConfigFromFlags() (iptables.Config, error) {
} }
} }
// Exclude any exposed health check ports when Proxy.Expose.Checks is true. // Exclude any exposed health check ports when Proxy.Expose.Checks is true and nodeName is not provided.
if svc.Proxy.Expose.Checks { if svc.Proxy.Expose.Checks && c.nodeName == "" {
// Get the health checks of the destination service. // Get the health checks of the destination service.
checks, err := c.client.Agent().ChecksWithFilter(fmt.Sprintf("ServiceName == %q", svc.Proxy.DestinationServiceName)) checks, err := c.client.Agent().ChecksWithFilter(fmt.Sprintf("ServiceName == %q", svc.Proxy.DestinationServiceName))
if err != nil { if err != nil {

View File

@ -572,6 +572,109 @@ func TestGenerateConfigFromFlags(t *testing.T) {
ExcludeInboundPorts: []string{"21500", "21501"}, 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 { for _, c := range cases {
@ -580,7 +683,7 @@ func TestGenerateConfigFromFlags(t *testing.T) {
if c.consulServices != nil { if c.consulServices != nil {
testServer, err := testutil.NewTestServerConfigT(t, nil) testServer, err := testutil.NewTestServerConfigT(t, nil)
require.NoError(t, err) require.NoError(t, err)
testServer.WaitForLeader(t) testServer.WaitForSerfCheck(t)
defer testServer.Stop() defer testServer.Stop()
client, err := api.NewClient(&api.Config{Address: testServer.HTTPAddr}) client, err := api.NewClient(&api.Config{Address: testServer.HTTPAddr})
@ -588,6 +691,27 @@ func TestGenerateConfigFromFlags(t *testing.T) {
cmd.client = client cmd.client = client
for _, service := range c.consulServices { 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) err = client.Agent().ServiceRegister(&service)
require.NoError(t, err) require.NoError(t, err)
} }

View File

@ -32,6 +32,8 @@ Usage: `consul connect redirect-traffic [options]`
#### Command 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. - `-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. - `-proxy-id` - The [proxy service](/docs/connect/registration/service-registration) ID.