From 5d888f530332c2cf5791f2ae76db3152f15d909b Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Wed, 1 Feb 2017 21:42:41 -0500 Subject: [PATCH] Added -relay-factor param to keyring operations --- command/agent/keyring.go | 29 +++++++++---- command/agent/keyring_test.go | 16 +++---- command/agent/operator_endpoint.go | 31 ++++++++++--- command/agent/operator_endpoint_test.go | 43 ++++++++++++++++--- command/agent/rpc.go | 19 ++++---- command/agent/rpc_client.go | 17 ++++---- command/agent/rpc_client_test.go | 14 +++--- command/keyring.go | 22 ++++++++-- command/keyring_test.go | 11 +++++ consul/internal_endpoint.go | 9 ++-- consul/structs/structs.go | 9 ++-- .../docs/agent/http/operator.html.markdown | 3 ++ .../docs/commands/keyring.html.markdown | 4 ++ 13 files changed, 162 insertions(+), 65 deletions(-) diff --git a/command/agent/keyring.go b/command/agent/keyring.go index e7b8aa4ce1..c870e2dbcb 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -121,31 +121,44 @@ func (a *Agent) keyringProcess(args *structs.KeyringRequest) (*structs.KeyringRe return &reply, nil } +// ParseRelayFactor validates and converts the given relay factor to uint8 +func ParseRelayFactor(n int) (uint8, error) { + if n < 0 || n > 5 { + return 0, fmt.Errorf("Relay factor must be in range: [0, 5]") + } + return uint8(n), nil +} + // ListKeys lists out all keys installed on the collective Consul cluster. This // includes both servers and clients in all DC's. -func (a *Agent) ListKeys(token string) (*structs.KeyringResponses, error) { +func (a *Agent) ListKeys(token string, relayFactor uint8) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Operation: structs.KeyringList} - args.Token = token + parseKeyringRequest(&args, token, relayFactor) return a.keyringProcess(&args) } // InstallKey installs a new gossip encryption key -func (a *Agent) InstallKey(key, token string) (*structs.KeyringResponses, error) { +func (a *Agent) InstallKey(key, token string, relayFactor uint8) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key, Operation: structs.KeyringInstall} - args.Token = token + parseKeyringRequest(&args, token, relayFactor) return a.keyringProcess(&args) } // UseKey changes the primary encryption key used to encrypt messages -func (a *Agent) UseKey(key, token string) (*structs.KeyringResponses, error) { +func (a *Agent) UseKey(key, token string, relayFactor uint8) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key, Operation: structs.KeyringUse} - args.Token = token + parseKeyringRequest(&args, token, relayFactor) return a.keyringProcess(&args) } // RemoveKey will remove a gossip encryption key from the keyring -func (a *Agent) RemoveKey(key, token string) (*structs.KeyringResponses, error) { +func (a *Agent) RemoveKey(key, token string, relayFactor uint8) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key, Operation: structs.KeyringRemove} - args.Token = token + parseKeyringRequest(&args, token, relayFactor) return a.keyringProcess(&args) } + +func parseKeyringRequest(req *structs.KeyringRequest, token string, relayFactor uint8) { + req.Token = token + req.RelayFactor = relayFactor +} diff --git a/command/agent/keyring_test.go b/command/agent/keyring_test.go index f364b6fa86..aa60a6303c 100644 --- a/command/agent/keyring_test.go +++ b/command/agent/keyring_test.go @@ -132,49 +132,49 @@ func TestAgentKeyring_ACL(t *testing.T) { testutil.WaitForLeader(t, agent.RPC, "dc1") // List keys without access fails - _, err := agent.ListKeys("") + _, err := agent.ListKeys("", 0) if err == nil || !strings.Contains(err.Error(), "denied") { t.Fatalf("expected denied error, got: %#v", err) } // List keys with access works - _, err = agent.ListKeys("root") + _, err = agent.ListKeys("root", 0) if err != nil { t.Fatalf("err: %s", err) } // Install without access fails - _, err = agent.InstallKey(key2, "") + _, err = agent.InstallKey(key2, "", 0) if err == nil || !strings.Contains(err.Error(), "denied") { t.Fatalf("expected denied error, got: %#v", err) } // Install with access works - _, err = agent.InstallKey(key2, "root") + _, err = agent.InstallKey(key2, "root", 0) if err != nil { t.Fatalf("err: %s", err) } // Use without access fails - _, err = agent.UseKey(key2, "") + _, err = agent.UseKey(key2, "", 0) if err == nil || !strings.Contains(err.Error(), "denied") { t.Fatalf("expected denied error, got: %#v", err) } // Use with access works - _, err = agent.UseKey(key2, "root") + _, err = agent.UseKey(key2, "root", 0) if err != nil { t.Fatalf("err: %s", err) } // Remove without access fails - _, err = agent.RemoveKey(key1, "") + _, err = agent.RemoveKey(key1, "", 0) if err == nil || !strings.Contains(err.Error(), "denied") { t.Fatalf("expected denied error, got: %#v", err) } // Remove with access works - _, err = agent.RemoveKey(key1, "root") + _, err = agent.RemoveKey(key1, "root", 0) if err != nil { t.Fatalf("err: %s", err) } diff --git a/command/agent/operator_endpoint.go b/command/agent/operator_endpoint.go index ac5377a614..c45f89aa5f 100644 --- a/command/agent/operator_endpoint.go +++ b/command/agent/operator_endpoint.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/consul/consul/structs" multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/raft" + "strconv" ) // OperatorRaftConfiguration is used to inspect the current Raft configuration. @@ -59,8 +60,9 @@ func (s *HTTPServer) OperatorRaftPeer(resp http.ResponseWriter, req *http.Reques } type keyringArgs struct { - Key string - Token string + Key string + Token string + RelayFactor uint8 } // OperatorKeyringEndpoint handles keyring operations (install, list, use, remove) @@ -75,6 +77,23 @@ func (s *HTTPServer) OperatorKeyringEndpoint(resp http.ResponseWriter, req *http } s.parseToken(req, &args.Token) + // Parse relay factor + if relayFactor := req.URL.Query().Get("relay-factor"); relayFactor != "" { + n, err := strconv.Atoi(relayFactor) + if err != nil { + resp.WriteHeader(400) + resp.Write([]byte(fmt.Sprintf("Error parsing relay factor: %v", err))) + return nil, nil + } + + args.RelayFactor, err = ParseRelayFactor(n) + if err != nil { + resp.WriteHeader(400) + resp.Write([]byte(fmt.Sprintf("Invalid relay factor: %v", err))) + return nil, nil + } + } + // Switch on the method switch req.Method { case "GET": @@ -93,7 +112,7 @@ func (s *HTTPServer) OperatorKeyringEndpoint(resp http.ResponseWriter, req *http // KeyringInstall is used to install a new gossip encryption key into the cluster func (s *HTTPServer) KeyringInstall(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) { - responses, err := s.agent.InstallKey(args.Key, args.Token) + responses, err := s.agent.InstallKey(args.Key, args.Token, args.RelayFactor) if err != nil { return nil, err } @@ -103,7 +122,7 @@ func (s *HTTPServer) KeyringInstall(resp http.ResponseWriter, req *http.Request, // KeyringList is used to list the keys installed in the cluster func (s *HTTPServer) KeyringList(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) { - responses, err := s.agent.ListKeys(args.Token) + responses, err := s.agent.ListKeys(args.Token, args.RelayFactor) if err != nil { return nil, err } @@ -113,7 +132,7 @@ func (s *HTTPServer) KeyringList(resp http.ResponseWriter, req *http.Request, ar // KeyringRemove is used to list the keys installed in the cluster func (s *HTTPServer) KeyringRemove(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) { - responses, err := s.agent.RemoveKey(args.Key, args.Token) + responses, err := s.agent.RemoveKey(args.Key, args.Token, args.RelayFactor) if err != nil { return nil, err } @@ -123,7 +142,7 @@ func (s *HTTPServer) KeyringRemove(resp http.ResponseWriter, req *http.Request, // KeyringUse is used to change the primary gossip encryption key func (s *HTTPServer) KeyringUse(resp http.ResponseWriter, req *http.Request, args *keyringArgs) (interface{}, error) { - responses, err := s.agent.UseKey(args.Key, args.Token) + responses, err := s.agent.UseKey(args.Key, args.Token, args.RelayFactor) if err != nil { return nil, err } diff --git a/command/agent/operator_endpoint_test.go b/command/agent/operator_endpoint_test.go index 9aff08a4b2..4b1dd54126 100644 --- a/command/agent/operator_endpoint_test.go +++ b/command/agent/operator_endpoint_test.go @@ -77,7 +77,7 @@ func TestOperator_KeyringInstall(t *testing.T) { t.Fatalf("err: %s", err) } - listResponse, err := srv.agent.ListKeys("") + listResponse, err := srv.agent.ListKeys("", 0) if err != nil { t.Fatalf("err: %s", err) } @@ -155,13 +155,13 @@ func TestOperator_KeyringRemove(t *testing.T) { c.EncryptKey = key } httpTestWithConfig(t, func(srv *HTTPServer) { - _, err := srv.agent.InstallKey(tempKey, "") + _, err := srv.agent.InstallKey(tempKey, "", 0) if err != nil { t.Fatalf("err: %v", err) } // Make sure the temp key is installed - list, err := srv.agent.ListKeys("") + list, err := srv.agent.ListKeys("", 0) if err != nil { t.Fatalf("err: %v", err) } @@ -191,7 +191,7 @@ func TestOperator_KeyringRemove(t *testing.T) { } // Make sure the temp key has been removed - list, err = srv.agent.ListKeys("") + list, err = srv.agent.ListKeys("", 0) if err != nil { t.Fatalf("err: %v", err) } @@ -217,7 +217,7 @@ func TestOperator_KeyringUse(t *testing.T) { c.EncryptKey = oldKey } httpTestWithConfig(t, func(srv *HTTPServer) { - if _, err := srv.agent.InstallKey(newKey, ""); err != nil { + if _, err := srv.agent.InstallKey(newKey, "", 0); err != nil { t.Fatalf("err: %v", err) } @@ -233,12 +233,12 @@ func TestOperator_KeyringUse(t *testing.T) { t.Fatalf("err: %s", err) } - if _, err := srv.agent.RemoveKey(oldKey, ""); err != nil { + if _, err := srv.agent.RemoveKey(oldKey, "", 0); err != nil { t.Fatalf("err: %v", err) } // Make sure only the new key remains - list, err := srv.agent.ListKeys("") + list, err := srv.agent.ListKeys("", 0) if err != nil { t.Fatalf("err: %v", err) } @@ -256,3 +256,32 @@ func TestOperator_KeyringUse(t *testing.T) { } }, configFunc) } + +func TestOperator_Keyring_InvalidRelayFactor(t *testing.T) { + key := "H3/9gBxcKKRf45CaI2DlRg==" + configFunc := func(c *Config) { + c.EncryptKey = key + } + httpTestWithConfig(t, func(srv *HTTPServer) { + cases := map[string]string{ + "999": "Relay factor must be in range", + "asdf": "Error parsing relay factor", + } + for relayFactor, errString := range cases { + req, err := http.NewRequest("GET", "/v1/operator/keyring?relay-factor="+relayFactor, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + resp := httptest.NewRecorder() + _, err = srv.OperatorKeyringEndpoint(resp, req) + if err != nil { + t.Fatalf("err: %v", err) + } + body := resp.Body.String() + if !strings.Contains(body, errString) { + t.Fatalf("bad: %v", body) + } + } + }, configFunc) +} diff --git a/command/agent/rpc.go b/command/agent/rpc.go index bcca2c332f..47276d2745 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -106,7 +106,8 @@ type joinResponse struct { } type keyringRequest struct { - Key string + Key string + RelayFactor uint8 } type KeyringEntry struct { @@ -604,21 +605,21 @@ func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd, token strin var r keyringResponse var err error - if cmd != listKeysCommand { - if err = client.dec.Decode(&req); err != nil { - return fmt.Errorf("decode failed: %v", err) - } + if err = client.dec.Decode(&req); err != nil { + return fmt.Errorf("decode failed: %v", err) } + i.agent.logger.Printf("[INFO] agent: Sending rpc command with relay factor %d", req.RelayFactor) + switch cmd { case listKeysCommand: - queryResp, err = i.agent.ListKeys(token) + queryResp, err = i.agent.ListKeys(token, req.RelayFactor) case installKeyCommand: - queryResp, err = i.agent.InstallKey(req.Key, token) + queryResp, err = i.agent.InstallKey(req.Key, token, req.RelayFactor) case useKeyCommand: - queryResp, err = i.agent.UseKey(req.Key, token) + queryResp, err = i.agent.UseKey(req.Key, token, req.RelayFactor) case removeKeyCommand: - queryResp, err = i.agent.RemoveKey(req.Key, token) + queryResp, err = i.agent.RemoveKey(req.Key, token, req.RelayFactor) default: respHeader := responseHeader{Seq: seq, Error: unsupportedCommand} client.Send(&respHeader, nil) diff --git a/command/agent/rpc_client.go b/command/agent/rpc_client.go index 744866b415..ecd97aecc2 100644 --- a/command/agent/rpc_client.go +++ b/command/agent/rpc_client.go @@ -194,48 +194,49 @@ func (c *RPCClient) WANMembers() ([]Member, error) { return resp.Members, err } -func (c *RPCClient) ListKeys(token string) (keyringResponse, error) { +func (c *RPCClient) ListKeys(token string, relayFactor uint8) (keyringResponse, error) { header := requestHeader{ Command: listKeysCommand, Seq: c.getSeq(), Token: token, } + req := keyringRequest{RelayFactor: relayFactor} var resp keyringResponse - err := c.genericRPC(&header, nil, &resp) + err := c.genericRPC(&header, req, &resp) return resp, err } -func (c *RPCClient) InstallKey(key, token string) (keyringResponse, error) { +func (c *RPCClient) InstallKey(key, token string, relayFactor uint8) (keyringResponse, error) { header := requestHeader{ Command: installKeyCommand, Seq: c.getSeq(), Token: token, } - req := keyringRequest{key} + req := keyringRequest{Key: key, RelayFactor: relayFactor} var resp keyringResponse err := c.genericRPC(&header, &req, &resp) return resp, err } -func (c *RPCClient) UseKey(key, token string) (keyringResponse, error) { +func (c *RPCClient) UseKey(key, token string, relayFactor uint8) (keyringResponse, error) { header := requestHeader{ Command: useKeyCommand, Seq: c.getSeq(), Token: token, } - req := keyringRequest{key} + req := keyringRequest{Key: key, RelayFactor: relayFactor} var resp keyringResponse err := c.genericRPC(&header, &req, &resp) return resp, err } -func (c *RPCClient) RemoveKey(key, token string) (keyringResponse, error) { +func (c *RPCClient) RemoveKey(key, token string, relayFactor uint8) (keyringResponse, error) { header := requestHeader{ Command: removeKeyCommand, Seq: c.getSeq(), Token: token, } - req := keyringRequest{key} + req := keyringRequest{Key: key, RelayFactor: relayFactor} var resp keyringResponse err := c.genericRPC(&header, &req, &resp) return resp, err diff --git a/command/agent/rpc_client_test.go b/command/agent/rpc_client_test.go index a6feb12f51..6ace0bb6f0 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -371,7 +371,7 @@ func TestRPCClientInstallKey(t *testing.T) { }) // install key2 - r, err := p1.client.InstallKey(key2, "") + r, err := p1.client.InstallKey(key2, "", 0) if err != nil { t.Fatalf("err: %s", err) } @@ -402,7 +402,7 @@ func TestRPCClientUseKey(t *testing.T) { defer p1.Close() // add a second key to the ring - r, err := p1.client.InstallKey(key2, "") + r, err := p1.client.InstallKey(key2, "", 0) if err != nil { t.Fatalf("err: %s", err) } @@ -423,21 +423,21 @@ func TestRPCClientUseKey(t *testing.T) { }) // can't remove key1 yet - r, err = p1.client.RemoveKey(key1, "") + r, err = p1.client.RemoveKey(key1, "", 0) if err != nil { t.Fatalf("err: %s", err) } keyringError(t, r) // change primary key - r, err = p1.client.UseKey(key2, "") + r, err = p1.client.UseKey(key2, "", 0) if err != nil { t.Fatalf("err: %s", err) } keyringSuccess(t, r) // can remove key1 now - r, err = p1.client.RemoveKey(key1, "") + r, err = p1.client.RemoveKey(key1, "", 0) if err != nil { t.Fatalf("err: %s", err) } @@ -450,7 +450,7 @@ func TestRPCClientKeyOperation_encryptionDisabled(t *testing.T) { }) defer p1.Close() - r, err := p1.client.ListKeys("") + r, err := p1.client.ListKeys("", 0) if err != nil { t.Fatalf("err: %s", err) } @@ -458,7 +458,7 @@ func TestRPCClientKeyOperation_encryptionDisabled(t *testing.T) { } func listKeys(t *testing.T, c *RPCClient) map[string]map[string]int { - resp, err := c.ListKeys("") + resp, err := c.ListKeys("", 0) if err != nil { t.Fatalf("err: %s", err) } diff --git a/command/keyring.go b/command/keyring.go index 3a47cb9358..863326f99e 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -18,6 +18,7 @@ type KeyringCommand struct { func (c *KeyringCommand) Run(args []string) int { var installKey, useKey, removeKey, token string var listKeys bool + var relay int cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } @@ -27,6 +28,7 @@ func (c *KeyringCommand) Run(args []string) int { cmdFlags.StringVar(&removeKey, "remove", "", "remove key") cmdFlags.BoolVar(&listKeys, "list", false, "list keys") cmdFlags.StringVar(&token, "token", "", "acl token") + cmdFlags.IntVar(&relay, "relay-factor", 0, "relay factor") rpcAddr := RPCAddrFlag(cmdFlags) if err := cmdFlags.Parse(args); err != nil { @@ -56,6 +58,13 @@ func (c *KeyringCommand) Run(args []string) int { return 1 } + // Validate the relay factor + relayFactor, err := agent.ParseRelayFactor(relay) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error parsing relay factor: %s", err)) + return 1 + } + // All other operations will require a client connection client, err := RPCClient(*rpcAddr) if err != nil { @@ -66,7 +75,7 @@ func (c *KeyringCommand) Run(args []string) int { if listKeys { c.Ui.Info("Gathering installed encryption keys...") - r, err := client.ListKeys(token) + r, err := client.ListKeys(token, relayFactor) if err != nil { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 @@ -80,7 +89,7 @@ func (c *KeyringCommand) Run(args []string) int { if installKey != "" { c.Ui.Info("Installing new gossip encryption key...") - r, err := client.InstallKey(installKey, token) + r, err := client.InstallKey(installKey, token, relayFactor) if err != nil { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 @@ -90,7 +99,7 @@ func (c *KeyringCommand) Run(args []string) int { if useKey != "" { c.Ui.Info("Changing primary gossip encryption key...") - r, err := client.UseKey(useKey, token) + r, err := client.UseKey(useKey, token, relayFactor) if err != nil { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 @@ -100,7 +109,7 @@ func (c *KeyringCommand) Run(args []string) int { if removeKey != "" { c.Ui.Info("Removing gossip encryption key...") - r, err := client.RemoveKey(removeKey, token) + r, err := client.RemoveKey(removeKey, token, relayFactor) if err != nil { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 @@ -206,6 +215,11 @@ Options: not currently the primary key. -token="" ACL token to use during requests. Defaults to that of the agent. + -relay-factor Added in Consul 0.7.4, setting this to a non-zero + value will cause nodes to relay their response to + the operation through this many randomly-chosen + other nodes in the cluster. The maximum allowed + value is 5. -use= Change the primary encryption key, which is used to encrypt messages. The key must already be installed before this operation can succeed. diff --git a/command/keyring_test.go b/command/keyring_test.go index bb8691ebb4..77027c47fc 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -89,6 +89,17 @@ func TestKeyringCommandRun_failedConnection(t *testing.T) { } } +func TestKeyringCommandRun_invalidRelayFactor(t *testing.T) { + ui := new(cli.MockUi) + c := &KeyringCommand{Ui: ui} + + args := []string{"-list", "-relay-factor=6"} + code := c.Run(args) + if code != 1 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } +} + func listKeys(t *testing.T, addr string) string { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 2d0c059619..2be6de14c8 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -147,15 +147,16 @@ func (m *Internal) executeKeyringOp( mgr = m.srv.KeyManagerLAN() } + opts := &serf.KeyRequestOptions{RelayFactor: args.RelayFactor} switch args.Operation { case structs.KeyringList: - serfResp, err = mgr.ListKeys() + serfResp, err = mgr.ListKeysWithOptions(opts) case structs.KeyringInstall: - serfResp, err = mgr.InstallKey(args.Key) + serfResp, err = mgr.InstallKeyWithOptions(args.Key, opts) case structs.KeyringUse: - serfResp, err = mgr.UseKey(args.Key) + serfResp, err = mgr.UseKeyWithOptions(args.Key, opts) case structs.KeyringRemove: - serfResp, err = mgr.RemoveKey(args.Key) + serfResp, err = mgr.RemoveKeyWithOptions(args.Key, opts) } errStr := "" diff --git a/consul/structs/structs.go b/consul/structs/structs.go index c4e134eeb8..13c67b3d55 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -1000,10 +1000,11 @@ const ( // KeyringRequest encapsulates a request to modify an encryption keyring. // It can be used for install, remove, or use key type operations. type KeyringRequest struct { - Operation KeyringOp - Key string - Datacenter string - Forwarded bool + Operation KeyringOp + Key string + Datacenter string + Forwarded bool + RelayFactor uint8 QueryOptions } diff --git a/website/source/docs/agent/http/operator.html.markdown b/website/source/docs/agent/http/operator.html.markdown index 81525c614f..7b6bdd52e9 100644 --- a/website/source/docs/agent/http/operator.html.markdown +++ b/website/source/docs/agent/http/operator.html.markdown @@ -138,6 +138,9 @@ Available in Consul 0.7.2 and later, the keyring endpoint supports the This endpoint supports the use of ACL tokens using either the `X-CONSUL-TOKEN` header or the `?token=` query parameter. +Added in Consul 0.7.4, this endpoint supports the `?relay-factor=` query parameter. +See the [Keyring Command](/docs/commands/keyring.html#_relay_factor) for more details. + #### GET Method Using the `GET` method, this endpoint will list the gossip encryption keys diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown index b2dba7defa..b34c3dbfc7 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -48,6 +48,10 @@ The list of available flags are: * `-token=""` - ACL token to use during requests. Defaults to that of the agent. +* `-relay-factor` - Added in Consul 0.7.4, setting this to a non-zero value will + cause nodes to relay their response to the operation through this many + randomly-chosen other nodes in the cluster. The maximum allowed value is 5. + * `-rpc-addr` - Address to the RPC server of the agent you want to contact to send this command. If this isn't specified, the command will contact "127.0.0.1:8400" which is the default RPC address of a Consul agent.