From e20a72499902c58b2f874e0dcd29062b05fda700 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 5 Sep 2014 17:22:33 -0700 Subject: [PATCH 01/80] consul: first pass at keyring integration --- command/agent/agent.go | 85 +++++++++++++++++++++++++++++++++++++++- command/agent/command.go | 15 +++---- command/agent/config.go | 6 +++ consul/config.go | 5 +++ 4 files changed, 103 insertions(+), 8 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 9ef0711377..f955d4a3ab 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -1,16 +1,21 @@ package agent import ( + "encoding/base64" + "encoding/json" "fmt" "io" + "io/ioutil" "log" "net" "os" + "path/filepath" "strconv" "sync" "github.com/hashicorp/consul/consul" "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/memberlist" "github.com/hashicorp/serf/serf" ) @@ -109,6 +114,33 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { // Initialize the local state agent.state.Init(config, agent.logger) + // Setup encryption keyring files + if !config.DisableKeyring && config.EncryptKey != "" { + keyringBytes, err := json.MarshalIndent([]string{config.EncryptKey}) + if err != nil { + return nil, err + } + paths := []string{ + filepath.Join(config.DataDir, "serf", "keyring_lan"), + filepath.Join(config.DataDir, "serf", "keyring_wan"), + } + for _, path := range paths { + if _, err := os.Stat(path); err == nil { + continue + } + fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return nil, err + } + defer fh.Close() + + if _, err := fh.Write(keyringBytes); err != nil { + os.Remove(path) + return nil, err + } + } + } + // Setup either the client or the server var err error if config.Server { @@ -160,11 +192,21 @@ func (a *Agent) consulConfig() *consul.Config { if a.config.DataDir != "" { base.DataDir = a.config.DataDir } - if a.config.EncryptKey != "" { + if a.config.EncryptKey != "" && a.config.DisableKeyring { key, _ := a.config.EncryptBytes() base.SerfLANConfig.MemberlistConfig.SecretKey = key base.SerfWANConfig.MemberlistConfig.SecretKey = key } + if !a.config.DisableKeyring { + lanKeyring := filepath.Join(base.DataDir, "serf", "keyring_lan") + wanKeyring := filepath.Join(base.DataDir, "serf", "keyring_wan") + + base.SerfLANConfig.KeyringFile = lanKeyring + base.SerfWANConfig.KeyringFile = wanKeyring + + base.SerfLANConfig.MemberlistConfig.Keyring = loadKeyringFile(lanKeyring) + base.SerfWANConfig.MemberlistConfig.Keyring = loadKeyringFile(wanKeyring) + } if a.config.NodeName != "" { base.NodeName = a.config.NodeName } @@ -253,6 +295,9 @@ func (a *Agent) consulConfig() *consul.Config { } } + // Setup gossip keyring configuration + base.DisableKeyring = a.config.DisableKeyring + // Setup the loggers base.LogOutput = a.logOutput return base @@ -648,3 +693,41 @@ func (a *Agent) deletePid() error { } return nil } + +// loadKeyringFile will load a keyring out of a file +func loadKeyringFile(keyringFile string) *memberlist.Keyring { + if _, err := os.Stat(keyringFile); err != nil { + return nil + } + + // Read in the keyring file data + keyringData, err := ioutil.ReadFile(keyringFile) + if err != nil { + return nil + } + + // Decode keyring JSON + keys := make([]string, 0) + if err := json.Unmarshal(keyringData, &keys); err != nil { + return nil + } + + // Decode base64 values + keysDecoded := make([][]byte, len(keys)) + for i, key := range keys { + keyBytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil + } + keysDecoded[i] = keyBytes + } + + // Create the keyring + keyring, err := memberlist.NewKeyring(keysDecoded, keysDecoded[0]) + if err != nil { + return nil + } + + // Success! + return keyring +} diff --git a/command/agent/command.go b/command/agent/command.go index 6474402f99..91118fc630 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -67,6 +67,7 @@ func (c *Command) readConfig() *Config { cmdFlags.StringVar(&cmdConfig.UiDir, "ui-dir", "", "path to the web UI directory") cmdFlags.StringVar(&cmdConfig.PidFile, "pid-file", "", "path to file to store PID") cmdFlags.StringVar(&cmdConfig.EncryptKey, "encrypt", "", "gossip encryption key") + cmdFlags.BoolVar(&cmdConfig.DisableKeyring, "disable-keyring", false, "disable use of encryption keyring") cmdFlags.BoolVar(&cmdConfig.Server, "server", false, "run agent as server") cmdFlags.BoolVar(&cmdConfig.Bootstrap, "bootstrap", false, "enable server bootstrap mode") @@ -143,13 +144,6 @@ func (c *Command) readConfig() *Config { config.NodeName = hostname } - if config.EncryptKey != "" { - if _, err := config.EncryptBytes(); err != nil { - c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err)) - return nil - } - } - // Ensure we have a data directory if config.DataDir == "" { c.Ui.Error("Must specify data directory using -data-dir") @@ -180,6 +174,13 @@ func (c *Command) readConfig() *Config { return nil } + if config.EncryptKey != "" { + if _, err := config.EncryptBytes(); err != nil { + c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err)) + return nil + } + } + // Compile all the watches for _, params := range config.Watches { // Parse the watches, excluding the handler diff --git a/command/agent/config.go b/command/agent/config.go index 36683ad16a..e099c64b14 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -104,6 +104,9 @@ type Config struct { // recursors array. DNSRecursor string `mapstructure:"recursor"` + // Disable use of an encryption keyring. + DisableKeyring bool `mapstructure:"disable_keyring"` + // DNSRecursors can be set to allow the DNS servers to recursively // resolve non-consul domains DNSRecursors []string `mapstructure:"recursors"` @@ -676,6 +679,9 @@ func MergeConfig(a, b *Config) *Config { if b.EncryptKey != "" { result.EncryptKey = b.EncryptKey } + if b.DisableKeyring { + result.DisableKeyring = true + } if b.LogLevel != "" { result.LogLevel = b.LogLevel } diff --git a/consul/config.go b/consul/config.go index 9cb1944cbc..10acaab2c6 100644 --- a/consul/config.go +++ b/consul/config.go @@ -165,6 +165,11 @@ type Config struct { // UserEventHandler callback can be used to handle incoming // user events. This function should not block. UserEventHandler func(serf.UserEvent) + + // DisableKeyring is used to disable persisting the encryption keyring to + // filesystem. By default, if encryption is enabled, Consul will create a + // file inside of the DataDir to keep track of changes made to the ring. + DisableKeyring bool } // CheckVersion is used to check if the ProtocolVersion is valid From 0da6e08d9df24da8c3fa71c82e4eb2d11d3aa3d2 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 5 Sep 2014 22:01:49 -0700 Subject: [PATCH 02/80] consul: fix json marshaling --- command/agent/agent.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index f955d4a3ab..97198c36c2 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -116,7 +116,8 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { // Setup encryption keyring files if !config.DisableKeyring && config.EncryptKey != "" { - keyringBytes, err := json.MarshalIndent([]string{config.EncryptKey}) + keys := []string{config.EncryptKey} + keyringBytes, err := json.MarshalIndent(keys, "", " ") if err != nil { return nil, err } From 471ee9ce8f826dfca0bd13d8c267572051cf9da6 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 5 Sep 2014 22:58:44 -0700 Subject: [PATCH 03/80] command: create serf dir if it doesn't exist, document -disable-keyring arg --- command/agent/agent.go | 11 +++++++++-- command/agent/command.go | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 97198c36c2..225c813a9c 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -116,15 +116,22 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { // Setup encryption keyring files if !config.DisableKeyring && config.EncryptKey != "" { + serfDir := filepath.Join(config.DataDir, "serf") + if err := os.MkdirAll(serfDir, 0700); err != nil { + return nil, err + } + keys := []string{config.EncryptKey} keyringBytes, err := json.MarshalIndent(keys, "", " ") if err != nil { return nil, err } + paths := []string{ - filepath.Join(config.DataDir, "serf", "keyring_lan"), - filepath.Join(config.DataDir, "serf", "keyring_wan"), + filepath.Join(serfDir, "keyring_lan"), + filepath.Join(serfDir, "keyring_wan"), } + for _, path := range paths { if _, err := os.Stat(path); err == nil { continue diff --git a/command/agent/command.go b/command/agent/command.go index 91118fc630..4f31249bbc 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -792,6 +792,10 @@ Options: -data-dir=path Path to a data directory to store agent state -dc=east-aws Datacenter of the agent -encrypt=key Provides the gossip encryption key + -disable-keyring Disables the use of an encryption keyring. The + Default behavior is to persist encryption keys using + a keyring file, and reload the keys on subsequent + starts. This argument disables keyring persistence. -join=1.2.3.4 Address of an agent to join at start time. Can be specified multiple times. -join-wan=1.2.3.4 Address of an agent to join -wan at start time. From b1c0bb60ce03e95fac5ebb7b0400d3e8df3bcb5f Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 6 Sep 2014 09:41:57 -0700 Subject: [PATCH 04/80] command: warn when passing -encrypt when keyring already exists --- command/agent/command.go | 12 +++++++++++- command/agent/config.go | 12 ++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/command/agent/command.go b/command/agent/command.go index 4f31249bbc..649cded48f 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -219,6 +219,13 @@ func (c *Command) readConfig() *Config { c.Ui.Error("WARNING: Windows is not recommended as a Consul server. Do not use in production.") } + // Warn if an encryption key is passed while a keyring already exists + if config.EncryptKey != "" && config.CheckKeyringFiles() { + c.Ui.Error(fmt.Sprintf( + "WARNING: Keyring already exists, ignoring new key %s", + config.EncryptKey)) + } + // Set the version info config.Revision = c.Revision config.Version = c.Version @@ -586,6 +593,9 @@ func (c *Command) Run(args []string) int { }(wp) } + // Determine if gossip is encrypted + gossipEncrypted := (config.EncryptKey != "" || config.CheckKeyringFiles()) + // Let the agent know we've finished registration c.agent.StartSync() @@ -598,7 +608,7 @@ func (c *Command) Run(args []string) int { c.Ui.Info(fmt.Sprintf(" Cluster Addr: %v (LAN: %d, WAN: %d)", config.AdvertiseAddr, config.Ports.SerfLan, config.Ports.SerfWan)) c.Ui.Info(fmt.Sprintf("Gossip encrypt: %v, RPC-TLS: %v, TLS-Incoming: %v", - config.EncryptKey != "", config.VerifyOutgoing, config.VerifyIncoming)) + gossipEncrypted, config.VerifyOutgoing, config.VerifyIncoming)) // Enable log streaming c.Ui.Info("") diff --git a/command/agent/config.go b/command/agent/config.go index e099c64b14..2fad1db399 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -411,6 +411,18 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { return addr.String(), nil } +// CheckKeyringFiles checks for existence of the keyring files for Serf +func (c *Config) CheckKeyringFiles() bool { + serfDir := filepath.Join(c.DataDir, "serf") + if _, err := os.Stat(filepath.Join(serfDir, "keyring_lan")); err != nil { + return false + } + if _, err := os.Stat(filepath.Join(serfDir, "keyring_wan")); err != nil { + return false + } + return true +} + // DecodeConfig reads the configuration from the given reader in JSON // format and decodes it into a proper Config structure. func DecodeConfig(r io.Reader) (*Config, error) { From ed3562b8099829d02b80a479156543de3cef52dd Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 6 Sep 2014 11:12:45 -0700 Subject: [PATCH 05/80] command: add skeletons for keys command --- command/keys.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ commands.go | 6 +++ 2 files changed, 103 insertions(+) create mode 100644 command/keys.go diff --git a/command/keys.go b/command/keys.go new file mode 100644 index 0000000000..da8e743d1c --- /dev/null +++ b/command/keys.go @@ -0,0 +1,97 @@ +package command + +import ( + "flag" + "fmt" + "github.com/mitchellh/cli" + "strings" +) + +// KeysCommand is a Command implementation that handles querying, installing, +// and removing gossip encryption keys from a keyring. +type KeysCommand struct { + Ui cli.Ui +} + +func (c *KeysCommand) Run(args []string) int { + var installKey, useKey, removeKey string + var listKeys bool + + cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) + cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } + + cmdFlags.StringVar(&installKey, "install", "", "install key") + cmdFlags.StringVar(&useKey, "use", "", "use key") + cmdFlags.StringVar(&removeKey, "remove", "", "remove key") + cmdFlags.BoolVar(&listKeys, "list", false, "list keys") + + rpcAddr := RPCAddrFlag(cmdFlags) + if err := cmdFlags.Parse(args); err != nil { + return 1 + } + + // Only accept a single argument + found := listKeys + for _, arg := range []string{installKey, useKey, removeKey} { + if found && len(arg) > 0 { + c.Ui.Error("Only one of -list, -install, -use, or -remove allowed") + return 1 + } + found = found || len(arg) > 0 + } + + client, err := RPCClient(*rpcAddr) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) + return 1 + } + defer client.Close() + + if listKeys { + return 0 + } + + if installKey != "" { + return 0 + } + + if useKey != "" { + return 0 + } + + if removeKey != "" { + return 0 + } + + return 0 +} + +func (c *KeysCommand) Help() string { + helpText := ` +Usage: consul keys [options] + + Manages encryption keys used for gossip messages. Gossip encryption is + optional. When enabled, this command may be used to examine active encryption + keys in the cluster, add new keys, and remove old ones. When combined, this + functionality provides the ability to perform key rotation cluster-wide, + without disrupting the cluster. + +Options: + + -install= Install a new encryption key. This will broadcast + the new key to all members in the cluster. + -use= Change the primary encryption key, which is used to + encrypt messages. The key must already be installed + before this operation can succeed. + -remove= Remove the given key from the cluster. This + operation may only be performed on keys which are + not currently the primary key. + -list List all keys currently in use within the cluster. + -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. +` + return strings.TrimSpace(helpText) +} + +func (c *KeysCommand) Synopsis() string { + return "Manages gossip layer encryption keys" +} diff --git a/commands.go b/commands.go index 3a31abd73f..8fab257ace 100644 --- a/commands.go +++ b/commands.go @@ -56,6 +56,12 @@ func init() { }, nil }, + "keys": func() (cli.Command, error) { + return &command.KeysCommand{ + Ui: ui, + }, nil + }, + "leave": func() (cli.Command, error) { return &command.LeaveCommand{ Ui: ui, From 4dd1b424779349da40ae9351277cd38c55d30e38 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 18:09:51 -0700 Subject: [PATCH 06/80] consul: use rpc layer only for key management functions, add rpc commands --- command/agent/agent.go | 16 +++++++++ command/agent/rpc.go | 79 ++++++++++++++++++++++++++++++++++++------ command/keys.go | 19 +++++----- consul/client.go | 5 +++ consul/server.go | 10 ++++++ 5 files changed, 111 insertions(+), 18 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 225c813a9c..16c1e33e51 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -739,3 +739,19 @@ func loadKeyringFile(keyringFile string) *memberlist.Keyring { // Success! return keyring } + +// ListKeysLAN returns the keys installed on the LAN gossip pool +func (a *Agent) ListKeysLAN() map[string]int { + if a.server != nil { + return a.server.ListKeysLAN() + } + return a.client.ListKeysLAN() +} + +// ListKeysWAN returns the keys installed on the WAN gossip pool +func (a *Agent) ListKeysWAN() map[string]int { + if a.server != nil { + return a.server.ListKeysWAN() + } + return nil +} diff --git a/command/agent/rpc.go b/command/agent/rpc.go index caf97cef1f..e35615de56 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -41,16 +41,24 @@ const ( ) const ( - handshakeCommand = "handshake" - forceLeaveCommand = "force-leave" - joinCommand = "join" - membersLANCommand = "members-lan" - membersWANCommand = "members-wan" - stopCommand = "stop" - monitorCommand = "monitor" - leaveCommand = "leave" - statsCommand = "stats" - reloadCommand = "reload" + handshakeCommand = "handshake" + forceLeaveCommand = "force-leave" + joinCommand = "join" + membersLANCommand = "members-lan" + membersWANCommand = "members-wan" + stopCommand = "stop" + monitorCommand = "monitor" + leaveCommand = "leave" + statsCommand = "stats" + reloadCommand = "reload" + listKeysLANCommand = "list-keys-lan" + listKeysWANCommand = "list-keys-wan" + installKeyLANCommand = "install-key-lan" + installKeyWANCommand = "install-key-wan" + useKeyLANCommand = "use-key-lan" + useKeyWANCommand = "use-key-wan" + removeKeyLANCommand = "remove-key-lan" + removeKeyWANCommand = "remove-key-wan" ) const ( @@ -103,6 +111,13 @@ type joinResponse struct { Num int32 } +type keysResponse struct { + Messages map[string]string + NumNodes int + NumResp int + Keys map[string]int +} + type membersResponse struct { Members []Member } @@ -373,6 +388,32 @@ func (i *AgentRPC) handleRequest(client *rpcClient, reqHeader *requestHeader) er case reloadCommand: return i.handleReload(client, seq) + case listKeysLANCommand: + return i.handleListKeysWAN(client, seq) + + case listKeysWANCommand: + return i.handleListKeysLAN(client, seq) + + /* + case installKeyLANCommand: + return i.handleInstallKeyLAN(client, seq) + + case installKeyWANCommand: + return i.handleInstallKeyWAN(client, seq) + + case useKeyLANCommand: + return i.handleUseKeyLAN(client, seq) + + case useKeyWANCommand: + return i.handleUseKeyWAN(client, seq) + + case removeKeyLANCommand: + return i.handleRemoveKeyLAN(client, seq) + + case removeKeyWANCommand: + return i.handleRemoveKeyWAN(client, seq) + */ + default: respHeader := responseHeader{Seq: seq, Error: unsupportedCommand} client.Send(&respHeader, nil) @@ -583,6 +624,24 @@ func (i *AgentRPC) handleReload(client *rpcClient, seq uint64) error { return client.Send(&resp, nil) } +func (i *AgentRPC) handleListKeysLAN(client *rpcClient, seq uint64) error { + header := responseHeader{ + Seq: seq, + Error: "", + } + resp := i.agent.ListKeysLAN() + return client.Send(&header, resp) +} + +func (i *AgentRPC) handleListKeysWAN(client *rpcClient, seq uint64) error { + header := responseHeader{ + Seq: seq, + Error: "", + } + resp := i.agent.ListKeysWAN() + return client.Send(&header, resp) +} + // Used to convert an error to a string representation func errToString(err error) string { if err == nil { diff --git a/command/keys.go b/command/keys.go index da8e743d1c..e63b460150 100644 --- a/command/keys.go +++ b/command/keys.go @@ -3,8 +3,9 @@ package command import ( "flag" "fmt" - "github.com/mitchellh/cli" "strings" + + "github.com/mitchellh/cli" ) // KeysCommand is a Command implementation that handles querying, installing, @@ -30,6 +31,13 @@ func (c *KeysCommand) Run(args []string) int { return 1 } + client, err := RPCClient(*rpcAddr) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) + return 1 + } + defer client.Close() + // Only accept a single argument found := listKeys for _, arg := range []string{installKey, useKey, removeKey} { @@ -40,14 +48,9 @@ func (c *KeysCommand) Run(args []string) int { found = found || len(arg) > 0 } - client, err := RPCClient(*rpcAddr) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) - return 1 - } - defer client.Close() - if listKeys { + km := client.KeyManager() + fmt.Println(km.ListKeys()) return 0 } diff --git a/consul/client.go b/consul/client.go index 28838bf795..fb02cdcd0d 100644 --- a/consul/client.go +++ b/consul/client.go @@ -206,6 +206,11 @@ func (c *Client) UserEvent(name string, payload []byte) error { return c.serf.UserEvent(userEventName(name), payload, false) } +// KeyManager returns the Serf keyring manager +func (c *Client) KeyManager() *serf.KeyManager { + return c.serf.KeyManager() +} + // lanEventHandler is used to handle events from the lan Serf cluster func (c *Client) lanEventHandler() { for { diff --git a/consul/server.go b/consul/server.go index 2adbc7edbb..8f913ed464 100644 --- a/consul/server.go +++ b/consul/server.go @@ -551,6 +551,16 @@ func (s *Server) IsLeader() bool { return s.raft.State() == raft.Leader } +// KeyManagerLAN returns the LAN Serf keyring manager +func (s *Server) KeyManagerLAN() *serf.KeyManager { + return s.serfLAN.KeyManager() +} + +// KeyManagerWAN returns the WAN Serf keyring manager +func (s *Server) KeyManagerWAN() *serf.KeyManager { + return s.serfWAN.KeyManager() +} + // inmemCodec is used to do an RPC call without going over a network type inmemCodec struct { method string From 67b179ccc956bba143264d41660078237245b843 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 18:36:54 -0700 Subject: [PATCH 07/80] command: basic rpc works for keys command --- command/agent/agent.go | 15 +++++++++------ command/agent/rpc.go | 13 +++++++++++-- command/agent/rpc_client.go | 22 ++++++++++++++++++++++ command/keys.go | 8 ++++++-- consul/client.go | 4 ++-- 5 files changed, 50 insertions(+), 12 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 16c1e33e51..b219f18bc7 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -741,17 +741,20 @@ func loadKeyringFile(keyringFile string) *memberlist.Keyring { } // ListKeysLAN returns the keys installed on the LAN gossip pool -func (a *Agent) ListKeysLAN() map[string]int { +func (a *Agent) ListKeysLAN() (*serf.KeyResponse, error) { if a.server != nil { - return a.server.ListKeysLAN() + km := a.server.KeyManagerLAN() + return km.ListKeys() } - return a.client.ListKeysLAN() + km := a.client.KeyManagerLAN() + return km.ListKeys() } // ListKeysWAN returns the keys installed on the WAN gossip pool -func (a *Agent) ListKeysWAN() map[string]int { +func (a *Agent) ListKeysWAN() (*serf.KeyResponse, error) { if a.server != nil { - return a.server.ListKeysWAN() + km := a.server.KeyManagerWAN() + return km.ListKeys() } - return nil + return nil, nil } diff --git a/command/agent/rpc.go b/command/agent/rpc.go index e35615de56..e5934fc8ad 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -629,7 +629,11 @@ func (i *AgentRPC) handleListKeysLAN(client *rpcClient, seq uint64) error { Seq: seq, Error: "", } - resp := i.agent.ListKeysLAN() + resp, err := i.agent.ListKeysLAN() + if err != nil { + return err + } + return client.Send(&header, resp) } @@ -638,7 +642,12 @@ func (i *AgentRPC) handleListKeysWAN(client *rpcClient, seq uint64) error { Seq: seq, Error: "", } - resp := i.agent.ListKeysWAN() + + resp, err := i.agent.ListKeysWAN() + if err != nil { + return err + } + return client.Send(&header, resp) } diff --git a/command/agent/rpc_client.go b/command/agent/rpc_client.go index 6cd0fc19f1..d5ff5894e6 100644 --- a/command/agent/rpc_client.go +++ b/command/agent/rpc_client.go @@ -176,6 +176,28 @@ func (c *RPCClient) WANMembers() ([]Member, error) { return resp.Members, err } +func (c *RPCClient) ListKeysLAN() (map[string]int, error) { + header := requestHeader{ + Command: listKeysLANCommand, + Seq: c.getSeq(), + } + resp := make(map[string]int) + + err := c.genericRPC(&header, nil, &resp) + return resp, err +} + +func (c *RPCClient) ListKeysWAN() (map[string]int, error) { + header := requestHeader{ + Command: listKeysWANCommand, + Seq: c.getSeq(), + } + resp := make(map[string]int) + + err := c.genericRPC(&header, nil, &resp) + return resp, err +} + // Leave is used to trigger a graceful leave and shutdown func (c *RPCClient) Leave() error { header := requestHeader{ diff --git a/command/keys.go b/command/keys.go index e63b460150..4f063a2dd1 100644 --- a/command/keys.go +++ b/command/keys.go @@ -49,8 +49,12 @@ func (c *KeysCommand) Run(args []string) int { } if listKeys { - km := client.KeyManager() - fmt.Println(km.ListKeys()) + keys, err := client.ListKeysLAN() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error: %s", err)) + return 1 + } + fmt.Println(keys) return 0 } diff --git a/consul/client.go b/consul/client.go index fb02cdcd0d..be18541017 100644 --- a/consul/client.go +++ b/consul/client.go @@ -206,8 +206,8 @@ func (c *Client) UserEvent(name string, payload []byte) error { return c.serf.UserEvent(userEventName(name), payload, false) } -// KeyManager returns the Serf keyring manager -func (c *Client) KeyManager() *serf.KeyManager { +// KeyManager returns the LAN Serf keyring manager +func (c *Client) KeyManagerLAN() *serf.KeyManager { return c.serf.KeyManager() } From f771f2ef92cbcc62250cd68b43a5f2bcc53ede0e Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 19:38:30 -0700 Subject: [PATCH 08/80] command: add option for -wan to keys command --- command/.keys.go.swp | Bin 0 -> 12288 bytes command/agent/rpc.go | 9 +++------ command/keys.go | 19 ++++++++++++++++--- 3 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 command/.keys.go.swp diff --git a/command/.keys.go.swp b/command/.keys.go.swp new file mode 100644 index 0000000000000000000000000000000000000000..370906d22e6ad9df77d1abb6dd771a7f967c2a1b GIT binary patch literal 12288 zcmeHNO^g&p6fRKEQIWqGPwJzM>kOHlS-^-{T@u{*L-<)_S&bnmHQhBc9lE>P>h4`f zL@#>Oc+o^jJfYDQ-+bvHY+?5^QR(nG$T{;Busy|2D{ zRTHW+@#MaPw7a!S;Im1HLHE|9FRa}n*4`n68OSK>D8sK-`;_g*!%jTDvlVDp2Fta7 zschKp>2_k=cF#}yS*PXdu-%tQ-*>f%+pdn1EWlS7%E)VdG~3|tKZ z8^o@iJ0{rTZf6^9-7 zzx@6G*G3`!1bziB0vCYqf$xA%fRBNXfMz4qODz1D^rsfDeFIfB`TEJOLaA_5hoKb-)@x0Ke6Q_!+ne{0Mvld<}dBdZxG@=;4E+kcn3HDtOGt@ z4<3Lq;Nm(V-T>x+^}y%XW88oTfct?hz$RcL@C(NE3xLP>9FKAJx#k%-Eow%knTg0b zC?nZZ3H5Z6_%Q`?pbV+VHG??ybwqr6#u42ta;fgp_Wih>&JWf!Xm$|kIPsHugOb$v zQIAduvFJO0mb8u|r@RdObO251dEY}};8wEZ+lL6h5R1*m z=v3MwQRsGj0TxYkW<@lbT$3lX4V_=&F7hZ>7U_VM#{>6NA1cDRy^bF#FQ01grfnKB zDBi0`b})v;Oe{|MSwR<*5d%W%c^Se9Nu)HdhUkL7vgD0&SPUklSk{U~K|Pii#~}kq zTFkNBCJ`?mH_UTBmdBbL7qzn1(c~1JSfPMEj(WI4@!TP5ZfnkO&17kurFFNJw`~Yf zLxnBN_k^hV-GaycQ2e$javZM0S2luF|EEPEz13RPx?$Qnj8dNlQJn{p%zW&12182C11x%~i)FDGoasO(ig_NACyT+V_>mQ~z>_CZn4?^d^vEJV zDwCxuspY-q0zur^F-%*L)VE_-u@`%XB{Kz~6h6?}4ZTOPf+wxR(jaK0lr$b3m0_)L z!=%7Ddt{S(Y0B=)>(;|s2Tw{%NGLOPk6iLAhQOn72163K*j zFgW@1;jqiBFEdwcVOOZsM>0FZ6DlWF1B-Xc6#?aTWi_M1JVUd@ZCH7!D`C%L=o{IS*oc3i(*t>WER_*69 zWd`ujM3{Ed!`DJUch1n6`Or^YtW!b1jrbj~=j0UwJN}x3%5A?uv?I`$HYwHdn^d|d MvF`CQ&ByD-f6l=}_5c6? literal 0 HcmV?d00001 diff --git a/command/agent/rpc.go b/command/agent/rpc.go index e5934fc8ad..09bf68fb87 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -638,14 +638,11 @@ func (i *AgentRPC) handleListKeysLAN(client *rpcClient, seq uint64) error { } func (i *AgentRPC) handleListKeysWAN(client *rpcClient, seq uint64) error { + resp, err := i.agent.ListKeysWAN() + header := responseHeader{ Seq: seq, - Error: "", - } - - resp, err := i.agent.ListKeysWAN() - if err != nil { - return err + Error: errToString(err), } return client.Send(&header, resp) diff --git a/command/keys.go b/command/keys.go index 4f063a2dd1..49bac18315 100644 --- a/command/keys.go +++ b/command/keys.go @@ -16,7 +16,7 @@ type KeysCommand struct { func (c *KeysCommand) Run(args []string) int { var installKey, useKey, removeKey string - var listKeys bool + var listKeys, wan bool cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } @@ -25,6 +25,7 @@ func (c *KeysCommand) Run(args []string) int { cmdFlags.StringVar(&useKey, "use", "", "use key") cmdFlags.StringVar(&removeKey, "remove", "", "remove key") cmdFlags.BoolVar(&listKeys, "list", false, "list keys") + cmdFlags.BoolVar(&wan, "wan", false, "operate on wan keys") rpcAddr := RPCAddrFlag(cmdFlags) if err := cmdFlags.Parse(args); err != nil { @@ -49,11 +50,20 @@ func (c *KeysCommand) Run(args []string) int { } if listKeys { - keys, err := client.ListKeysLAN() + var keys map[string]int + var err error + + if wan { + keys, err = client.ListKeysWAN() + } else { + keys, err = client.ListKeysLAN() + } + if err != nil { c.Ui.Error(fmt.Sprintf("Error: %s", err)) return 1 } + fmt.Println(keys) return 0 } @@ -70,7 +80,8 @@ func (c *KeysCommand) Run(args []string) int { return 0 } - return 0 + c.Ui.Output(c.Help()) + return 1 } func (c *KeysCommand) Help() string { @@ -94,6 +105,8 @@ Options: operation may only be performed on keys which are not currently the primary key. -list List all keys currently in use within the cluster. + -wan If talking with a server node, this flag can be used + to operate on the WAN gossip layer. -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. ` return strings.TrimSpace(helpText) From 9b4707a3297fd1edebfdc2bb42732aa1018daaca Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 20:09:35 -0700 Subject: [PATCH 09/80] command/keys: list keys working end-to-end --- command/.keys.go.swp | Bin 12288 -> 0 bytes command/agent/rpc.go | 47 ++++++++++++++++++++++++------------ command/agent/rpc_client.go | 16 ++++++------ command/keys.go | 24 +++++++++++++++--- 4 files changed, 59 insertions(+), 28 deletions(-) delete mode 100644 command/.keys.go.swp diff --git a/command/.keys.go.swp b/command/.keys.go.swp deleted file mode 100644 index 370906d22e6ad9df77d1abb6dd771a7f967c2a1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeHNO^g&p6fRKEQIWqGPwJzM>kOHlS-^-{T@u{*L-<)_S&bnmHQhBc9lE>P>h4`f zL@#>Oc+o^jJfYDQ-+bvHY+?5^QR(nG$T{;Busy|2D{ zRTHW+@#MaPw7a!S;Im1HLHE|9FRa}n*4`n68OSK>D8sK-`;_g*!%jTDvlVDp2Fta7 zschKp>2_k=cF#}yS*PXdu-%tQ-*>f%+pdn1EWlS7%E)VdG~3|tKZ z8^o@iJ0{rTZf6^9-7 zzx@6G*G3`!1bziB0vCYqf$xA%fRBNXfMz4qODz1D^rsfDeFIfB`TEJOLaA_5hoKb-)@x0Ke6Q_!+ne{0Mvld<}dBdZxG@=;4E+kcn3HDtOGt@ z4<3Lq;Nm(V-T>x+^}y%XW88oTfct?hz$RcL@C(NE3xLP>9FKAJx#k%-Eow%knTg0b zC?nZZ3H5Z6_%Q`?pbV+VHG??ybwqr6#u42ta;fgp_Wih>&JWf!Xm$|kIPsHugOb$v zQIAduvFJO0mb8u|r@RdObO251dEY}};8wEZ+lL6h5R1*m z=v3MwQRsGj0TxYkW<@lbT$3lX4V_=&F7hZ>7U_VM#{>6NA1cDRy^bF#FQ01grfnKB zDBi0`b})v;Oe{|MSwR<*5d%W%c^Se9Nu)HdhUkL7vgD0&SPUklSk{U~K|Pii#~}kq zTFkNBCJ`?mH_UTBmdBbL7qzn1(c~1JSfPMEj(WI4@!TP5ZfnkO&17kurFFNJw`~Yf zLxnBN_k^hV-GaycQ2e$javZM0S2luF|EEPEz13RPx?$Qnj8dNlQJn{p%zW&12182C11x%~i)FDGoasO(ig_NACyT+V_>mQ~z>_CZn4?^d^vEJV zDwCxuspY-q0zur^F-%*L)VE_-u@`%XB{Kz~6h6?}4ZTOPf+wxR(jaK0lr$b3m0_)L z!=%7Ddt{S(Y0B=)>(;|s2Tw{%NGLOPk6iLAhQOn72163K*j zFgW@1;jqiBFEdwcVOOZsM>0FZ6DlWF1B-Xc6#?aTWi_M1JVUd@ZCH7!D`C%L=o{IS*oc3i(*t>WER_*69 zWd`ujM3{Ed!`DJUch1n6`Or^YtW!b1jrbj~=j0UwJN}x3%5A?uv?I`$HYwHdn^d|d MvF`CQ&ByD-f6l=}_5c6? diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 09bf68fb87..e1007cb1d3 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -111,10 +111,11 @@ type joinResponse struct { Num int32 } -type keysResponse struct { +type keyResponse struct { Messages map[string]string NumNodes int NumResp int + NumErr int Keys map[string]int } @@ -625,27 +626,41 @@ func (i *AgentRPC) handleReload(client *rpcClient, seq uint64) error { } func (i *AgentRPC) handleListKeysLAN(client *rpcClient, seq uint64) error { - header := responseHeader{ - Seq: seq, - Error: "", - } - resp, err := i.agent.ListKeysLAN() - if err != nil { - return err - } - - return client.Send(&header, resp) -} - -func (i *AgentRPC) handleListKeysWAN(client *rpcClient, seq uint64) error { - resp, err := i.agent.ListKeysWAN() + queryResp, err := i.agent.ListKeysLAN() header := responseHeader{ Seq: seq, Error: errToString(err), } - return client.Send(&header, resp) + resp := keyResponse{ + Messages: queryResp.Messages, + Keys: queryResp.Keys, + NumResp: queryResp.NumResp, + NumErr: queryResp.NumErr, + NumNodes: queryResp.NumNodes, + } + + return client.Send(&header, &resp) +} + +func (i *AgentRPC) handleListKeysWAN(client *rpcClient, seq uint64) error { + queryResp, err := i.agent.ListKeysWAN() + + header := responseHeader{ + Seq: seq, + Error: errToString(err), + } + + resp := keyResponse{ + Messages: queryResp.Messages, + Keys: queryResp.Keys, + NumResp: queryResp.NumResp, + NumErr: queryResp.NumErr, + NumNodes: queryResp.NumNodes, + } + + return client.Send(&header, &resp) } // Used to convert an error to a string representation diff --git a/command/agent/rpc_client.go b/command/agent/rpc_client.go index d5ff5894e6..daa72e1c05 100644 --- a/command/agent/rpc_client.go +++ b/command/agent/rpc_client.go @@ -176,26 +176,26 @@ func (c *RPCClient) WANMembers() ([]Member, error) { return resp.Members, err } -func (c *RPCClient) ListKeysLAN() (map[string]int, error) { +func (c *RPCClient) ListKeysLAN() (map[string]int, int, map[string]string, error) { header := requestHeader{ Command: listKeysLANCommand, Seq: c.getSeq(), } - resp := make(map[string]int) + resp := new(keyResponse) - err := c.genericRPC(&header, nil, &resp) - return resp, err + err := c.genericRPC(&header, nil, resp) + return resp.Keys, resp.NumNodes, resp.Messages, err } -func (c *RPCClient) ListKeysWAN() (map[string]int, error) { +func (c *RPCClient) ListKeysWAN() (map[string]int, int, map[string]string, error) { header := requestHeader{ Command: listKeysWANCommand, Seq: c.getSeq(), } - resp := make(map[string]int) + resp := new(keyResponse) - err := c.genericRPC(&header, nil, &resp) - return resp, err + err := c.genericRPC(&header, nil, resp) + return resp.Keys, resp.NumNodes, resp.Messages, err } // Leave is used to trigger a graceful leave and shutdown diff --git a/command/keys.go b/command/keys.go index 49bac18315..069af85661 100644 --- a/command/keys.go +++ b/command/keys.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/mitchellh/cli" + "github.com/ryanuber/columnize" ) // KeysCommand is a Command implementation that handles querying, installing, @@ -51,20 +52,35 @@ func (c *KeysCommand) Run(args []string) int { if listKeys { var keys map[string]int + var numNodes int + var messages map[string]string var err error + var out []string if wan { - keys, err = client.ListKeysWAN() + keys, numNodes, messages, err = client.ListKeysWAN() } else { - keys, err = client.ListKeysLAN() + keys, numNodes, messages, err = client.ListKeysLAN() } if err != nil { - c.Ui.Error(fmt.Sprintf("Error: %s", err)) + for node, msg := range messages { + out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + } + c.Ui.Error(columnize.SimpleFormat(out)) + c.Ui.Error("") + c.Ui.Error(fmt.Sprintf("Failed gathering member keys: %s", err)) return 1 } - fmt.Println(keys) + c.Ui.Info("Keys gathered, listing cluster keys...") + c.Ui.Output("") + + for key, num := range keys { + out = append(out, fmt.Sprintf("%s | [%d/%d]", key, num, numNodes)) + } + c.Ui.Output(columnize.SimpleFormat(out)) + return 0 } From e4251a3372956e8c3d9b55e5b4644a190b2c4f58 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 20:13:11 -0700 Subject: [PATCH 10/80] agent: fix inversed lan/wan key listing --- command/agent/rpc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command/agent/rpc.go b/command/agent/rpc.go index e1007cb1d3..7030f6538d 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -390,10 +390,10 @@ func (i *AgentRPC) handleRequest(client *rpcClient, reqHeader *requestHeader) er return i.handleReload(client, seq) case listKeysLANCommand: - return i.handleListKeysWAN(client, seq) + return i.handleListKeysLAN(client, seq) case listKeysWANCommand: - return i.handleListKeysLAN(client, seq) + return i.handleListKeysWAN(client, seq) /* case installKeyLANCommand: From 0f611572cc558da87e377e5af29d5f6ff6ac065a Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 21:45:15 -0700 Subject: [PATCH 11/80] command/keys: fail fast if no actionable args were passed --- command/keys.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/command/keys.go b/command/keys.go index 069af85661..0373804cf7 100644 --- a/command/keys.go +++ b/command/keys.go @@ -33,13 +33,6 @@ func (c *KeysCommand) Run(args []string) int { return 1 } - client, err := RPCClient(*rpcAddr) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) - return 1 - } - defer client.Close() - // Only accept a single argument found := listKeys for _, arg := range []string{installKey, useKey, removeKey} { @@ -50,6 +43,19 @@ func (c *KeysCommand) Run(args []string) int { found = found || len(arg) > 0 } + // Fail fast if no actionable args were passed + if !found { + c.Ui.Error(c.Help()) + return 1 + } + + client, err := RPCClient(*rpcAddr) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) + return 1 + } + defer client.Close() + if listKeys { var keys map[string]int var numNodes int @@ -96,8 +102,8 @@ func (c *KeysCommand) Run(args []string) int { return 0 } - c.Ui.Output(c.Help()) - return 1 + // Should never make it here + return 0 } func (c *KeysCommand) Help() string { From 3a68d9e506004ba5de455c5fdca09229f4754099 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 22:27:17 -0700 Subject: [PATCH 12/80] command/keys: use PrefixedUi for keys command --- command/keys.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/command/keys.go b/command/keys.go index 0373804cf7..9be460063c 100644 --- a/command/keys.go +++ b/command/keys.go @@ -33,6 +33,13 @@ func (c *KeysCommand) Run(args []string) int { return 1 } + c.Ui = &cli.PrefixedUi{ + OutputPrefix: "", + InfoPrefix: "==> ", + ErrorPrefix: "", + Ui: c.Ui, + } + // Only accept a single argument found := listKeys for _, arg := range []string{installKey, useKey, removeKey} { @@ -57,6 +64,8 @@ func (c *KeysCommand) Run(args []string) int { defer client.Close() if listKeys { + c.Ui.Info("Asking all members for installed keys...") + var keys map[string]int var numNodes int var messages map[string]string From 7b6f3d6dcc23e8df1f91e0979d4ef65cec92a3c8 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 23:25:38 -0700 Subject: [PATCH 13/80] agent: install key command implemented --- command/agent/agent.go | 21 +++++++++++++++- command/agent/rpc.go | 50 ++++++++++++++++++++++++++----------- command/agent/rpc_client.go | 26 +++++++++++++++++++ command/keys.go | 40 +++++++++++++++++++++++------ 4 files changed, 113 insertions(+), 24 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index b219f18bc7..f1d52075f2 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -756,5 +756,24 @@ func (a *Agent) ListKeysWAN() (*serf.KeyResponse, error) { km := a.server.KeyManagerWAN() return km.ListKeys() } - return nil, nil + return nil, fmt.Errorf("WAN keyring not available on client node") +} + +// InstallKeyWAN installs a new WAN gossip encryption key on server nodes +func (a *Agent) InstallKeyWAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerWAN() + return km.InstallKey(key) + } + return nil, fmt.Errorf("WAN keyring not available on client node") +} + +// InstallKeyLAN installs a new LAN gossip encryption key on all nodes +func (a *Agent) InstallKeyLAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerLAN() + return km.InstallKey(key) + } + km := a.client.KeyManagerLAN() + return km.InstallKey(key) } diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 7030f6538d..5124a6d659 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -111,6 +111,10 @@ type joinResponse struct { Num int32 } +type keyRequest struct { + Key string +} + type keyResponse struct { Messages map[string]string NumNodes int @@ -389,19 +393,13 @@ func (i *AgentRPC) handleRequest(client *rpcClient, reqHeader *requestHeader) er case reloadCommand: return i.handleReload(client, seq) - case listKeysLANCommand: - return i.handleListKeysLAN(client, seq) + case listKeysLANCommand, listKeysWANCommand: + return i.handleListKeys(client, seq, command) - case listKeysWANCommand: - return i.handleListKeysWAN(client, seq) + case installKeyLANCommand, installKeyWANCommand: + return i.handleInstallKey(client, seq, command) /* - case installKeyLANCommand: - return i.handleInstallKeyLAN(client, seq) - - case installKeyWANCommand: - return i.handleInstallKeyWAN(client, seq) - case useKeyLANCommand: return i.handleUseKeyLAN(client, seq) @@ -625,8 +623,16 @@ func (i *AgentRPC) handleReload(client *rpcClient, seq uint64) error { return client.Send(&resp, nil) } -func (i *AgentRPC) handleListKeysLAN(client *rpcClient, seq uint64) error { - queryResp, err := i.agent.ListKeysLAN() +func (i *AgentRPC) handleListKeys(client *rpcClient, seq uint64, cmd string) error { + var queryResp *serf.KeyResponse + var err error + + switch cmd { + case listKeysWANCommand: + queryResp, err = i.agent.ListKeysWAN() + default: + queryResp, err = i.agent.ListKeysLAN() + } header := responseHeader{ Seq: seq, @@ -644,15 +650,29 @@ func (i *AgentRPC) handleListKeysLAN(client *rpcClient, seq uint64) error { return client.Send(&header, &resp) } -func (i *AgentRPC) handleListKeysWAN(client *rpcClient, seq uint64) error { - queryResp, err := i.agent.ListKeysWAN() +func (i *AgentRPC) handleInstallKey(client *rpcClient, seq uint64, cmd string) error { + var req keyRequest + var resp keyResponse + var queryResp *serf.KeyResponse + var err error + + if err = client.dec.Decode(&req); err != nil { + return fmt.Errorf("decode failed: %v", err) + } + + switch cmd { + case installKeyWANCommand: + queryResp, err = i.agent.InstallKeyWAN(req.Key) + default: + queryResp, err = i.agent.InstallKeyLAN(req.Key) + } header := responseHeader{ Seq: seq, Error: errToString(err), } - resp := keyResponse{ + resp = keyResponse{ Messages: queryResp.Messages, Keys: queryResp.Keys, NumResp: queryResp.NumResp, diff --git a/command/agent/rpc_client.go b/command/agent/rpc_client.go index daa72e1c05..c6b442f773 100644 --- a/command/agent/rpc_client.go +++ b/command/agent/rpc_client.go @@ -198,6 +198,32 @@ func (c *RPCClient) ListKeysWAN() (map[string]int, int, map[string]string, error return resp.Keys, resp.NumNodes, resp.Messages, err } +func (c *RPCClient) InstallKeyWAN(key string) (map[string]string, error) { + header := requestHeader{ + Command: installKeyWANCommand, + Seq: c.getSeq(), + } + + req := keyRequest{key} + + resp := new(keyResponse) + err := c.genericRPC(&header, &req, resp) + return resp.Messages, err +} + +func (c *RPCClient) InstallKeyLAN(key string) (map[string]string, error) { + header := requestHeader{ + Command: installKeyLANCommand, + Seq: c.getSeq(), + } + + req := keyRequest{key} + + resp := new(keyResponse) + err := c.genericRPC(&header, &req, resp) + return resp.Messages, err +} + // Leave is used to trigger a graceful leave and shutdown func (c *RPCClient) Leave() error { header := requestHeader{ diff --git a/command/keys.go b/command/keys.go index 9be460063c..2c0ce8f8ec 100644 --- a/command/keys.go +++ b/command/keys.go @@ -40,6 +40,10 @@ func (c *KeysCommand) Run(args []string) int { Ui: c.Ui, } + var out []string + var failures map[string]string + var err error + // Only accept a single argument found := listKeys for _, arg := range []string{installKey, useKey, removeKey} { @@ -68,21 +72,20 @@ func (c *KeysCommand) Run(args []string) int { var keys map[string]int var numNodes int - var messages map[string]string - var err error - var out []string if wan { - keys, numNodes, messages, err = client.ListKeysWAN() + keys, numNodes, failures, err = client.ListKeysWAN() } else { - keys, numNodes, messages, err = client.ListKeysLAN() + keys, numNodes, failures, err = client.ListKeysLAN() } if err != nil { - for node, msg := range messages { - out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + if len(failures) > 0 { + for node, msg := range failures { + out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + } + c.Ui.Error(columnize.SimpleFormat(out)) } - c.Ui.Error(columnize.SimpleFormat(out)) c.Ui.Error("") c.Ui.Error(fmt.Sprintf("Failed gathering member keys: %s", err)) return 1 @@ -100,6 +103,27 @@ func (c *KeysCommand) Run(args []string) int { } if installKey != "" { + if wan { + c.Ui.Info("Installing new WAN gossip encryption key...") + failures, err = client.InstallKeyWAN(installKey) + } else { + c.Ui.Info("Installing new LAN gossip encryption key...") + failures, err = client.InstallKeyLAN(installKey) + } + + if err != nil { + if len(failures) > 0 { + for node, msg := range failures { + out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + } + c.Ui.Error(columnize.SimpleFormat(out)) + } + c.Ui.Error("") + c.Ui.Error(fmt.Sprintf("Error installing key: %s", err)) + return 1 + } + + c.Ui.Info("Successfully installed key!") return 0 } From 222adc92a0b6bd5c68d184e43f30845b04adf239 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 23:30:55 -0700 Subject: [PATCH 14/80] command/keys: customize info message when listing keys --- command/keys.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command/keys.go b/command/keys.go index 2c0ce8f8ec..c6464146ce 100644 --- a/command/keys.go +++ b/command/keys.go @@ -68,14 +68,14 @@ func (c *KeysCommand) Run(args []string) int { defer client.Close() if listKeys { - c.Ui.Info("Asking all members for installed keys...") - var keys map[string]int var numNodes int if wan { + c.Ui.Info("Asking all WAN members for installed keys...") keys, numNodes, failures, err = client.ListKeysWAN() } else { + c.Ui.Info("Asking all LAN members for installed keys...") keys, numNodes, failures, err = client.ListKeysLAN() } From 1ac6b10aed2358a1be8d240a632adb85d5df02df Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 8 Sep 2014 23:55:02 -0700 Subject: [PATCH 15/80] command/keys: use key command implemented --- command/agent/agent.go | 19 +++++++++++++++++++ command/agent/rpc.go | 19 ++++++++++--------- command/agent/rpc_client.go | 25 ++++++++++++++----------- command/keys.go | 22 ++++++++++++++++++++++ 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index f1d52075f2..9caf815474 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -777,3 +777,22 @@ func (a *Agent) InstallKeyLAN(key string) (*serf.KeyResponse, error) { km := a.client.KeyManagerLAN() return km.InstallKey(key) } + +// UseKeyWAN changes the primary WAN gossip encryption key on server nodes +func (a *Agent) UseKeyWAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerWAN() + return km.UseKey(key) + } + return nil, fmt.Errorf("WAN keyring not available on client node") +} + +// UseKeyLAN changes the primary LAN gossip encryption key on all nodes +func (a *Agent) UseKeyLAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerLAN() + return km.UseKey(key) + } + km := a.client.KeyManagerLAN() + return km.UseKey(key) +} diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 5124a6d659..6c33a13d66 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -397,15 +397,12 @@ func (i *AgentRPC) handleRequest(client *rpcClient, reqHeader *requestHeader) er return i.handleListKeys(client, seq, command) case installKeyLANCommand, installKeyWANCommand: - return i.handleInstallKey(client, seq, command) + return i.handleGossipKeyChange(client, seq, command) + + case useKeyLANCommand, useKeyWANCommand: + return i.handleGossipKeyChange(client, seq, command) /* - case useKeyLANCommand: - return i.handleUseKeyLAN(client, seq) - - case useKeyWANCommand: - return i.handleUseKeyWAN(client, seq) - case removeKeyLANCommand: return i.handleRemoveKeyLAN(client, seq) @@ -650,7 +647,7 @@ func (i *AgentRPC) handleListKeys(client *rpcClient, seq uint64, cmd string) err return client.Send(&header, &resp) } -func (i *AgentRPC) handleInstallKey(client *rpcClient, seq uint64, cmd string) error { +func (i *AgentRPC) handleGossipKeyChange(client *rpcClient, seq uint64, cmd string) error { var req keyRequest var resp keyResponse var queryResp *serf.KeyResponse @@ -663,8 +660,12 @@ func (i *AgentRPC) handleInstallKey(client *rpcClient, seq uint64, cmd string) e switch cmd { case installKeyWANCommand: queryResp, err = i.agent.InstallKeyWAN(req.Key) - default: + case installKeyLANCommand: queryResp, err = i.agent.InstallKeyLAN(req.Key) + case useKeyWANCommand: + queryResp, err = i.agent.UseKeyWAN(req.Key) + case useKeyLANCommand: + queryResp, err = i.agent.UseKeyLAN(req.Key) } header := responseHeader{ diff --git a/command/agent/rpc_client.go b/command/agent/rpc_client.go index c6b442f773..5fe988b20a 100644 --- a/command/agent/rpc_client.go +++ b/command/agent/rpc_client.go @@ -199,21 +199,24 @@ func (c *RPCClient) ListKeysWAN() (map[string]int, int, map[string]string, error } func (c *RPCClient) InstallKeyWAN(key string) (map[string]string, error) { - header := requestHeader{ - Command: installKeyWANCommand, - Seq: c.getSeq(), - } - - req := keyRequest{key} - - resp := new(keyResponse) - err := c.genericRPC(&header, &req, resp) - return resp.Messages, err + return c.changeGossipKey(key, installKeyWANCommand) } func (c *RPCClient) InstallKeyLAN(key string) (map[string]string, error) { + return c.changeGossipKey(key, installKeyLANCommand) +} + +func (c *RPCClient) UseKeyWAN(key string) (map[string]string, error) { + return c.changeGossipKey(key, useKeyWANCommand) +} + +func (c *RPCClient) UseKeyLAN(key string) (map[string]string, error) { + return c.changeGossipKey(key, useKeyLANCommand) +} + +func (c *RPCClient) changeGossipKey(key, cmd string) (map[string]string, error) { header := requestHeader{ - Command: installKeyLANCommand, + Command: cmd, Seq: c.getSeq(), } diff --git a/command/keys.go b/command/keys.go index c6464146ce..e555517ea7 100644 --- a/command/keys.go +++ b/command/keys.go @@ -128,6 +128,28 @@ func (c *KeysCommand) Run(args []string) int { } if useKey != "" { + if wan { + c.Ui.Info("Changing primary encryption key on WAN members...") + failures, err = client.UseKeyWAN(useKey) + } else { + c.Ui.Info("Changing primary encryption key on LAN members...") + failures, err = client.UseKeyLAN(useKey) + } + + if err != nil { + if len(failures) > 0 { + for node, msg := range failures { + out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + } + c.Ui.Error(columnize.SimpleFormat(out)) + } + c.Ui.Error("") + c.Ui.Error(fmt.Sprintf("Error changing primary key: %s", err)) + return 1 + } + + c.Ui.Info("Successfully changed primary key!") + return 0 } From 46ce9e936fa9e12d8b598d7fc8a48db4128eccbd Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Tue, 9 Sep 2014 00:08:38 -0700 Subject: [PATCH 16/80] command/keys: remove key command implemented --- command/agent/agent.go | 19 +++++++++++++++++++ command/agent/rpc.go | 13 ++++++------- command/agent/rpc_client.go | 8 ++++++++ command/keys.go | 22 +++++++++++++++++++++- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 9caf815474..456415f964 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -796,3 +796,22 @@ func (a *Agent) UseKeyLAN(key string) (*serf.KeyResponse, error) { km := a.client.KeyManagerLAN() return km.UseKey(key) } + +// RemoveKeyWAN removes a WAN gossip encryption key on server nodes +func (a *Agent) RemoveKeyWAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerWAN() + return km.RemoveKey(key) + } + return nil, fmt.Errorf("WAN keyring not available on client node") +} + +// RemoveKeyLAN removes a LAN gossip encryption key on all nodes +func (a *Agent) RemoveKeyLAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerLAN() + return km.RemoveKey(key) + } + km := a.client.KeyManagerLAN() + return km.RemoveKey(key) +} diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 6c33a13d66..2382bee488 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -402,13 +402,8 @@ func (i *AgentRPC) handleRequest(client *rpcClient, reqHeader *requestHeader) er case useKeyLANCommand, useKeyWANCommand: return i.handleGossipKeyChange(client, seq, command) - /* - case removeKeyLANCommand: - return i.handleRemoveKeyLAN(client, seq) - - case removeKeyWANCommand: - return i.handleRemoveKeyWAN(client, seq) - */ + case removeKeyLANCommand, removeKeyWANCommand: + return i.handleGossipKeyChange(client, seq, command) default: respHeader := responseHeader{Seq: seq, Error: unsupportedCommand} @@ -666,6 +661,10 @@ func (i *AgentRPC) handleGossipKeyChange(client *rpcClient, seq uint64, cmd stri queryResp, err = i.agent.UseKeyWAN(req.Key) case useKeyLANCommand: queryResp, err = i.agent.UseKeyLAN(req.Key) + case removeKeyWANCommand: + queryResp, err = i.agent.RemoveKeyWAN(req.Key) + case removeKeyLANCommand: + queryResp, err = i.agent.RemoveKeyLAN(req.Key) } header := responseHeader{ diff --git a/command/agent/rpc_client.go b/command/agent/rpc_client.go index 5fe988b20a..5d82dc8fa8 100644 --- a/command/agent/rpc_client.go +++ b/command/agent/rpc_client.go @@ -214,6 +214,14 @@ func (c *RPCClient) UseKeyLAN(key string) (map[string]string, error) { return c.changeGossipKey(key, useKeyLANCommand) } +func (c *RPCClient) RemoveKeyWAN(key string) (map[string]string, error) { + return c.changeGossipKey(key, removeKeyWANCommand) +} + +func (c *RPCClient) RemoveKeyLAN(key string) (map[string]string, error) { + return c.changeGossipKey(key, removeKeyLANCommand) +} + func (c *RPCClient) changeGossipKey(key, cmd string) (map[string]string, error) { header := requestHeader{ Command: cmd, diff --git a/command/keys.go b/command/keys.go index e555517ea7..b998535763 100644 --- a/command/keys.go +++ b/command/keys.go @@ -149,11 +149,31 @@ func (c *KeysCommand) Run(args []string) int { } c.Ui.Info("Successfully changed primary key!") - return 0 } if removeKey != "" { + if wan { + c.Ui.Info("Removing key from WAN members...") + failures, err = client.RemoveKeyWAN(removeKey) + } else { + c.Ui.Info("Removing key from LAN members...") + failures, err = client.RemoveKeyLAN(removeKey) + } + + if err != nil { + if len(failures) > 0 { + for node, msg := range failures { + out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + } + c.Ui.Error(columnize.SimpleFormat(out)) + } + c.Ui.Error("") + c.Ui.Error(fmt.Sprintf("Error removing key: %s", err)) + return 1 + } + + c.Ui.Info("Successfully removed key!") return 0 } From 90de483871836f0beb1a1a2e3b7fa659f58cf310 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 10 Sep 2014 08:49:16 -0700 Subject: [PATCH 17/80] command/keys: begin tests --- command/agent/rpc.go | 4 ++++ command/keys_test.go | 33 +++++++++++++++++++++++++++++++++ command/util_test.go | 7 +++++++ 3 files changed, 44 insertions(+) create mode 100644 command/keys_test.go diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 2382bee488..bf9b42d527 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -665,6 +665,10 @@ func (i *AgentRPC) handleGossipKeyChange(client *rpcClient, seq uint64, cmd stri queryResp, err = i.agent.RemoveKeyWAN(req.Key) case removeKeyLANCommand: queryResp, err = i.agent.RemoveKeyLAN(req.Key) + default: + respHeader := responseHeader{Seq: seq, Error: unsupportedCommand} + client.Send(&respHeader, nil) + return fmt.Errorf("command '%s' not recognized", cmd) } header := responseHeader{ diff --git a/command/keys_test.go b/command/keys_test.go new file mode 100644 index 0000000000..0d1c464b4b --- /dev/null +++ b/command/keys_test.go @@ -0,0 +1,33 @@ +package command + +import ( + "strings" + "testing" + + "github.com/hashicorp/consul/command/agent" + "github.com/mitchellh/cli" +) + +func TestKeysCommand_implements(t *testing.T) { + var _ cli.Command = &KeysCommand{} +} + +func TestKeysCommand_list(t *testing.T) { + conf := agent.Config{EncryptKey: "HS5lJ+XuTlYKWaeGYyG+/A=="} + + a1 := testAgentWithConfig(&conf, t) + defer a1.Shutdown() + + ui := new(cli.MockUi) + c := &KeysCommand{Ui: ui} + args := []string{"-list", "-rpc-addr=" + a1.addr} + + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } + + if !strings.Contains(ui.OutputWriter.String(), conf.EncryptKey) { + t.Fatalf("bad: %#v", ui.OutputWriter.String()) + } +} diff --git a/command/util_test.go b/command/util_test.go index cd201139bc..388c1e62b5 100644 --- a/command/util_test.go +++ b/command/util_test.go @@ -39,6 +39,10 @@ func (a *agentWrapper) Shutdown() { } func testAgent(t *testing.T) *agentWrapper { + return testAgentWithConfig(nil, t) +} + +func testAgentWithConfig(c *agent.Config, t *testing.T) *agentWrapper { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("err: %s", err) @@ -48,6 +52,9 @@ func testAgent(t *testing.T) *agentWrapper { mult := io.MultiWriter(os.Stderr, lw) conf := nextConfig() + if c != nil { + conf = agent.MergeConfig(conf, c) + } dir, err := ioutil.TempDir("", "agent") if err != nil { From 5bf70bc605c1941c9b24bb2ad24c616557593cd8 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 10 Sep 2014 10:11:11 -0700 Subject: [PATCH 18/80] command/keys: adding more tests --- command/keys_test.go | 157 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 152 insertions(+), 5 deletions(-) diff --git a/command/keys_test.go b/command/keys_test.go index 0d1c464b4b..b326e7b3dc 100644 --- a/command/keys_test.go +++ b/command/keys_test.go @@ -12,22 +12,169 @@ func TestKeysCommand_implements(t *testing.T) { var _ cli.Command = &KeysCommand{} } -func TestKeysCommand_list(t *testing.T) { - conf := agent.Config{EncryptKey: "HS5lJ+XuTlYKWaeGYyG+/A=="} +func TestKeysCommandRun(t *testing.T) { + key1 := "HS5lJ+XuTlYKWaeGYyG+/A==" + key2 := "kZyFABeAmc64UMTrm9XuKA==" + key3 := "2k5VRlBIIKUPc1v77rsswg==" + // Begin with a single key + conf := agent.Config{EncryptKey: key1} a1 := testAgentWithConfig(&conf, t) defer a1.Shutdown() + // The keyring was initialized with only the provided key + out := listKeys(t, a1.addr, false) + if !strings.Contains(out, key1) { + t.Fatalf("bad: %#v", out) + } + if strings.Contains(out, key2) { + t.Fatalf("bad: %#v", out) + } + + // The key was installed on the WAN gossip layer also + out = listKeys(t, a1.addr, true) + if !strings.Contains(out, key1) { + t.Fatalf("bad: %#v", out) + } + if strings.Contains(out, key3) { + t.Fatalf("bad: %#v", out) + } + + // Install the second key onto the keyring + installKey(t, a1.addr, false, key2) + + // Both keys should be present + out = listKeys(t, a1.addr, false) + for _, key := range []string{key1, key2} { + if !strings.Contains(out, key) { + t.Fatalf("bad: %#v", out) + } + } + + // Second key should not be installed on WAN + out = listKeys(t, a1.addr, true) + if strings.Contains(out, key2) { + t.Fatalf("bad: %#v", out) + } + + // Change out the primary key + useKey(t, a1.addr, false, key2) + + // Remove the original key + removeKey(t, a1.addr, false, key1) + + // Make sure only the new key is present + out = listKeys(t, a1.addr, false) + if strings.Contains(out, key1) { + t.Fatalf("bad: %#v", out) + } + if !strings.Contains(out, key2) { + t.Fatalf("bad: %#v", out) + } + + // Original key still remains on WAN keyring + out = listKeys(t, a1.addr, true) + if !strings.Contains(out, key1) { + t.Fatalf("bad: %#v", out) + } + + // Install second key on WAN keyring + installKey(t, a1.addr, true, key3) + + // Two keys now present on WAN keyring + out = listKeys(t, a1.addr, true) + for _, key := range []string{key1, key3} { + if !strings.Contains(out, key) { + t.Fatalf("bad: %#v", out) + } + } + + // Change WAN primary key + useKey(t, a1.addr, true, key3) + + // Remove original key from WAN keyring + removeKey(t, a1.addr, true, key1) + + // Only new key should exist on WAN keyring + out = listKeys(t, a1.addr, true) + if !strings.Contains(out, key3) { + t.Fatalf("bad: %#v", out) + } + if strings.Contains(out, key1) { + t.Fatalf("bad: %#v", out) + } +} + +func TestKeysCommandRun_help(t *testing.T) { ui := new(cli.MockUi) c := &KeysCommand{Ui: ui} - args := []string{"-list", "-rpc-addr=" + a1.addr} + code := c.Run(nil) + if code != 1 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } + if !strings.Contains(ui.ErrorWriter.String(), "Usage:") { + t.Fatalf("bad: %#v", ui.ErrorWriter.String()) + } +} + +func listKeys(t *testing.T, addr string, wan bool) string { + ui := new(cli.MockUi) + c := &KeysCommand{Ui: ui} + + args := []string{"-list", "-rpc-addr=" + addr} + if wan { + args = append(args, "-wan") + } code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } - if !strings.Contains(ui.OutputWriter.String(), conf.EncryptKey) { - t.Fatalf("bad: %#v", ui.OutputWriter.String()) + return ui.OutputWriter.String() +} + +func installKey(t *testing.T, addr string, wan bool, key string) { + ui := new(cli.MockUi) + c := &KeysCommand{Ui: ui} + + args := []string{"-install=" + key, "-rpc-addr=" + addr} + if wan { + args = append(args, "-wan") + } + + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } +} + +func useKey(t *testing.T, addr string, wan bool, key string) { + ui := new(cli.MockUi) + c := &KeysCommand{Ui: ui} + + args := []string{"-use=" + key, "-rpc-addr=" + addr} + if wan { + args = append(args, "-wan") + } + + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } +} + +func removeKey(t *testing.T, addr string, wan bool, key string) { + ui := new(cli.MockUi) + c := &KeysCommand{Ui: ui} + + args := []string{"-remove=" + key, "-rpc-addr=" + addr} + if wan { + args = append(args, "-wan") + } + + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } } From e78c235dcffe6fddf5ed2c69afc46c75be214062 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 10 Sep 2014 10:20:40 -0700 Subject: [PATCH 19/80] command/keys: test network connection failure --- command/keys_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/command/keys_test.go b/command/keys_test.go index b326e7b3dc..903d13b19f 100644 --- a/command/keys_test.go +++ b/command/keys_test.go @@ -112,11 +112,26 @@ func TestKeysCommandRun_help(t *testing.T) { if code != 1 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } + + // Test that we didn't actually try to dial the RPC server. if !strings.Contains(ui.ErrorWriter.String(), "Usage:") { t.Fatalf("bad: %#v", ui.ErrorWriter.String()) } } +func TestKeysCommandRun_failedConnection(t *testing.T) { + ui := new(cli.MockUi) + c := &KeysCommand{Ui: ui} + args := []string{"-list", "-rpc-addr=127.0.0.1:0"} + code := c.Run(args) + if code != 1 { + t.Fatalf("bad: %d, %#v", code, ui.ErrorWriter.String()) + } + if !strings.Contains(ui.ErrorWriter.String(), "dial") { + t.Fatalf("bad: %#v", ui.OutputWriter.String()) + } +} + func listKeys(t *testing.T, addr string, wan bool) string { ui := new(cli.MockUi) c := &KeysCommand{Ui: ui} From 2280434e1627d131ed6716cc2814e5758dfc6b59 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 10 Sep 2014 21:56:31 -0700 Subject: [PATCH 20/80] agent: add rpc tests for listing lan/wan gossip keys --- command/agent/rpc_client_test.go | 84 ++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/command/agent/rpc_client_test.go b/command/agent/rpc_client_test.go index 18825613c1..b290a6dee5 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -30,6 +30,10 @@ func (r *rpcParts) Close() { // testRPCClient returns an RPCClient connected to an RPC server that // serves only this connection. func testRPCClient(t *testing.T) *rpcParts { + return testRPCClientWithConfig(t, nil) +} + +func testRPCClientWithConfig(t *testing.T, c *Config) *rpcParts { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("err: %s", err) @@ -39,6 +43,10 @@ func testRPCClient(t *testing.T) *rpcParts { mult := io.MultiWriter(os.Stderr, lw) conf := nextConfig() + if c != nil { + conf = MergeConfig(conf, c) + } + dir, agent := makeAgentLog(t, conf, mult) rpc := NewAgentRPC(agent, l, mult, lw) @@ -273,3 +281,79 @@ OUTER2: t.Fatalf("should log joining") } } + +func TestRPCClientListKeysLAN(t *testing.T) { + key1 := "tbLJg26ZJyJ9pK3qhc9jig==" + conf := Config{EncryptKey: key1} + p1 := testRPCClientWithConfig(t, &conf) + defer p1.Close() + + keys, numNodes, messages, err := p1.client.ListKeysLAN() + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, ok := keys[key1]; !ok { + t.Fatalf("bad: %#v", keys) + } + + if keys[key1] != 1 { + t.Fatalf("bad: %#v", keys) + } + + if numNodes != 1 { + t.Fatalf("bad: %d", numNodes) + } + + if len(messages) != 0 { + t.Fatalf("bad: %#v", messages) + } +} + +func TestRPCClientListKeysWAN(t *testing.T) { + key1 := "tbLJg26ZJyJ9pK3qhc9jig==" + conf := Config{EncryptKey: key1} + p1 := testRPCClientWithConfig(t, &conf) + defer p1.Close() + + keys, numNodes, messages, err := p1.client.ListKeysWAN() + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, ok := keys[key1]; !ok { + t.Fatalf("bad: %#v", keys) + } + + if keys[key1] != 1 { + t.Fatalf("bad: %#v", keys) + } + + if numNodes != 1 { + t.Fatalf("bad: %d", numNodes) + } + + if len(messages) != 0 { + t.Fatalf("bad: %#v", messages) + } +} + +func TestRPCClientListKeysLAN_encryptionDisabled(t *testing.T) { + p1 := testRPCClient(t) + defer p1.Close() + + _, _, _, err := p1.client.ListKeysLAN() + if err == nil { + t.Fatalf("no error listing keys with encryption disabled") + } +} + +func TestRPCClientListKeysWAN_encryptionDisabled(t *testing.T) { + p1 := testRPCClient(t) + defer p1.Close() + + _, _, _, err := p1.client.ListKeysWAN() + if err == nil { + t.Fatalf("no error listing keys with encryption disabled") + } +} From df68820645a0cc41c7289509156f26e19ce78678 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 10 Sep 2014 22:51:33 -0700 Subject: [PATCH 21/80] agent: install/use/remove key tests --- command/agent/rpc_client_test.go | 169 +++++++++++++++++++++++-------- 1 file changed, 129 insertions(+), 40 deletions(-) diff --git a/command/agent/rpc_client_test.go b/command/agent/rpc_client_test.go index b290a6dee5..a042d85e85 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -282,78 +282,167 @@ OUTER2: } } -func TestRPCClientListKeysLAN(t *testing.T) { +func TestRPCClientListKeys(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" conf := Config{EncryptKey: key1} p1 := testRPCClientWithConfig(t, &conf) defer p1.Close() - keys, numNodes, messages, err := p1.client.ListKeysLAN() - if err != nil { - t.Fatalf("err: %s", err) - } - + // Check WAN keys + keys := listKeys(t, p1.client, false) if _, ok := keys[key1]; !ok { t.Fatalf("bad: %#v", keys) } - if keys[key1] != 1 { + // Check LAN keys + keys = listKeys(t, p1.client, true) + if _, ok := keys[key1]; !ok { t.Fatalf("bad: %#v", keys) } - - if numNodes != 1 { - t.Fatalf("bad: %d", numNodes) - } - - if len(messages) != 0 { - t.Fatalf("bad: %#v", messages) - } } -func TestRPCClientListKeysWAN(t *testing.T) { +func TestRPCClientInstallKey(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" + key2 := "xAEZ3uVHRMZD9GcYMZaRQw==" conf := Config{EncryptKey: key1} p1 := testRPCClientWithConfig(t, &conf) defer p1.Close() - keys, numNodes, messages, err := p1.client.ListKeysWAN() + // Test WAN keys + keys := listKeys(t, p1.client, true) + if _, ok := keys[key2]; ok { + t.Fatalf("bad: %#v", keys) + } + + installKey(t, p1.client, key2, true) + + keys = listKeys(t, p1.client, true) + if _, ok := keys[key2]; !ok { + t.Fatalf("bad: %#v", keys) + } + + // Test LAN keys + keys = listKeys(t, p1.client, false) + if _, ok := keys[key2]; ok { + t.Fatalf("bad: %#v", keys) + } + + installKey(t, p1.client, key2, false) + + keys = listKeys(t, p1.client, false) + if _, ok := keys[key2]; !ok { + t.Fatalf("bad: %#v", keys) + } +} + +func TestRPCClientRotateKey(t *testing.T) { + key1 := "tbLJg26ZJyJ9pK3qhc9jig==" + key2 := "xAEZ3uVHRMZD9GcYMZaRQw==" + conf := Config{EncryptKey: key1} + p1 := testRPCClientWithConfig(t, &conf) + defer p1.Close() + + // Test WAN keys + keys := listKeys(t, p1.client, true) + if _, ok := keys[key2]; ok { + t.Fatalf("bad: %#v", keys) + } + + installKey(t, p1.client, key2, true) + useKey(t, p1.client, key2, true) + removeKey(t, p1.client, key1, true) + + keys = listKeys(t, p1.client, true) + if _, ok := keys[key1]; ok { + t.Fatalf("bad: %#v", keys) + } + if _, ok := keys[key2]; !ok { + t.Fatalf("bad: %#v", keys) + } + + // Test LAN keys + keys = listKeys(t, p1.client, false) + if _, ok := keys[key2]; ok { + t.Fatalf("bad: %#v", keys) + } + + installKey(t, p1.client, key2, false) + useKey(t, p1.client, key2, false) + removeKey(t, p1.client, key1, false) + + keys = listKeys(t, p1.client, false) + if _, ok := keys[key1]; ok { + t.Fatalf("bad: %#v", keys) + } + if _, ok := keys[key2]; !ok { + t.Fatalf("bad: %#v", keys) + } +} + +func TestRPCClientKeyOperation_encryptionDisabled(t *testing.T) { + p1 := testRPCClient(t) + defer p1.Close() + + _, _, failures, err := p1.client.ListKeysLAN() + if err == nil { + t.Fatalf("no error listing keys with encryption disabled") + } + + if len(failures) != 1 { + t.Fatalf("bad: %#v", failures) + } +} + +func listKeys(t *testing.T, c *RPCClient, wan bool) (keys map[string]int) { + var err error + + if wan { + keys, _, _, err = c.ListKeysWAN() + } else { + keys, _, _, err = c.ListKeysLAN() + } if err != nil { t.Fatalf("err: %s", err) } - if _, ok := keys[key1]; !ok { - t.Fatalf("bad: %#v", keys) - } + return +} - if keys[key1] != 1 { - t.Fatalf("bad: %#v", keys) - } +func installKey(t *testing.T, c *RPCClient, key string, wan bool) { + var err error - if numNodes != 1 { - t.Fatalf("bad: %d", numNodes) + if wan { + _, err = c.InstallKeyWAN(key) + } else { + _, err = c.InstallKeyLAN(key) } - - if len(messages) != 0 { - t.Fatalf("bad: %#v", messages) + if err != nil { + t.Fatalf("err: %s", err) } } -func TestRPCClientListKeysLAN_encryptionDisabled(t *testing.T) { - p1 := testRPCClient(t) - defer p1.Close() +func useKey(t *testing.T, c *RPCClient, key string, wan bool) { + var err error - _, _, _, err := p1.client.ListKeysLAN() - if err == nil { - t.Fatalf("no error listing keys with encryption disabled") + if wan { + _, err = c.UseKeyWAN(key) + } else { + _, err = c.UseKeyLAN(key) + } + if err != nil { + t.Fatalf("err: %s", err) } } -func TestRPCClientListKeysWAN_encryptionDisabled(t *testing.T) { - p1 := testRPCClient(t) - defer p1.Close() +func removeKey(t *testing.T, c *RPCClient, key string, wan bool) { + var err error - _, _, _, err := p1.client.ListKeysWAN() - if err == nil { - t.Fatalf("no error listing keys with encryption disabled") + if wan { + _, err = c.RemoveKeyWAN(key) + } else { + _, err = c.RemoveKeyLAN(key) + } + if err != nil { + t.Fatalf("err: %s", err) } } From 6b41e9896401cccee3193a3c8696734ec90e9967 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 11 Sep 2014 11:19:48 -0700 Subject: [PATCH 22/80] website: document keys command --- .../source/docs/commands/keys.html.markdown | 58 +++++++++++++++++++ website/source/layouts/docs.erb | 4 ++ 2 files changed, 62 insertions(+) create mode 100644 website/source/docs/commands/keys.html.markdown diff --git a/website/source/docs/commands/keys.html.markdown b/website/source/docs/commands/keys.html.markdown new file mode 100644 index 0000000000..785025b348 --- /dev/null +++ b/website/source/docs/commands/keys.html.markdown @@ -0,0 +1,58 @@ +--- +layout: "docs" +page_title: "Commands: Keys" +sidebar_current: "docs-commands-keys" +--- + +# Consul Keys + +Command: `consul keys` + +The `keys` command is used to examine and modify the encryption keys used in +Consul's [Gossip Pools](/docs/internals/gossip.html). It is capable of +distributing new encryption keys to the cluster, revoking old encryption keys, +and changing the key used by the cluster to encrypt messages. + +Because Consul utilizes multiple gossip pools, this command will operate on only +a single pool at a time. The pool can be specified using the arguments +documented below. + +Consul allows multiple encryption keys to be in use simultaneously. This is +intended to provide a transition state while the cluster converges. It is the +responsibility of the operator to ensure that only the required encryption keys +are installed on the cluster. You can ensure that a key is not installed using +the `-list` and `-remove` options. + +By default, modifications made using this command will be persisted in the +Consul agent's data directory. This functionality can be altered via the +[Agent Configuration](/docs/agent/options.html). + +All variations of the keys command will return 0 if all nodes reply and there +are no errors. If any node fails to reply or reports failure, the exit code will +be 1. + +## Usage + +Usage: `consul keys [options]` + +Exactly one of `-list`, `-install`, `-remove`, or `-update` must be provided. + +The list of available flags are: + +* `-install` - Install a new encryption key. This will broadcast the new key to + all members in the cluster. + +* `-use` - Change the primary encryption key, which is used to encrypt messages. + The key must already be installed before this operation can succeed. + +* `-remove` - Remove the given key from the cluster. This operation may only be + performed on keys which are not currently the primary key. + +* `-list` - List all keys currently in use within the cluster. + +* `-wan` - If talking with a server node, this flag can be used to operate on + the WAN gossip layer. By default, this command operates on the LAN layer. More + information about the different gossip layers can be found on the + [gossip protocol](/docs/internals/gossip.html) page. + +* `-rpc-addr` - RPC address of the Consul agent. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 52d171e4dd..6c48f1dbbd 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -79,6 +79,10 @@ keygen + > + keys + + > leave From cea2b49c15389a254855f7f8956f5e3ef71822bf Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 11 Sep 2014 11:34:58 -0700 Subject: [PATCH 23/80] website: update consul keys documentation --- website/source/docs/agent/options.html.markdown | 7 +++++++ website/source/docs/commands/keys.html.markdown | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown index 01149ef590..2a60a4309c 100644 --- a/website/source/docs/agent/options.html.markdown +++ b/website/source/docs/agent/options.html.markdown @@ -85,10 +85,17 @@ The options below are all specified on the command-line. it relies on proper configuration. Nodes in the same datacenter should be on a single LAN. +* `-persist-keyring` - This flag enables persistence of changes to the + encryption keys used in the gossip pools. By default, any modifications to + the keyring via the [consul keys](/docs/command/keys.html) command will be + lost when the agent shuts down. + * `-encrypt` - Specifies the secret key to use for encryption of Consul network traffic. This key must be 16-bytes that are base64 encoded. The easiest way to create an encryption key is to use `consul keygen`. All nodes within a cluster must share the same encryption key to communicate. + If keyring persistence is enabled, the given key will only be used if there is + no pre-existing keyring. Otherwise, Consul will emit a warning and continue. * `-join` - Address of another agent to join upon starting up. This can be specified multiple times to specify multiple agents to join. If Consul is diff --git a/website/source/docs/commands/keys.html.markdown b/website/source/docs/commands/keys.html.markdown index 785025b348..beb9f38945 100644 --- a/website/source/docs/commands/keys.html.markdown +++ b/website/source/docs/commands/keys.html.markdown @@ -23,8 +23,9 @@ responsibility of the operator to ensure that only the required encryption keys are installed on the cluster. You can ensure that a key is not installed using the `-list` and `-remove` options. -By default, modifications made using this command will be persisted in the -Consul agent's data directory. This functionality can be altered via the +By default, modifications made using this command will **NOT** be persisted, and +will be lost when the agent shuts down. You can alter this behavior via the +`-persist-keyring` option in the [Agent Configuration](/docs/agent/options.html). All variations of the keys command will return 0 if all nodes reply and there From fcb09614365819cf55d170cc074d13b86aacf61f Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 11 Sep 2014 19:52:16 -0700 Subject: [PATCH 24/80] agent: refactor keyring loader --- command/agent/agent.go | 44 ++++++++++++++++++++++++---------------- command/agent/command.go | 7 ++----- command/agent/config.go | 15 +++++++++----- consul/config.go | 5 ----- 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 456415f964..5453f4ad95 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -115,7 +115,7 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { agent.state.Init(config, agent.logger) // Setup encryption keyring files - if !config.DisableKeyring && config.EncryptKey != "" { + if config.PersistKeyring && config.EncryptKey != "" { serfDir := filepath.Join(config.DataDir, "serf") if err := os.MkdirAll(serfDir, 0700); err != nil { return nil, err @@ -200,20 +200,17 @@ func (a *Agent) consulConfig() *consul.Config { if a.config.DataDir != "" { base.DataDir = a.config.DataDir } - if a.config.EncryptKey != "" && a.config.DisableKeyring { + if a.config.EncryptKey != "" && !a.config.PersistKeyring { key, _ := a.config.EncryptBytes() base.SerfLANConfig.MemberlistConfig.SecretKey = key base.SerfWANConfig.MemberlistConfig.SecretKey = key } - if !a.config.DisableKeyring { + if a.config.PersistKeyring { lanKeyring := filepath.Join(base.DataDir, "serf", "keyring_lan") wanKeyring := filepath.Join(base.DataDir, "serf", "keyring_wan") base.SerfLANConfig.KeyringFile = lanKeyring base.SerfWANConfig.KeyringFile = wanKeyring - - base.SerfLANConfig.MemberlistConfig.Keyring = loadKeyringFile(lanKeyring) - base.SerfWANConfig.MemberlistConfig.Keyring = loadKeyringFile(wanKeyring) } if a.config.NodeName != "" { base.NodeName = a.config.NodeName @@ -303,9 +300,6 @@ func (a *Agent) consulConfig() *consul.Config { } } - // Setup gossip keyring configuration - base.DisableKeyring = a.config.DisableKeyring - // Setup the loggers base.LogOutput = a.logOutput return base @@ -313,6 +307,16 @@ func (a *Agent) consulConfig() *consul.Config { // setupServer is used to initialize the Consul server func (a *Agent) setupServer() error { + config := a.consulConfig() + + // Load a keyring file, if present + if err := loadKeyringFile(config.SerfLANConfig); err != nil { + return err + } + if err := loadKeyringFile(config.SerfWANConfig); err != nil { + return err + } + server, err := consul.NewServer(a.consulConfig()) if err != nil { return fmt.Errorf("Failed to start Consul server: %v", err) @@ -703,21 +707,25 @@ func (a *Agent) deletePid() error { } // loadKeyringFile will load a keyring out of a file -func loadKeyringFile(keyringFile string) *memberlist.Keyring { - if _, err := os.Stat(keyringFile); err != nil { +func loadKeyringFile(c *serf.Config) error { + if c.KeyringFile == "" { return nil } + if _, err := os.Stat(c.KeyringFile); err != nil { + return err + } + // Read in the keyring file data - keyringData, err := ioutil.ReadFile(keyringFile) + keyringData, err := ioutil.ReadFile(c.KeyringFile) if err != nil { - return nil + return err } // Decode keyring JSON keys := make([]string, 0) if err := json.Unmarshal(keyringData, &keys); err != nil { - return nil + return err } // Decode base64 values @@ -725,7 +733,7 @@ func loadKeyringFile(keyringFile string) *memberlist.Keyring { for i, key := range keys { keyBytes, err := base64.StdEncoding.DecodeString(key) if err != nil { - return nil + return err } keysDecoded[i] = keyBytes } @@ -733,11 +741,13 @@ func loadKeyringFile(keyringFile string) *memberlist.Keyring { // Create the keyring keyring, err := memberlist.NewKeyring(keysDecoded, keysDecoded[0]) if err != nil { - return nil + return err } + c.MemberlistConfig.Keyring = keyring + // Success! - return keyring + return nil } // ListKeysLAN returns the keys installed on the LAN gossip pool diff --git a/command/agent/command.go b/command/agent/command.go index 649cded48f..6f1da6cf7a 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -67,7 +67,7 @@ func (c *Command) readConfig() *Config { cmdFlags.StringVar(&cmdConfig.UiDir, "ui-dir", "", "path to the web UI directory") cmdFlags.StringVar(&cmdConfig.PidFile, "pid-file", "", "path to file to store PID") cmdFlags.StringVar(&cmdConfig.EncryptKey, "encrypt", "", "gossip encryption key") - cmdFlags.BoolVar(&cmdConfig.DisableKeyring, "disable-keyring", false, "disable use of encryption keyring") + cmdFlags.BoolVar(&cmdConfig.PersistKeyring, "persist-keyring", false, "persist keyring changes") cmdFlags.BoolVar(&cmdConfig.Server, "server", false, "run agent as server") cmdFlags.BoolVar(&cmdConfig.Bootstrap, "bootstrap", false, "enable server bootstrap mode") @@ -802,10 +802,6 @@ Options: -data-dir=path Path to a data directory to store agent state -dc=east-aws Datacenter of the agent -encrypt=key Provides the gossip encryption key - -disable-keyring Disables the use of an encryption keyring. The - Default behavior is to persist encryption keys using - a keyring file, and reload the keys on subsequent - starts. This argument disables keyring persistence. -join=1.2.3.4 Address of an agent to join at start time. Can be specified multiple times. -join-wan=1.2.3.4 Address of an agent to join -wan at start time. @@ -823,6 +819,7 @@ Options: -log-level=info Log level of the agent. -node=hostname Name of this node. Must be unique in the cluster -protocol=N Sets the protocol version. Defaults to latest. + -persist-keyring Enable encryption keyring persistence. -rejoin Ignores a previous leave and attempts to rejoin the cluster. -server Switches agent to server mode. -syslog Enables logging to syslog diff --git a/command/agent/config.go b/command/agent/config.go index 2fad1db399..cb35961674 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -104,13 +104,13 @@ type Config struct { // recursors array. DNSRecursor string `mapstructure:"recursor"` - // Disable use of an encryption keyring. - DisableKeyring bool `mapstructure:"disable_keyring"` - // DNSRecursors can be set to allow the DNS servers to recursively // resolve non-consul domains DNSRecursors []string `mapstructure:"recursors"` + // Disable use of an encryption keyring. + DisableKeyring bool `mapstructure:"disable_keyring"` + // DNS configuration DNSConfig DNSConfig `mapstructure:"dns_config"` @@ -150,6 +150,11 @@ type Config struct { // the TERM signal. Defaults false. This can be changed on reload. LeaveOnTerm bool `mapstructure:"leave_on_terminate"` + // Enable keyring persistence. There are currently two keyrings; one for + // the LAN serf cluster and the other for the WAN. Each will maintain its + // own keyring file in the agent's data directory. + PersistKeyring bool `mapstructure:"persist_keyring"` + // SkipLeaveOnInt controls if Serf skips a graceful leave when receiving // the INT signal. Defaults false. This can be changed on reload. SkipLeaveOnInt bool `mapstructure:"skip_leave_on_interrupt"` @@ -691,8 +696,8 @@ func MergeConfig(a, b *Config) *Config { if b.EncryptKey != "" { result.EncryptKey = b.EncryptKey } - if b.DisableKeyring { - result.DisableKeyring = true + if b.PersistKeyring { + result.PersistKeyring = true } if b.LogLevel != "" { result.LogLevel = b.LogLevel diff --git a/consul/config.go b/consul/config.go index 10acaab2c6..9cb1944cbc 100644 --- a/consul/config.go +++ b/consul/config.go @@ -165,11 +165,6 @@ type Config struct { // UserEventHandler callback can be used to handle incoming // user events. This function should not block. UserEventHandler func(serf.UserEvent) - - // DisableKeyring is used to disable persisting the encryption keyring to - // filesystem. By default, if encryption is enabled, Consul will create a - // file inside of the DataDir to keep track of changes made to the ring. - DisableKeyring bool } // CheckVersion is used to check if the ProtocolVersion is valid From b6037ef323f57d51d5cfbf0a6eca8b73ce1595eb Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 11 Sep 2014 22:28:23 -0700 Subject: [PATCH 25/80] agent: clean up keyring file implementation --- command/agent/agent.go | 79 ++++++++++++++++++++++++---------------- command/agent/command.go | 7 +++- command/agent/config.go | 9 +++-- 3 files changed, 58 insertions(+), 37 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 5453f4ad95..781ecc90a0 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -19,6 +19,11 @@ import ( "github.com/hashicorp/serf/serf" ) +const ( + serfLANKeyring = "serf/local.keyring" + serfWANKeyring = "serf/remote.keyring" +) + /* The agent is the long running process that is run on every machine. It exposes an RPC interface that is used by the CLI to control the @@ -116,37 +121,14 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { // Setup encryption keyring files if config.PersistKeyring && config.EncryptKey != "" { - serfDir := filepath.Join(config.DataDir, "serf") - if err := os.MkdirAll(serfDir, 0700); err != nil { - return nil, err - } - - keys := []string{config.EncryptKey} - keyringBytes, err := json.MarshalIndent(keys, "", " ") - if err != nil { - return nil, err - } - - paths := []string{ - filepath.Join(serfDir, "keyring_lan"), - filepath.Join(serfDir, "keyring_wan"), - } - - for _, path := range paths { - if _, err := os.Stat(path); err == nil { - continue - } - fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return nil, err - } - defer fh.Close() - - if _, err := fh.Write(keyringBytes); err != nil { - os.Remove(path) + if config.Server { + if err := agent.initKeyringFile(serfWANKeyring); err != nil { return nil, err } } + if err := agent.initKeyringFile(serfLANKeyring); err != nil { + return nil, err + } } // Setup either the client or the server @@ -206,9 +188,8 @@ func (a *Agent) consulConfig() *consul.Config { base.SerfWANConfig.MemberlistConfig.SecretKey = key } if a.config.PersistKeyring { - lanKeyring := filepath.Join(base.DataDir, "serf", "keyring_lan") - wanKeyring := filepath.Join(base.DataDir, "serf", "keyring_wan") - + lanKeyring := filepath.Join(base.DataDir, serfLANKeyring) + wanKeyring := filepath.Join(base.DataDir, serfWANKeyring) base.SerfLANConfig.KeyringFile = lanKeyring base.SerfWANConfig.KeyringFile = wanKeyring } @@ -825,3 +806,39 @@ func (a *Agent) RemoveKeyLAN(key string) (*serf.KeyResponse, error) { km := a.client.KeyManagerLAN() return km.RemoveKey(key) } + +// initKeyringFile is used to create and initialize a persistent keyring file +// for gossip encryption keys. It is used at agent startup to dump the initial +// encryption key into a keyfile if persistence is enabled. +func (a *Agent) initKeyringFile(path string) error { + serfDir := filepath.Join(a.config.DataDir, "serf") + if err := os.MkdirAll(serfDir, 0700); err != nil { + return err + } + + keys := []string{a.config.EncryptKey} + keyringBytes, err := json.MarshalIndent(keys, "", " ") + if err != nil { + return err + } + + keyringFile := filepath.Join(a.config.DataDir, path) + + // If the keyring file already exists, don't re-initialize + if _, err := os.Stat(keyringFile); err == nil { + return nil + } + + fh, err := os.OpenFile(keyringFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer fh.Close() + + if _, err := fh.Write(keyringBytes); err != nil { + os.Remove(keyringFile) + return err + } + + return nil +} diff --git a/command/agent/command.go b/command/agent/command.go index 6f1da6cf7a..94b6eacd30 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -220,7 +220,7 @@ func (c *Command) readConfig() *Config { } // Warn if an encryption key is passed while a keyring already exists - if config.EncryptKey != "" && config.CheckKeyringFiles() { + if config.EncryptKey != "" && (config.PersistKeyring && config.CheckKeyringFiles()) { c.Ui.Error(fmt.Sprintf( "WARNING: Keyring already exists, ignoring new key %s", config.EncryptKey)) @@ -594,7 +594,10 @@ func (c *Command) Run(args []string) int { } // Determine if gossip is encrypted - gossipEncrypted := (config.EncryptKey != "" || config.CheckKeyringFiles()) + gossipEncrypted := false + if config.EncryptKey != "" || (config.PersistKeyring && config.CheckKeyringFiles()) { + gossipEncrypted = true + } // Let the agent know we've finished registration c.agent.StartSync() diff --git a/command/agent/config.go b/command/agent/config.go index cb35961674..e4c1ee73aa 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -418,12 +418,13 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { // CheckKeyringFiles checks for existence of the keyring files for Serf func (c *Config) CheckKeyringFiles() bool { - serfDir := filepath.Join(c.DataDir, "serf") - if _, err := os.Stat(filepath.Join(serfDir, "keyring_lan")); err != nil { + if _, err := os.Stat(filepath.Join(c.DataDir, serfLANKeyring)); err != nil { return false } - if _, err := os.Stat(filepath.Join(serfDir, "keyring_wan")); err != nil { - return false + if c.Server { + if _, err := os.Stat(filepath.Join(c.DataDir, serfWANKeyring)); err != nil { + return false + } } return true } From 0952535e3316bfa464cb759686568aa14926be3e Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 11 Sep 2014 22:46:57 -0700 Subject: [PATCH 26/80] agent: fix keyring loading when config is passed off --- command/agent/agent.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 781ecc90a0..06aad6d433 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -298,7 +298,7 @@ func (a *Agent) setupServer() error { return err } - server, err := consul.NewServer(a.consulConfig()) + server, err := consul.NewServer(config) if err != nil { return fmt.Errorf("Failed to start Consul server: %v", err) } @@ -308,7 +308,14 @@ func (a *Agent) setupServer() error { // setupClient is used to initialize the Consul client func (a *Agent) setupClient() error { - client, err := consul.NewClient(a.consulConfig()) + config := a.consulConfig() + + // Load a keyring file, if present + if err := loadKeyringFile(config.SerfLANConfig); err != nil { + return err + } + + client, err := consul.NewClient(config) if err != nil { return fmt.Errorf("Failed to start Consul client: %v", err) } From 8a40f3888c63f23dfb36c4d37e554d20966a80f5 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 12 Sep 2014 17:18:10 -0700 Subject: [PATCH 27/80] agent: move keyring initialization out of agent, add -init option to keys command --- command/agent/agent.go | 64 ++++-------------------------- command/agent/command.go | 13 +++---- command/agent/config.go | 16 ++------ command/keys.go | 84 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 94 insertions(+), 83 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 06aad6d433..95fcd8bec5 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -20,8 +20,8 @@ import ( ) const ( - serfLANKeyring = "serf/local.keyring" - serfWANKeyring = "serf/remote.keyring" + SerfLANKeyring = "serf/local.keyring" + SerfWANKeyring = "serf/remote.keyring" ) /* @@ -119,18 +119,6 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { // Initialize the local state agent.state.Init(config, agent.logger) - // Setup encryption keyring files - if config.PersistKeyring && config.EncryptKey != "" { - if config.Server { - if err := agent.initKeyringFile(serfWANKeyring); err != nil { - return nil, err - } - } - if err := agent.initKeyringFile(serfLANKeyring); err != nil { - return nil, err - } - } - // Setup either the client or the server var err error if config.Server { @@ -182,16 +170,16 @@ func (a *Agent) consulConfig() *consul.Config { if a.config.DataDir != "" { base.DataDir = a.config.DataDir } - if a.config.EncryptKey != "" && !a.config.PersistKeyring { + if a.config.EncryptKey != "" { key, _ := a.config.EncryptBytes() base.SerfLANConfig.MemberlistConfig.SecretKey = key base.SerfWANConfig.MemberlistConfig.SecretKey = key } - if a.config.PersistKeyring { - lanKeyring := filepath.Join(base.DataDir, serfLANKeyring) - wanKeyring := filepath.Join(base.DataDir, serfWANKeyring) - base.SerfLANConfig.KeyringFile = lanKeyring - base.SerfWANConfig.KeyringFile = wanKeyring + if a.config.Server && a.config.keyringFilesExist() { + pathWAN := filepath.Join(base.DataDir, SerfWANKeyring) + pathLAN := filepath.Join(base.DataDir, SerfLANKeyring) + base.SerfWANConfig.KeyringFile = pathWAN + base.SerfLANConfig.KeyringFile = pathLAN } if a.config.NodeName != "" { base.NodeName = a.config.NodeName @@ -813,39 +801,3 @@ func (a *Agent) RemoveKeyLAN(key string) (*serf.KeyResponse, error) { km := a.client.KeyManagerLAN() return km.RemoveKey(key) } - -// initKeyringFile is used to create and initialize a persistent keyring file -// for gossip encryption keys. It is used at agent startup to dump the initial -// encryption key into a keyfile if persistence is enabled. -func (a *Agent) initKeyringFile(path string) error { - serfDir := filepath.Join(a.config.DataDir, "serf") - if err := os.MkdirAll(serfDir, 0700); err != nil { - return err - } - - keys := []string{a.config.EncryptKey} - keyringBytes, err := json.MarshalIndent(keys, "", " ") - if err != nil { - return err - } - - keyringFile := filepath.Join(a.config.DataDir, path) - - // If the keyring file already exists, don't re-initialize - if _, err := os.Stat(keyringFile); err == nil { - return nil - } - - fh, err := os.OpenFile(keyringFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer fh.Close() - - if _, err := fh.Write(keyringBytes); err != nil { - os.Remove(keyringFile) - return err - } - - return nil -} diff --git a/command/agent/command.go b/command/agent/command.go index 94b6eacd30..bacc099d53 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -67,8 +67,6 @@ func (c *Command) readConfig() *Config { cmdFlags.StringVar(&cmdConfig.UiDir, "ui-dir", "", "path to the web UI directory") cmdFlags.StringVar(&cmdConfig.PidFile, "pid-file", "", "path to file to store PID") cmdFlags.StringVar(&cmdConfig.EncryptKey, "encrypt", "", "gossip encryption key") - cmdFlags.BoolVar(&cmdConfig.PersistKeyring, "persist-keyring", false, "persist keyring changes") - cmdFlags.BoolVar(&cmdConfig.Server, "server", false, "run agent as server") cmdFlags.BoolVar(&cmdConfig.Bootstrap, "bootstrap", false, "enable server bootstrap mode") cmdFlags.IntVar(&cmdConfig.BootstrapExpect, "bootstrap-expect", 0, "enable automatic bootstrap via expect mode") @@ -219,11 +217,10 @@ func (c *Command) readConfig() *Config { c.Ui.Error("WARNING: Windows is not recommended as a Consul server. Do not use in production.") } - // Warn if an encryption key is passed while a keyring already exists - if config.EncryptKey != "" && (config.PersistKeyring && config.CheckKeyringFiles()) { - c.Ui.Error(fmt.Sprintf( - "WARNING: Keyring already exists, ignoring new key %s", - config.EncryptKey)) + // Error if an encryption key is passed while a keyring already exists + if config.EncryptKey != "" && config.keyringFilesExist() { + c.Ui.Error(fmt.Sprintf("Error: -encrypt specified but keyring files exist")) + return nil } // Set the version info @@ -595,7 +592,7 @@ func (c *Command) Run(args []string) int { // Determine if gossip is encrypted gossipEncrypted := false - if config.EncryptKey != "" || (config.PersistKeyring && config.CheckKeyringFiles()) { + if config.EncryptKey != "" || config.keyringFilesExist() { gossipEncrypted = true } diff --git a/command/agent/config.go b/command/agent/config.go index e4c1ee73aa..9334553d00 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -150,11 +150,6 @@ type Config struct { // the TERM signal. Defaults false. This can be changed on reload. LeaveOnTerm bool `mapstructure:"leave_on_terminate"` - // Enable keyring persistence. There are currently two keyrings; one for - // the LAN serf cluster and the other for the WAN. Each will maintain its - // own keyring file in the agent's data directory. - PersistKeyring bool `mapstructure:"persist_keyring"` - // SkipLeaveOnInt controls if Serf skips a graceful leave when receiving // the INT signal. Defaults false. This can be changed on reload. SkipLeaveOnInt bool `mapstructure:"skip_leave_on_interrupt"` @@ -416,13 +411,13 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { return addr.String(), nil } -// CheckKeyringFiles checks for existence of the keyring files for Serf -func (c *Config) CheckKeyringFiles() bool { - if _, err := os.Stat(filepath.Join(c.DataDir, serfLANKeyring)); err != nil { +// keyringFilesExist checks for existence of the keyring files for Serf +func (c *Config) keyringFilesExist() bool { + if _, err := os.Stat(filepath.Join(c.DataDir, SerfLANKeyring)); err != nil { return false } if c.Server { - if _, err := os.Stat(filepath.Join(c.DataDir, serfWANKeyring)); err != nil { + if _, err := os.Stat(filepath.Join(c.DataDir, SerfWANKeyring)); err != nil { return false } } @@ -697,9 +692,6 @@ func MergeConfig(a, b *Config) *Config { if b.EncryptKey != "" { result.EncryptKey = b.EncryptKey } - if b.PersistKeyring { - result.PersistKeyring = true - } if b.LogLevel != "" { result.LogLevel = b.LogLevel } diff --git a/command/keys.go b/command/keys.go index b998535763..8ca98bdf2f 100644 --- a/command/keys.go +++ b/command/keys.go @@ -1,10 +1,15 @@ package command import ( + "encoding/base64" + "encoding/json" "flag" "fmt" + "os" + "path/filepath" "strings" + "github.com/hashicorp/consul/command/agent" "github.com/mitchellh/cli" "github.com/ryanuber/columnize" ) @@ -16,7 +21,7 @@ type KeysCommand struct { } func (c *KeysCommand) Run(args []string) int { - var installKey, useKey, removeKey string + var installKey, useKey, removeKey, init, dataDir string var listKeys, wan bool cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) @@ -27,6 +32,8 @@ func (c *KeysCommand) Run(args []string) int { cmdFlags.StringVar(&removeKey, "remove", "", "remove key") cmdFlags.BoolVar(&listKeys, "list", false, "list keys") cmdFlags.BoolVar(&wan, "wan", false, "operate on wan keys") + cmdFlags.StringVar(&init, "init", "", "initialize keyring") + cmdFlags.StringVar(&dataDir, "data-dir", "", "data directory") rpcAddr := RPCAddrFlag(cmdFlags) if err := cmdFlags.Parse(args); err != nil { @@ -40,15 +47,11 @@ func (c *KeysCommand) Run(args []string) int { Ui: c.Ui, } - var out []string - var failures map[string]string - var err error - // Only accept a single argument found := listKeys - for _, arg := range []string{installKey, useKey, removeKey} { + for _, arg := range []string{installKey, useKey, removeKey, init} { if found && len(arg) > 0 { - c.Ui.Error("Only one of -list, -install, -use, or -remove allowed") + c.Ui.Error("Only a single action is allowed") return 1 } found = found || len(arg) > 0 @@ -60,6 +63,43 @@ func (c *KeysCommand) Run(args []string) int { return 1 } + var out, paths []string + var failures map[string]string + var err error + + if init != "" { + if dataDir == "" { + c.Ui.Error("Must provide -data-dir") + return 1 + } + if _, err := base64.StdEncoding.DecodeString(init); err != nil { + c.Ui.Error(fmt.Sprintf("Invalid key: %s", err)) + return 1 + } + + paths = append(paths, filepath.Join(dataDir, agent.SerfLANKeyring)) + if wan { + paths = append(paths, filepath.Join(dataDir, agent.SerfWANKeyring)) + } + + keys := []string{init} + keyringBytes, err := json.MarshalIndent(keys, "", " ") + if err != nil { + c.Ui.Error(fmt.Sprintf("Error: %s", err)) + return 1 + } + + for _, path := range paths { + if err := initializeKeyring(path, keyringBytes); err != nil { + c.Ui.Error("Error: %s", err) + return 1 + } + } + + return 0 + } + + // All other operations will require a client connection client, err := RPCClient(*rpcAddr) if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) @@ -181,6 +221,30 @@ func (c *KeysCommand) Run(args []string) int { return 0 } +// initializeKeyring will create a keyring file at a given path. +func initializeKeyring(path string, key []byte) error { + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return err + } + + if _, err := os.Stat(path); err == nil { + return fmt.Errorf("File already exists: %s", path) + } + + fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer fh.Close() + + if _, err := fh.Write(keyringBytes); err != nil { + os.Remove(path) + return err + } + + return nil +} + func (c *KeysCommand) Help() string { helpText := ` Usage: consul keys [options] @@ -204,6 +268,12 @@ Options: -list List all keys currently in use within the cluster. -wan If talking with a server node, this flag can be used to operate on the WAN gossip layer. + -init= Create an initial keyring file for Consul to use + containing the provided key. By default, this option + will only initialize the LAN keyring. If the -wan + option is also passed, then the wan keyring will be + created as well. The -data-dir argument is required + with this option. -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. ` return strings.TrimSpace(helpText) From 530f3ba747edcd3a3ac94a615bb695f2b263f58a Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 12 Sep 2014 18:13:49 -0700 Subject: [PATCH 28/80] command/keys: refactor, restrict key operations to server nodes --- command/agent/agent.go | 9 +- command/agent/config.go | 7 +- command/keys.go | 208 +++++++++++++++++++--------------------- 3 files changed, 102 insertions(+), 122 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 95fcd8bec5..d0f62fc3e6 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -20,8 +20,7 @@ import ( ) const ( - SerfLANKeyring = "serf/local.keyring" - SerfWANKeyring = "serf/remote.keyring" + SerfKeyring = "serf/keyring" ) /* @@ -176,10 +175,8 @@ func (a *Agent) consulConfig() *consul.Config { base.SerfWANConfig.MemberlistConfig.SecretKey = key } if a.config.Server && a.config.keyringFilesExist() { - pathWAN := filepath.Join(base.DataDir, SerfWANKeyring) - pathLAN := filepath.Join(base.DataDir, SerfLANKeyring) - base.SerfWANConfig.KeyringFile = pathWAN - base.SerfLANConfig.KeyringFile = pathLAN + path := filepath.Join(base.DataDir, SerfKeyring) + base.SerfLANConfig.KeyringFile = path } if a.config.NodeName != "" { base.NodeName = a.config.NodeName diff --git a/command/agent/config.go b/command/agent/config.go index 9334553d00..60bd730321 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -413,14 +413,9 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { // keyringFilesExist checks for existence of the keyring files for Serf func (c *Config) keyringFilesExist() bool { - if _, err := os.Stat(filepath.Join(c.DataDir, SerfLANKeyring)); err != nil { + if _, err := os.Stat(filepath.Join(c.DataDir, SerfKeyring)); err != nil { return false } - if c.Server { - if _, err := os.Stat(filepath.Join(c.DataDir, SerfWANKeyring)); err != nil { - return false - } - } return true } diff --git a/command/keys.go b/command/keys.go index 8ca98bdf2f..c603a3d53f 100644 --- a/command/keys.go +++ b/command/keys.go @@ -22,7 +22,7 @@ type KeysCommand struct { func (c *KeysCommand) Run(args []string) int { var installKey, useKey, removeKey, init, dataDir string - var listKeys, wan bool + var listKeys bool cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } @@ -31,7 +31,6 @@ func (c *KeysCommand) Run(args []string) int { cmdFlags.StringVar(&useKey, "use", "", "use key") cmdFlags.StringVar(&removeKey, "remove", "", "remove key") cmdFlags.BoolVar(&listKeys, "list", false, "list keys") - cmdFlags.BoolVar(&wan, "wan", false, "operate on wan keys") cmdFlags.StringVar(&init, "init", "", "initialize keyring") cmdFlags.StringVar(&dataDir, "data-dir", "", "data directory") @@ -63,39 +62,17 @@ func (c *KeysCommand) Run(args []string) int { return 1 } - var out, paths []string - var failures map[string]string - var err error - if init != "" { if dataDir == "" { c.Ui.Error("Must provide -data-dir") return 1 } - if _, err := base64.StdEncoding.DecodeString(init); err != nil { - c.Ui.Error(fmt.Sprintf("Invalid key: %s", err)) - return 1 - } - - paths = append(paths, filepath.Join(dataDir, agent.SerfLANKeyring)) - if wan { - paths = append(paths, filepath.Join(dataDir, agent.SerfWANKeyring)) - } - - keys := []string{init} - keyringBytes, err := json.MarshalIndent(keys, "", " ") - if err != nil { + path := filepath.Join(dataDir, agent.SerfKeyring) + if err := initializeKeyring(path, init); err != nil { c.Ui.Error(fmt.Sprintf("Error: %s", err)) return 1 } - for _, path := range paths { - if err := initializeKeyring(path, keyringBytes); err != nil { - c.Ui.Error("Error: %s", err) - return 1 - } - } - return 0 } @@ -107,112 +84,64 @@ func (c *KeysCommand) Run(args []string) int { } defer client.Close() + // For all key-related operations, we must be querying a server node. + s, err := client.Stats() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error: %s", err)) + return 1 + } + if s["consul"]["server"] != "true" { + c.Ui.Error("Error: Key modification can only be handled by a server") + return 1 + } + if listKeys { - var keys map[string]int - var numNodes int - - if wan { - c.Ui.Info("Asking all WAN members for installed keys...") - keys, numNodes, failures, err = client.ListKeysWAN() - } else { - c.Ui.Info("Asking all LAN members for installed keys...") - keys, numNodes, failures, err = client.ListKeysLAN() + c.Ui.Info("Asking all WAN members for installed keys...") + if rval := c.listKeysOperation(client.ListKeysWAN); rval != 0 { + return rval } - - if err != nil { - if len(failures) > 0 { - for node, msg := range failures { - out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) - } - c.Ui.Error(columnize.SimpleFormat(out)) - } - c.Ui.Error("") - c.Ui.Error(fmt.Sprintf("Failed gathering member keys: %s", err)) - return 1 + c.Ui.Info("Asking all LAN members for installed keys...") + if rval := c.listKeysOperation(client.ListKeysLAN); rval != 0 { + return rval } - - c.Ui.Info("Keys gathered, listing cluster keys...") - c.Ui.Output("") - - for key, num := range keys { - out = append(out, fmt.Sprintf("%s | [%d/%d]", key, num, numNodes)) - } - c.Ui.Output(columnize.SimpleFormat(out)) - return 0 } if installKey != "" { - if wan { - c.Ui.Info("Installing new WAN gossip encryption key...") - failures, err = client.InstallKeyWAN(installKey) - } else { - c.Ui.Info("Installing new LAN gossip encryption key...") - failures, err = client.InstallKeyLAN(installKey) + c.Ui.Info("Installing new WAN gossip encryption key...") + if rval := c.keyOperation(installKey, client.InstallKeyWAN); rval != 0 { + return rval } - - if err != nil { - if len(failures) > 0 { - for node, msg := range failures { - out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) - } - c.Ui.Error(columnize.SimpleFormat(out)) - } - c.Ui.Error("") - c.Ui.Error(fmt.Sprintf("Error installing key: %s", err)) - return 1 + c.Ui.Info("Installing new LAN gossip encryption key...") + if rval := c.keyOperation(installKey, client.InstallKeyLAN); rval != 0 { + return rval } - c.Ui.Info("Successfully installed key!") return 0 } if useKey != "" { - if wan { - c.Ui.Info("Changing primary encryption key on WAN members...") - failures, err = client.UseKeyWAN(useKey) - } else { - c.Ui.Info("Changing primary encryption key on LAN members...") - failures, err = client.UseKeyLAN(useKey) + c.Ui.Info("Changing primary WAN gossip encryption key...") + if rval := c.keyOperation(useKey, client.UseKeyWAN); rval != 0 { + return rval } - - if err != nil { - if len(failures) > 0 { - for node, msg := range failures { - out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) - } - c.Ui.Error(columnize.SimpleFormat(out)) - } - c.Ui.Error("") - c.Ui.Error(fmt.Sprintf("Error changing primary key: %s", err)) - return 1 + c.Ui.Info("Changing primary LAN gossip encryption key...") + if rval := c.keyOperation(useKey, client.UseKeyLAN); rval != 0 { + return rval } - c.Ui.Info("Successfully changed primary key!") return 0 } if removeKey != "" { - if wan { - c.Ui.Info("Removing key from WAN members...") - failures, err = client.RemoveKeyWAN(removeKey) - } else { - c.Ui.Info("Removing key from LAN members...") - failures, err = client.RemoveKeyLAN(removeKey) + c.Ui.Info("Removing WAN gossip encryption key...") + if rval := c.keyOperation(removeKey, client.RemoveKeyWAN); rval != 0 { + return rval } - - if err != nil { - if len(failures) > 0 { - for node, msg := range failures { - out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) - } - c.Ui.Error(columnize.SimpleFormat(out)) - } - c.Ui.Error("") - c.Ui.Error(fmt.Sprintf("Error removing key: %s", err)) - return 1 + c.Ui.Info("Removing LAN gossip encryption key...") + if rval := c.keyOperation(removeKey, client.RemoveKeyLAN); rval != 0 { + return rval } - c.Ui.Info("Successfully removed key!") return 0 } @@ -221,8 +150,67 @@ func (c *KeysCommand) Run(args []string) int { return 0 } +type keyFunc func(string) (map[string]string, error) + +func (c *KeysCommand) keyOperation(key string, fn keyFunc) int { + var out []string + + failures, err := fn(key) + + if err != nil { + if len(failures) > 0 { + for node, msg := range failures { + out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + } + c.Ui.Error(columnize.SimpleFormat(out)) + } + c.Ui.Error("") + c.Ui.Error(fmt.Sprintf("Error: %s", err)) + return 1 + } + + return 0 +} + +type listKeysFunc func() (map[string]int, int, map[string]string, error) + +func (c *KeysCommand) listKeysOperation(fn listKeysFunc) int { + var out []string + + keys, numNodes, failures, err := fn() + + if err != nil { + if len(failures) > 0 { + for node, msg := range failures { + out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + } + c.Ui.Error(columnize.SimpleFormat(out)) + } + c.Ui.Error("") + c.Ui.Error(fmt.Sprintf("Failed gathering member keys: %s", err)) + return 1 + } + for key, num := range keys { + out = append(out, fmt.Sprintf("%s | [%d/%d]", key, num, numNodes)) + } + c.Ui.Output(columnize.SimpleFormat(out)) + + c.Ui.Output("") + return 0 +} + // initializeKeyring will create a keyring file at a given path. -func initializeKeyring(path string, key []byte) error { +func initializeKeyring(path, key string) error { + if _, err := base64.StdEncoding.DecodeString(key); err != nil { + return fmt.Errorf("Invalid key: %s", err) + } + + keys := []string{key} + keyringBytes, err := json.MarshalIndent(keys, "", " ") + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { return err } From 67d78628a311e51f26d6e38d7a5feac94a94d0af Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 12 Sep 2014 20:08:54 -0700 Subject: [PATCH 29/80] command: renamed keys to keyring to disambiguate usage --- command/{keys.go => keyring.go} | 34 +++--- command/{keys_test.go => keyring_test.go} | 105 ++++-------------- commands.go | 4 +- ...ys.html.markdown => keyring.html.markdown} | 39 ++++--- website/source/layouts/docs.erb | 4 +- 5 files changed, 63 insertions(+), 123 deletions(-) rename command/{keys.go => keyring.go} (86%) rename command/{keys_test.go => keyring_test.go} (50%) rename website/source/docs/commands/{keys.html.markdown => keyring.html.markdown} (60%) diff --git a/command/keys.go b/command/keyring.go similarity index 86% rename from command/keys.go rename to command/keyring.go index c603a3d53f..17d1c5c4c4 100644 --- a/command/keys.go +++ b/command/keyring.go @@ -14,13 +14,13 @@ import ( "github.com/ryanuber/columnize" ) -// KeysCommand is a Command implementation that handles querying, installing, +// KeyringCommand is a Command implementation that handles querying, installing, // and removing gossip encryption keys from a keyring. -type KeysCommand struct { +type KeyringCommand struct { Ui cli.Ui } -func (c *KeysCommand) Run(args []string) int { +func (c *KeyringCommand) Run(args []string) int { var installKey, useKey, removeKey, init, dataDir string var listKeys bool @@ -150,9 +150,12 @@ func (c *KeysCommand) Run(args []string) int { return 0 } +// keyFunc is a function which manipulates gossip encryption keyrings. This is +// used for key installation, removal, and primary key changes. type keyFunc func(string) (map[string]string, error) -func (c *KeysCommand) keyOperation(key string, fn keyFunc) int { +// keyOperation is a unified process for manipulating the gossip keyrings. +func (c *KeyringCommand) keyOperation(key string, fn keyFunc) int { var out []string failures, err := fn(key) @@ -172,9 +175,12 @@ func (c *KeysCommand) keyOperation(key string, fn keyFunc) int { return 0 } +// listKeysFunc is a function which handles querying lists of gossip keys type listKeysFunc func() (map[string]int, int, map[string]string, error) -func (c *KeysCommand) listKeysOperation(fn listKeysFunc) int { +// listKeysOperation is a unified process for querying and +// displaying gossip keys. +func (c *KeyringCommand) listKeysOperation(fn listKeysFunc) int { var out []string keys, numNodes, failures, err := fn() @@ -233,9 +239,9 @@ func initializeKeyring(path, key string) error { return nil } -func (c *KeysCommand) Help() string { +func (c *KeyringCommand) Help() string { helpText := ` -Usage: consul keys [options] +Usage: consul keyring [options] Manages encryption keys used for gossip messages. Gossip encryption is optional. When enabled, this command may be used to examine active encryption @@ -243,6 +249,9 @@ Usage: consul keys [options] functionality provides the ability to perform key rotation cluster-wide, without disrupting the cluster. + With the exception of the -init argument, all other operations performed by + this command can only be run against server nodes. + Options: -install= Install a new encryption key. This will broadcast @@ -254,19 +263,14 @@ Options: operation may only be performed on keys which are not currently the primary key. -list List all keys currently in use within the cluster. - -wan If talking with a server node, this flag can be used - to operate on the WAN gossip layer. -init= Create an initial keyring file for Consul to use - containing the provided key. By default, this option - will only initialize the LAN keyring. If the -wan - option is also passed, then the wan keyring will be - created as well. The -data-dir argument is required - with this option. + containing the provided key. The -data-dir argument + is required with this option. -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. ` return strings.TrimSpace(helpText) } -func (c *KeysCommand) Synopsis() string { +func (c *KeyringCommand) Synopsis() string { return "Manages gossip layer encryption keys" } diff --git a/command/keys_test.go b/command/keyring_test.go similarity index 50% rename from command/keys_test.go rename to command/keyring_test.go index 903d13b19f..d3f01ade92 100644 --- a/command/keys_test.go +++ b/command/keyring_test.go @@ -8,14 +8,13 @@ import ( "github.com/mitchellh/cli" ) -func TestKeysCommand_implements(t *testing.T) { - var _ cli.Command = &KeysCommand{} +func TestKeyringCommand_implements(t *testing.T) { + var _ cli.Command = &KeyringCommand{} } -func TestKeysCommandRun(t *testing.T) { +func TestKeyringCommandRun(t *testing.T) { key1 := "HS5lJ+XuTlYKWaeGYyG+/A==" key2 := "kZyFABeAmc64UMTrm9XuKA==" - key3 := "2k5VRlBIIKUPc1v77rsswg==" // Begin with a single key conf := agent.Config{EncryptKey: key1} @@ -23,7 +22,7 @@ func TestKeysCommandRun(t *testing.T) { defer a1.Shutdown() // The keyring was initialized with only the provided key - out := listKeys(t, a1.addr, false) + out := listKeys(t, a1.addr) if !strings.Contains(out, key1) { t.Fatalf("bad: %#v", out) } @@ -31,83 +30,36 @@ func TestKeysCommandRun(t *testing.T) { t.Fatalf("bad: %#v", out) } - // The key was installed on the WAN gossip layer also - out = listKeys(t, a1.addr, true) - if !strings.Contains(out, key1) { - t.Fatalf("bad: %#v", out) - } - if strings.Contains(out, key3) { - t.Fatalf("bad: %#v", out) - } - // Install the second key onto the keyring - installKey(t, a1.addr, false, key2) + installKey(t, a1.addr, key2) // Both keys should be present - out = listKeys(t, a1.addr, false) + out = listKeys(t, a1.addr) for _, key := range []string{key1, key2} { if !strings.Contains(out, key) { t.Fatalf("bad: %#v", out) } } - // Second key should not be installed on WAN - out = listKeys(t, a1.addr, true) - if strings.Contains(out, key2) { - t.Fatalf("bad: %#v", out) - } - // Change out the primary key - useKey(t, a1.addr, false, key2) + useKey(t, a1.addr, key2) // Remove the original key - removeKey(t, a1.addr, false, key1) + removeKey(t, a1.addr, key1) // Make sure only the new key is present - out = listKeys(t, a1.addr, false) + out = listKeys(t, a1.addr) if strings.Contains(out, key1) { t.Fatalf("bad: %#v", out) } if !strings.Contains(out, key2) { t.Fatalf("bad: %#v", out) } - - // Original key still remains on WAN keyring - out = listKeys(t, a1.addr, true) - if !strings.Contains(out, key1) { - t.Fatalf("bad: %#v", out) - } - - // Install second key on WAN keyring - installKey(t, a1.addr, true, key3) - - // Two keys now present on WAN keyring - out = listKeys(t, a1.addr, true) - for _, key := range []string{key1, key3} { - if !strings.Contains(out, key) { - t.Fatalf("bad: %#v", out) - } - } - - // Change WAN primary key - useKey(t, a1.addr, true, key3) - - // Remove original key from WAN keyring - removeKey(t, a1.addr, true, key1) - - // Only new key should exist on WAN keyring - out = listKeys(t, a1.addr, true) - if !strings.Contains(out, key3) { - t.Fatalf("bad: %#v", out) - } - if strings.Contains(out, key1) { - t.Fatalf("bad: %#v", out) - } } -func TestKeysCommandRun_help(t *testing.T) { +func TestKeyringCommandRun_help(t *testing.T) { ui := new(cli.MockUi) - c := &KeysCommand{Ui: ui} + c := &KeyringCommand{Ui: ui} code := c.Run(nil) if code != 1 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) @@ -119,9 +71,9 @@ func TestKeysCommandRun_help(t *testing.T) { } } -func TestKeysCommandRun_failedConnection(t *testing.T) { +func TestKeyringCommandRun_failedConnection(t *testing.T) { ui := new(cli.MockUi) - c := &KeysCommand{Ui: ui} + c := &KeyringCommand{Ui: ui} args := []string{"-list", "-rpc-addr=127.0.0.1:0"} code := c.Run(args) if code != 1 { @@ -132,14 +84,11 @@ func TestKeysCommandRun_failedConnection(t *testing.T) { } } -func listKeys(t *testing.T, addr string, wan bool) string { +func listKeys(t *testing.T, addr string) string { ui := new(cli.MockUi) - c := &KeysCommand{Ui: ui} + c := &KeyringCommand{Ui: ui} args := []string{"-list", "-rpc-addr=" + addr} - if wan { - args = append(args, "-wan") - } code := c.Run(args) if code != 0 { @@ -149,45 +98,33 @@ func listKeys(t *testing.T, addr string, wan bool) string { return ui.OutputWriter.String() } -func installKey(t *testing.T, addr string, wan bool, key string) { +func installKey(t *testing.T, addr string, key string) { ui := new(cli.MockUi) - c := &KeysCommand{Ui: ui} + c := &KeyringCommand{Ui: ui} args := []string{"-install=" + key, "-rpc-addr=" + addr} - if wan { - args = append(args, "-wan") - } - code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } } -func useKey(t *testing.T, addr string, wan bool, key string) { +func useKey(t *testing.T, addr string, key string) { ui := new(cli.MockUi) - c := &KeysCommand{Ui: ui} + c := &KeyringCommand{Ui: ui} args := []string{"-use=" + key, "-rpc-addr=" + addr} - if wan { - args = append(args, "-wan") - } - code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } } -func removeKey(t *testing.T, addr string, wan bool, key string) { +func removeKey(t *testing.T, addr string, key string) { ui := new(cli.MockUi) - c := &KeysCommand{Ui: ui} + c := &KeyringCommand{Ui: ui} args := []string{"-remove=" + key, "-rpc-addr=" + addr} - if wan { - args = append(args, "-wan") - } - code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) diff --git a/commands.go b/commands.go index 8fab257ace..f6cf50a91a 100644 --- a/commands.go +++ b/commands.go @@ -56,8 +56,8 @@ func init() { }, nil }, - "keys": func() (cli.Command, error) { - return &command.KeysCommand{ + "keyring": func() (cli.Command, error) { + return &command.KeyringCommand{ Ui: ui, }, nil }, diff --git a/website/source/docs/commands/keys.html.markdown b/website/source/docs/commands/keyring.html.markdown similarity index 60% rename from website/source/docs/commands/keys.html.markdown rename to website/source/docs/commands/keyring.html.markdown index beb9f38945..2b65241e6b 100644 --- a/website/source/docs/commands/keys.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -1,21 +1,21 @@ --- layout: "docs" -page_title: "Commands: Keys" -sidebar_current: "docs-commands-keys" +page_title: "Commands: Keyring" +sidebar_current: "docs-commands-keyring" --- -# Consul Keys +# Consul Keyring -Command: `consul keys` +Command: `consul keyring` -The `keys` command is used to examine and modify the encryption keys used in +The `keyring` command is used to examine and modify the encryption keys used in Consul's [Gossip Pools](/docs/internals/gossip.html). It is capable of distributing new encryption keys to the cluster, revoking old encryption keys, and changing the key used by the cluster to encrypt messages. -Because Consul utilizes multiple gossip pools, this command will operate on only -a single pool at a time. The pool can be specified using the arguments -documented below. +Because Consul utilizes multiple gossip pools, this command will only operate +against a server node for most operations. The only operation which may be used +on client machines is the `-init` argument for initial key configuration. Consul allows multiple encryption keys to be in use simultaneously. This is intended to provide a transition state while the cluster converges. It is the @@ -23,23 +23,27 @@ responsibility of the operator to ensure that only the required encryption keys are installed on the cluster. You can ensure that a key is not installed using the `-list` and `-remove` options. -By default, modifications made using this command will **NOT** be persisted, and -will be lost when the agent shuts down. You can alter this behavior via the -`-persist-keyring` option in the -[Agent Configuration](/docs/agent/options.html). - All variations of the keys command will return 0 if all nodes reply and there are no errors. If any node fails to reply or reports failure, the exit code will be 1. ## Usage -Usage: `consul keys [options]` +Usage: `consul keyring [options]` -Exactly one of `-list`, `-install`, `-remove`, or `-update` must be provided. +Only one actionable argument may be specified per run, including `-init`, +`-list`, `-install`, `-remove`, and `-update`. The list of available flags are: +* `-init` - Creates the keyring file(s). This is useful to configure initial + encryption keyrings, which can later be mutated using the other arguments in + this command. This argument accepts an ASCII key, which can be generated using + the [keygen command](/docs/commands/keygen.html). + + This operation can be run on both client and server nodes and requires no + network connectivity. + * `-install` - Install a new encryption key. This will broadcast the new key to all members in the cluster. @@ -51,9 +55,4 @@ The list of available flags are: * `-list` - List all keys currently in use within the cluster. -* `-wan` - If talking with a server node, this flag can be used to operate on - the WAN gossip layer. By default, this command operates on the LAN layer. More - information about the different gossip layers can be found on the - [gossip protocol](/docs/internals/gossip.html) page. - * `-rpc-addr` - RPC address of the Consul agent. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 6c48f1dbbd..d6764e7929 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -79,8 +79,8 @@ keygen - > - keys + > + keyring > From 353b67826a62e4671a3a82245b6d92dab2ccca76 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 13 Sep 2014 11:24:17 -0700 Subject: [PATCH 30/80] command: use separate key files for LAN/WAN --- command/agent/agent.go | 19 ++++++++++++++----- command/agent/command.go | 5 ++--- command/agent/config.go | 18 ++++++++++++++---- command/keyring.go | 17 +++++++++++++---- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index d0f62fc3e6..b19645f167 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -20,7 +20,8 @@ import ( ) const ( - SerfKeyring = "serf/keyring" + SerfLANKeyring = "serf/local.keyring" + SerfWANKeyring = "serf/remote.keyring" ) /* @@ -174,10 +175,6 @@ func (a *Agent) consulConfig() *consul.Config { base.SerfLANConfig.MemberlistConfig.SecretKey = key base.SerfWANConfig.MemberlistConfig.SecretKey = key } - if a.config.Server && a.config.keyringFilesExist() { - path := filepath.Join(base.DataDir, SerfKeyring) - base.SerfLANConfig.KeyringFile = path - } if a.config.NodeName != "" { base.NodeName = a.config.NodeName } @@ -276,6 +273,14 @@ func (a *Agent) setupServer() error { config := a.consulConfig() // Load a keyring file, if present + keyfileLAN := filepath.Join(config.DataDir, SerfLANKeyring) + if _, err := os.Stat(keyfileLAN); err == nil { + config.SerfLANConfig.KeyringFile = keyfileLAN + } + keyfileWAN := filepath.Join(config.DataDir, SerfWANKeyring) + if _, err := os.Stat(keyfileWAN); err == nil { + config.SerfWANConfig.KeyringFile = keyfileWAN + } if err := loadKeyringFile(config.SerfLANConfig); err != nil { return err } @@ -296,6 +301,10 @@ func (a *Agent) setupClient() error { config := a.consulConfig() // Load a keyring file, if present + keyfileLAN := filepath.Join(config.DataDir, SerfLANKeyring) + if _, err := os.Stat(keyfileLAN); err == nil { + config.SerfLANConfig.KeyringFile = keyfileLAN + } if err := loadKeyringFile(config.SerfLANConfig); err != nil { return err } diff --git a/command/agent/command.go b/command/agent/command.go index bacc099d53..c02b8135c6 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -218,7 +218,7 @@ func (c *Command) readConfig() *Config { } // Error if an encryption key is passed while a keyring already exists - if config.EncryptKey != "" && config.keyringFilesExist() { + if config.EncryptKey != "" && config.keyringFileExists() { c.Ui.Error(fmt.Sprintf("Error: -encrypt specified but keyring files exist")) return nil } @@ -592,7 +592,7 @@ func (c *Command) Run(args []string) int { // Determine if gossip is encrypted gossipEncrypted := false - if config.EncryptKey != "" || config.keyringFilesExist() { + if config.EncryptKey != "" || config.keyringFileExists() { gossipEncrypted = true } @@ -819,7 +819,6 @@ Options: -log-level=info Log level of the agent. -node=hostname Name of this node. Must be unique in the cluster -protocol=N Sets the protocol version. Defaults to latest. - -persist-keyring Enable encryption keyring persistence. -rejoin Ignores a previous leave and attempts to rejoin the cluster. -server Switches agent to server mode. -syslog Enables logging to syslog diff --git a/command/agent/config.go b/command/agent/config.go index 60bd730321..69097984d9 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -411,12 +411,22 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { return addr.String(), nil } -// keyringFilesExist checks for existence of the keyring files for Serf -func (c *Config) keyringFilesExist() bool { - if _, err := os.Stat(filepath.Join(c.DataDir, SerfKeyring)); err != nil { +// keyringFileExists determines if there are encryption key files present +// in the data directory. +func (c *Config) keyringFileExists() bool { + fileLAN := filepath.Join(c.DataDir, SerfLANKeyring) + fileWAN := filepath.Join(c.DataDir, SerfWANKeyring) + + if _, err := os.Stat(fileLAN); err == nil { + return true + } + if !c.Server { return false } - return true + if _, err := os.Stat(fileWAN); err == nil { + return true + } + return false } // DecodeConfig reads the configuration from the given reader in JSON diff --git a/command/keyring.go b/command/keyring.go index 17d1c5c4c4..aed55c0397 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -67,8 +67,14 @@ func (c *KeyringCommand) Run(args []string) int { c.Ui.Error("Must provide -data-dir") return 1 } - path := filepath.Join(dataDir, agent.SerfKeyring) - if err := initializeKeyring(path, init); err != nil { + + fileLAN := filepath.Join(dataDir, agent.SerfLANKeyring) + if err := initializeKeyring(fileLAN, init); err != nil { + c.Ui.Error(fmt.Sprintf("Error: %s", err)) + return 1 + } + fileWAN := filepath.Join(dataDir, agent.SerfWANKeyring) + if err := initializeKeyring(fileWAN, init); err != nil { c.Ui.Error(fmt.Sprintf("Error: %s", err)) return 1 } @@ -84,7 +90,10 @@ func (c *KeyringCommand) Run(args []string) int { } defer client.Close() - // For all key-related operations, we must be querying a server node. + // For all key-related operations, we must be querying a server node. It is + // probably better to enforce this even for LAN pool changes, because other- + // wise, the same exact command syntax will have different results depending + // on where it was run. s, err := client.Stats() if err != nil { c.Ui.Error(fmt.Sprintf("Error: %s", err)) @@ -263,7 +272,7 @@ Options: operation may only be performed on keys which are not currently the primary key. -list List all keys currently in use within the cluster. - -init= Create an initial keyring file for Consul to use + -init= Create the initial keyring files for Consul to use containing the provided key. The -data-dir argument is required with this option. -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. From 77519a5439407240c00a190e008e16922f4910b8 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 13 Sep 2014 11:47:37 -0700 Subject: [PATCH 31/80] command/keyring: add tests for init --- command/keyring_test.go | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/command/keyring_test.go b/command/keyring_test.go index d3f01ade92..b98c3c5971 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -1,6 +1,9 @@ package command import ( + "io/ioutil" + "os" + "path/filepath" "strings" "testing" @@ -84,6 +87,72 @@ func TestKeyringCommandRun_failedConnection(t *testing.T) { } } +func TestKeyCommandRun_initKeyringFail(t *testing.T) { + ui := new(cli.MockUi) + c := &KeyringCommand{Ui: ui} + + // Should error if no data-dir given + args := []string{"-init=HS5lJ+XuTlYKWaeGYyG+/A=="} + code := c.Run(args) + if code != 1 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } + + // Errors on invalid key + args = []string{"-init=xyz", "-data-dir=/tmp"} + code = c.Run(args) + if code != 1 { + t.Fatalf("should have errored") + } +} + +func TestKeyCommandRun_initKeyring(t *testing.T) { + ui := new(cli.MockUi) + c := &KeyringCommand{Ui: ui} + + tempDir, err := ioutil.TempDir("", "consul") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(tempDir) + + args := []string{ + "-init=HS5lJ+XuTlYKWaeGYyG+/A==", + "-data-dir=" + tempDir, + } + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } + + fileLAN := filepath.Join(tempDir, agent.SerfLANKeyring) + fileWAN := filepath.Join(tempDir, agent.SerfWANKeyring) + if _, err := os.Stat(fileLAN); err != nil { + t.Fatalf("err: %s", err) + } + if _, err := os.Stat(fileWAN); err != nil { + t.Fatalf("err: %s", err) + } + + expected := "[\n \"HS5lJ+XuTlYKWaeGYyG+/A==\"\n]" + + contentLAN, err := ioutil.ReadFile(fileLAN) + if err != nil { + t.Fatalf("err: %s", err) + } + if string(contentLAN) != expected { + t.Fatalf("bad: %#v", string(contentLAN)) + } + + contentWAN, err := ioutil.ReadFile(fileWAN) + if err != nil { + t.Fatalf("err: %s", err) + } + if string(contentWAN) != expected { + t.Fatalf("bad: %#v", string(contentWAN)) + } +} + func listKeys(t *testing.T, addr string) string { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} From d906d16d1594f5aec0680df39f208f3136a4c273 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 13 Sep 2014 12:12:24 -0700 Subject: [PATCH 32/80] agent: add tests for keyring presence checks --- command/agent/config_test.go | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/command/agent/config_test.go b/command/agent/config_test.go index 13e8634ce4..19097e2c3c 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -1030,3 +1030,56 @@ func TestReadConfigPaths_dir(t *testing.T) { t.Fatalf("bad: %#v", config) } } + +func TestKeyringFileExists(t *testing.T) { + tempDir, err := ioutil.TempDir("", "consul") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(tempDir) + + fileLAN := filepath.Join(tempDir, SerfLANKeyring) + fileWAN := filepath.Join(tempDir, SerfWANKeyring) + config := &Config{DataDir: tempDir, Server: true} + + // Returns false if we are a server and no keyring files present + if config.keyringFileExists() { + t.Fatalf("should return false") + } + + // Returns false if we are a client and no keyring files present + config.Server = false + if config.keyringFileExists() { + t.Fatalf("should return false") + } + + // Returns true if we are a client and the lan file exists + if err := ioutil.WriteFile(fileLAN, nil, 0600); err != nil { + t.Fatalf("err: %s", err) + } + if !config.keyringFileExists() { + t.Fatalf("should return true") + } + + // Returns true if we are a server and only the lan file exists + config.Server = true + if !config.keyringFileExists() { + t.Fatalf("should return true") + } + + // Returns true if we are a server and both files exist + if err := ioutil.WriteFile(fileWAN, nil, 0600); err != nil { + t.Fatalf("err: %s", err) + } + if !config.keyringFileExists() { + t.Fatalf("should return true") + } + + // Returns true if we are a server and only the wan file exists + if err := os.Remove(fileLAN); err != nil { + t.Fatalf("err: %s", err) + } + if !config.keyringFileExists() { + t.Fatalf("should return true") + } +} From 026ebcef58fe6f327d971691361aca29f4a09a56 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 13 Sep 2014 12:19:21 -0700 Subject: [PATCH 33/80] website: remove keyring persistence options from agent page --- website/source/docs/agent/options.html.markdown | 7 ------- website/source/docs/commands/keyring.html.markdown | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown index 2a60a4309c..01149ef590 100644 --- a/website/source/docs/agent/options.html.markdown +++ b/website/source/docs/agent/options.html.markdown @@ -85,17 +85,10 @@ The options below are all specified on the command-line. it relies on proper configuration. Nodes in the same datacenter should be on a single LAN. -* `-persist-keyring` - This flag enables persistence of changes to the - encryption keys used in the gossip pools. By default, any modifications to - the keyring via the [consul keys](/docs/command/keys.html) command will be - lost when the agent shuts down. - * `-encrypt` - Specifies the secret key to use for encryption of Consul network traffic. This key must be 16-bytes that are base64 encoded. The easiest way to create an encryption key is to use `consul keygen`. All nodes within a cluster must share the same encryption key to communicate. - If keyring persistence is enabled, the given key will only be used if there is - no pre-existing keyring. Otherwise, Consul will emit a warning and continue. * `-join` - Address of another agent to join upon starting up. This can be specified multiple times to specify multiple agents to join. If Consul is diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown index 2b65241e6b..8481b588a3 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -32,7 +32,7 @@ be 1. Usage: `consul keyring [options]` Only one actionable argument may be specified per run, including `-init`, -`-list`, `-install`, `-remove`, and `-update`. +`-list`, `-install`, `-remove`, and `-use`. The list of available flags are: From 2220ccdac21fa36b7fda54e986bd22aa5662a085 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 14 Sep 2014 17:31:44 -0700 Subject: [PATCH 34/80] command: various cleanup --- command/agent/agent.go | 3 ++- command/agent/command.go | 20 +++++++++----------- command/agent/config.go | 3 ++- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index b19645f167..169db3276b 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -688,7 +688,8 @@ func (a *Agent) deletePid() error { return nil } -// loadKeyringFile will load a keyring out of a file +// loadKeyringFile will load a gossip encryption keyring out of a file. The file +// must be in JSON format and contain a list of encryption key strings. func loadKeyringFile(c *serf.Config) error { if c.KeyringFile == "" { return nil diff --git a/command/agent/command.go b/command/agent/command.go index c02b8135c6..f15cea41fa 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -67,6 +67,7 @@ func (c *Command) readConfig() *Config { cmdFlags.StringVar(&cmdConfig.UiDir, "ui-dir", "", "path to the web UI directory") cmdFlags.StringVar(&cmdConfig.PidFile, "pid-file", "", "path to file to store PID") cmdFlags.StringVar(&cmdConfig.EncryptKey, "encrypt", "", "gossip encryption key") + cmdFlags.BoolVar(&cmdConfig.Server, "server", false, "run agent as server") cmdFlags.BoolVar(&cmdConfig.Bootstrap, "bootstrap", false, "enable server bootstrap mode") cmdFlags.IntVar(&cmdConfig.BootstrapExpect, "bootstrap-expect", 0, "enable automatic bootstrap via expect mode") @@ -142,6 +143,13 @@ func (c *Command) readConfig() *Config { config.NodeName = hostname } + if config.EncryptKey != "" { + if _, err := config.EncryptBytes(); err != nil { + c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err)) + return nil + } + } + // Ensure we have a data directory if config.DataDir == "" { c.Ui.Error("Must specify data directory using -data-dir") @@ -172,13 +180,6 @@ func (c *Command) readConfig() *Config { return nil } - if config.EncryptKey != "" { - if _, err := config.EncryptBytes(); err != nil { - c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err)) - return nil - } - } - // Compile all the watches for _, params := range config.Watches { // Parse the watches, excluding the handler @@ -591,10 +592,7 @@ func (c *Command) Run(args []string) int { } // Determine if gossip is encrypted - gossipEncrypted := false - if config.EncryptKey != "" || config.keyringFileExists() { - gossipEncrypted = true - } + gossipEncrypted := config.EncryptKey != "" || config.keyringFileExists() // Let the agent know we've finished registration c.agent.StartSync() diff --git a/command/agent/config.go b/command/agent/config.go index 69097984d9..1188c7be9a 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -412,7 +412,8 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { } // keyringFileExists determines if there are encryption key files present -// in the data directory. +// in the data directory. On client nodes, this returns true if a LAN keyring +// is present. On server nodes, it returns true if either keyring file exists. func (c *Config) keyringFileExists() bool { fileLAN := filepath.Join(c.DataDir, SerfLANKeyring) fileWAN := filepath.Join(c.DataDir, SerfWANKeyring) From 621aafa9b45e2f741695dbb80d9ef4664189ebb5 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 17 Sep 2014 22:31:32 -0700 Subject: [PATCH 35/80] agent: test loading keyring files for client and server --- command/agent/agent_test.go | 104 +++++++++++++++++++++++++++++++++++ command/agent/config_test.go | 8 +++ 2 files changed, 112 insertions(+) diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index a00d5cc117..1e806a9ce5 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -1,10 +1,12 @@ package agent import ( + "encoding/json" "fmt" "io" "io/ioutil" "os" + "path/filepath" "sync/atomic" "testing" "time" @@ -71,6 +73,43 @@ func makeAgentLog(t *testing.T, conf *Config, l io.Writer) (string, *Agent) { return dir, agent } +func makeAgentKeyring(t *testing.T, conf *Config, key string) (string, *Agent) { + keyBytes, err := json.Marshal([]string{key}) + if err != nil { + t.Fatalf("err: %s", err) + } + + dir, err := ioutil.TempDir("", "agent") + if err != nil { + t.Fatalf("err: %v", err) + } + + conf.DataDir = dir + + fileLAN := filepath.Join(dir, SerfLANKeyring) + if err := os.MkdirAll(filepath.Dir(fileLAN), 0700); err != nil { + t.Fatalf("err: %s", err) + } + if err := ioutil.WriteFile(fileLAN, keyBytes, 0600); err != nil { + t.Fatalf("err: %s", err) + } + + fileWAN := filepath.Join(dir, SerfWANKeyring) + if err := os.MkdirAll(filepath.Dir(fileWAN), 0700); err != nil { + t.Fatalf("err: %s", err) + } + if err := ioutil.WriteFile(fileWAN, keyBytes, 0600); err != nil { + t.Fatalf("err: %s", err) + } + + agent, err := Create(conf, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + return dir, agent +} + func makeAgent(t *testing.T, conf *Config) (string, *Agent) { return makeAgentLog(t, conf, nil) } @@ -354,3 +393,68 @@ func TestAgent_ConsulService(t *testing.T) { t.Fatalf("%s service should be in sync", consul.ConsulServiceID) } } + +func TestAgent_LoadKeyrings(t *testing.T) { + key := "tbLJg26ZJyJ9pK3qhc9jig==" + + // Should be no configured keyring file by default + conf1 := nextConfig() + dir1, agent1 := makeAgent(t, conf1) + defer os.RemoveAll(dir1) + defer agent1.Shutdown() + + c := agent1.config.ConsulConfig + if c.SerfLANConfig.KeyringFile != "" { + t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) + } + if c.SerfLANConfig.MemberlistConfig.Keyring != nil { + t.Fatalf("keyring should not be loaded") + } + if c.SerfWANConfig.KeyringFile != "" { + t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) + } + if c.SerfWANConfig.MemberlistConfig.Keyring != nil { + t.Fatalf("keyring should not be loaded") + } + + // Server should auto-load LAN and WAN keyring files + conf2 := nextConfig() + dir2, agent2 := makeAgentKeyring(t, conf2, key) + defer os.RemoveAll(dir2) + defer agent2.Shutdown() + + c = agent2.config.ConsulConfig + if c.SerfLANConfig.KeyringFile == "" { + t.Fatalf("should have keyring file") + } + if c.SerfLANConfig.MemberlistConfig.Keyring == nil { + t.Fatalf("keyring should be loaded") + } + if c.SerfWANConfig.KeyringFile == "" { + t.Fatalf("should have keyring file") + } + if c.SerfWANConfig.MemberlistConfig.Keyring == nil { + t.Fatalf("keyring should be loaded") + } + + // Client should auto-load only the LAN keyring file + conf3 := nextConfig() + conf3.Server = false + dir3, agent3 := makeAgentKeyring(t, conf3, key) + defer os.RemoveAll(dir3) + defer agent3.Shutdown() + + c = agent3.config.ConsulConfig + if c.SerfLANConfig.KeyringFile == "" { + t.Fatalf("should have keyring file") + } + if c.SerfLANConfig.MemberlistConfig.Keyring == nil { + t.Fatalf("keyring should be loaded") + } + if c.SerfWANConfig.KeyringFile != "" { + t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) + } + if c.SerfWANConfig.MemberlistConfig.Keyring != nil { + t.Fatalf("keyring should not be loaded") + } +} diff --git a/command/agent/config_test.go b/command/agent/config_test.go index 19097e2c3c..52c0c58f76 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -1040,6 +1040,14 @@ func TestKeyringFileExists(t *testing.T) { fileLAN := filepath.Join(tempDir, SerfLANKeyring) fileWAN := filepath.Join(tempDir, SerfWANKeyring) + + if err := os.MkdirAll(filepath.Dir(fileLAN), 0700); err != nil { + t.Fatalf("err: %s", err) + } + if err := os.MkdirAll(filepath.Dir(fileWAN), 0700); err != nil { + t.Fatalf("err: %s", err) + } + config := &Config{DataDir: tempDir, Server: true} // Returns false if we are a server and no keyring files present From 5ab4a590d763a8aa00fef628dd05e4638cca3a99 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 17 Sep 2014 23:28:39 -0700 Subject: [PATCH 36/80] command: test generated keyring file content and conflicting args for agent --- command/agent/agent_test.go | 17 ++------------ command/agent/command_test.go | 36 +++++++++++++++++++++++++++++ command/keyring.go | 10 ++++---- command/keyring_test.go | 2 +- testutil/keyring.go | 43 +++++++++++++++++++++++++++++++++++ testutil/keyring_test.go | 42 ++++++++++++++++++++++++++++++++++ 6 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 testutil/keyring.go create mode 100644 testutil/keyring_test.go diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 1e806a9ce5..8240b4854d 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -1,7 +1,6 @@ package agent import ( - "encoding/json" "fmt" "io" "io/ioutil" @@ -74,11 +73,6 @@ func makeAgentLog(t *testing.T, conf *Config, l io.Writer) (string, *Agent) { } func makeAgentKeyring(t *testing.T, conf *Config, key string) (string, *Agent) { - keyBytes, err := json.Marshal([]string{key}) - if err != nil { - t.Fatalf("err: %s", err) - } - dir, err := ioutil.TempDir("", "agent") if err != nil { t.Fatalf("err: %v", err) @@ -87,18 +81,11 @@ func makeAgentKeyring(t *testing.T, conf *Config, key string) (string, *Agent) { conf.DataDir = dir fileLAN := filepath.Join(dir, SerfLANKeyring) - if err := os.MkdirAll(filepath.Dir(fileLAN), 0700); err != nil { + if err := testutil.InitKeyring(fileLAN, key); err != nil { t.Fatalf("err: %s", err) } - if err := ioutil.WriteFile(fileLAN, keyBytes, 0600); err != nil { - t.Fatalf("err: %s", err) - } - fileWAN := filepath.Join(dir, SerfWANKeyring) - if err := os.MkdirAll(filepath.Dir(fileWAN), 0700); err != nil { - t.Fatalf("err: %s", err) - } - if err := ioutil.WriteFile(fileWAN, keyBytes, 0600); err != nil { + if err := testutil.InitKeyring(fileWAN, key); err != nil { t.Fatalf("err: %s", err) } diff --git a/command/agent/command_test.go b/command/agent/command_test.go index 10557daa78..db7d1c9a5c 100644 --- a/command/agent/command_test.go +++ b/command/agent/command_test.go @@ -1,10 +1,17 @@ package agent import ( +<<<<<<< HEAD "fmt" "io/ioutil" "log" "os" +======= + "github.com/hashicorp/consul/testutil" + "github.com/mitchellh/cli" + "io/ioutil" + "path/filepath" +>>>>>>> command: test generated keyring file content and conflicting args for agent "testing" "github.com/hashicorp/consul/testutil" @@ -38,6 +45,7 @@ func TestValidDatacenter(t *testing.T) { } } +<<<<<<< HEAD func TestRetryJoin(t *testing.T) { dir, agent := makeAgent(t, nextConfig()) defer os.RemoveAll(dir) @@ -161,5 +169,33 @@ func TestRetryJoinWanFail(t *testing.T) { if code := cmd.Run(args); code == 0 { t.Fatalf("bad: %d", code) +======= +func TestArgConflict(t *testing.T) { + ui := new(cli.MockUi) + c := &Command{Ui: ui} + + dir, err := ioutil.TempDir("", "agent") + if err != nil { + t.Fatalf("err: %s", err) + } + + key := "HS5lJ+XuTlYKWaeGYyG+/A==" + + fileLAN := filepath.Join(dir, SerfLANKeyring) + if err := testutil.InitKeyring(fileLAN, key); err != nil { + t.Fatalf("err: %s", err) + } + + args := []string{ + "-encrypt=" + key, + "-data-dir=" + dir, + } + code := c.Run(args) + if code != 1 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } + if !strings.Contains(ui.ErrorWriter.String(), "keyring files exist") { + t.Fatalf("bad: %#v", ui.ErrorWriter.String()) +>>>>>>> command: test generated keyring file content and conflicting args for agent } } diff --git a/command/keyring.go b/command/keyring.go index aed55c0397..83293a644c 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -69,12 +69,12 @@ func (c *KeyringCommand) Run(args []string) int { } fileLAN := filepath.Join(dataDir, agent.SerfLANKeyring) - if err := initializeKeyring(fileLAN, init); err != nil { + if err := initKeyring(fileLAN, init); err != nil { c.Ui.Error(fmt.Sprintf("Error: %s", err)) return 1 } fileWAN := filepath.Join(dataDir, agent.SerfWANKeyring) - if err := initializeKeyring(fileWAN, init); err != nil { + if err := initKeyring(fileWAN, init); err != nil { c.Ui.Error(fmt.Sprintf("Error: %s", err)) return 1 } @@ -214,14 +214,14 @@ func (c *KeyringCommand) listKeysOperation(fn listKeysFunc) int { return 0 } -// initializeKeyring will create a keyring file at a given path. -func initializeKeyring(path, key string) error { +// initKeyring will create a keyring file at a given path. +func initKeyring(path, key string) error { if _, err := base64.StdEncoding.DecodeString(key); err != nil { return fmt.Errorf("Invalid key: %s", err) } keys := []string{key} - keyringBytes, err := json.MarshalIndent(keys, "", " ") + keyringBytes, err := json.Marshal(keys) if err != nil { return err } diff --git a/command/keyring_test.go b/command/keyring_test.go index b98c3c5971..cc18ad7996 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -134,7 +134,7 @@ func TestKeyCommandRun_initKeyring(t *testing.T) { t.Fatalf("err: %s", err) } - expected := "[\n \"HS5lJ+XuTlYKWaeGYyG+/A==\"\n]" + expected := `["HS5lJ+XuTlYKWaeGYyG+/A=="]` contentLAN, err := ioutil.ReadFile(fileLAN) if err != nil { diff --git a/testutil/keyring.go b/testutil/keyring.go new file mode 100644 index 0000000000..60486bbb88 --- /dev/null +++ b/testutil/keyring.go @@ -0,0 +1,43 @@ +package testutil + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +// InitKeyring will create a keyring file at a given path. +func InitKeyring(path, key string) error { + if _, err := base64.StdEncoding.DecodeString(key); err != nil { + return fmt.Errorf("Invalid key: %s", err) + } + + keys := []string{key} + keyringBytes, err := json.Marshal(keys) + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return err + } + + if _, err := os.Stat(path); err == nil { + return fmt.Errorf("File already exists: %s", path) + } + + fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer fh.Close() + + if _, err := fh.Write(keyringBytes); err != nil { + os.Remove(path) + return err + } + + return nil +} diff --git a/testutil/keyring_test.go b/testutil/keyring_test.go new file mode 100644 index 0000000000..7e5b3e63fe --- /dev/null +++ b/testutil/keyring_test.go @@ -0,0 +1,42 @@ +package testutil + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +func TestAgent_InitKeyring(t *testing.T) { + key := "tbLJg26ZJyJ9pK3qhc9jig==" + + dir, err := ioutil.TempDir("", "agent") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(dir) + keyFile := filepath.Join(dir, "test/keyring") + + if err := InitKeyring(keyFile, key); err != nil { + t.Fatalf("err: %s", err) + } + + fi, err := os.Stat(filepath.Dir(keyFile)) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !fi.IsDir() { + t.Fatalf("bad: %#v", fi) + } + + data, err := ioutil.ReadFile(keyFile) + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := `["tbLJg26ZJyJ9pK3qhc9jig=="]` + if string(data) != expected { + t.Fatalf("bad: %#v", string(data)) + } +} From c52997c95b55d6fe1dec10abcd9df092b07afcbf Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 18 Sep 2014 18:57:18 -0700 Subject: [PATCH 37/80] website: documentation updates for keyring command --- .../docs/commands/keyring.html.markdown | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown index 8481b588a3..ff3285dc68 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -10,12 +10,14 @@ Command: `consul keyring` The `keyring` command is used to examine and modify the encryption keys used in Consul's [Gossip Pools](/docs/internals/gossip.html). It is capable of -distributing new encryption keys to the cluster, revoking old encryption keys, -and changing the key used by the cluster to encrypt messages. +distributing new encryption keys to the cluster, retiring old encryption keys, +and changing the keys used by the cluster to encrypt messages. Because Consul utilizes multiple gossip pools, this command will only operate -against a server node for most operations. The only operation which may be used -on client machines is the `-init` argument for initial key configuration. +against a server node for most operations. All members in a Consul cluster, +regardless of operational mode (client or server) or datacenter, will be +modified/queried each time this command is run. This helps maintain operational +simplicity by managing the multiple pools as a single unit. Consul allows multiple encryption keys to be in use simultaneously. This is intended to provide a transition state while the cluster converges. It is the @@ -23,9 +25,9 @@ responsibility of the operator to ensure that only the required encryption keys are installed on the cluster. You can ensure that a key is not installed using the `-list` and `-remove` options. -All variations of the keys command will return 0 if all nodes reply and there -are no errors. If any node fails to reply or reports failure, the exit code will -be 1. +All variations of the `keyring` command, unless otherwise specified below, will +return 0 if all nodes reply and there are no errors. If any node fails to reply +or reports failure, the exit code will be 1. ## Usage @@ -44,6 +46,9 @@ The list of available flags are: This operation can be run on both client and server nodes and requires no network connectivity. + Returns 0 if the key is successfully configured, or 1 if there were any + problems. + * `-install` - Install a new encryption key. This will broadcast the new key to all members in the cluster. From 431b366d4f9f9f4a06ca74ecd0ecd5a8fe624ff7 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 21 Sep 2014 11:21:54 -0700 Subject: [PATCH 38/80] agent: split keyring functionality out of agent.go --- command/agent/agent.go | 130 -------------------------------- command/agent/keyring.go | 138 ++++++++++++++++++++++++++++++++++ command/agent/keyring_test.go | 71 +++++++++++++++++ 3 files changed, 209 insertions(+), 130 deletions(-) create mode 100644 command/agent/keyring.go create mode 100644 command/agent/keyring_test.go diff --git a/command/agent/agent.go b/command/agent/agent.go index 169db3276b..f8b477147c 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -1,11 +1,8 @@ package agent import ( - "encoding/base64" - "encoding/json" "fmt" "io" - "io/ioutil" "log" "net" "os" @@ -15,15 +12,9 @@ import ( "github.com/hashicorp/consul/consul" "github.com/hashicorp/consul/consul/structs" - "github.com/hashicorp/memberlist" "github.com/hashicorp/serf/serf" ) -const ( - SerfLANKeyring = "serf/local.keyring" - SerfWANKeyring = "serf/remote.keyring" -) - /* The agent is the long running process that is run on every machine. It exposes an RPC interface that is used by the CLI to control the @@ -687,124 +678,3 @@ func (a *Agent) deletePid() error { } return nil } - -// loadKeyringFile will load a gossip encryption keyring out of a file. The file -// must be in JSON format and contain a list of encryption key strings. -func loadKeyringFile(c *serf.Config) error { - if c.KeyringFile == "" { - return nil - } - - if _, err := os.Stat(c.KeyringFile); err != nil { - return err - } - - // Read in the keyring file data - keyringData, err := ioutil.ReadFile(c.KeyringFile) - if err != nil { - return err - } - - // Decode keyring JSON - keys := make([]string, 0) - if err := json.Unmarshal(keyringData, &keys); err != nil { - return err - } - - // Decode base64 values - keysDecoded := make([][]byte, len(keys)) - for i, key := range keys { - keyBytes, err := base64.StdEncoding.DecodeString(key) - if err != nil { - return err - } - keysDecoded[i] = keyBytes - } - - // Create the keyring - keyring, err := memberlist.NewKeyring(keysDecoded, keysDecoded[0]) - if err != nil { - return err - } - - c.MemberlistConfig.Keyring = keyring - - // Success! - return nil -} - -// ListKeysLAN returns the keys installed on the LAN gossip pool -func (a *Agent) ListKeysLAN() (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerLAN() - return km.ListKeys() - } - km := a.client.KeyManagerLAN() - return km.ListKeys() -} - -// ListKeysWAN returns the keys installed on the WAN gossip pool -func (a *Agent) ListKeysWAN() (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerWAN() - return km.ListKeys() - } - return nil, fmt.Errorf("WAN keyring not available on client node") -} - -// InstallKeyWAN installs a new WAN gossip encryption key on server nodes -func (a *Agent) InstallKeyWAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerWAN() - return km.InstallKey(key) - } - return nil, fmt.Errorf("WAN keyring not available on client node") -} - -// InstallKeyLAN installs a new LAN gossip encryption key on all nodes -func (a *Agent) InstallKeyLAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerLAN() - return km.InstallKey(key) - } - km := a.client.KeyManagerLAN() - return km.InstallKey(key) -} - -// UseKeyWAN changes the primary WAN gossip encryption key on server nodes -func (a *Agent) UseKeyWAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerWAN() - return km.UseKey(key) - } - return nil, fmt.Errorf("WAN keyring not available on client node") -} - -// UseKeyLAN changes the primary LAN gossip encryption key on all nodes -func (a *Agent) UseKeyLAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerLAN() - return km.UseKey(key) - } - km := a.client.KeyManagerLAN() - return km.UseKey(key) -} - -// RemoveKeyWAN removes a WAN gossip encryption key on server nodes -func (a *Agent) RemoveKeyWAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerWAN() - return km.RemoveKey(key) - } - return nil, fmt.Errorf("WAN keyring not available on client node") -} - -// RemoveKeyLAN removes a LAN gossip encryption key on all nodes -func (a *Agent) RemoveKeyLAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerLAN() - return km.RemoveKey(key) - } - km := a.client.KeyManagerLAN() - return km.RemoveKey(key) -} diff --git a/command/agent/keyring.go b/command/agent/keyring.go new file mode 100644 index 0000000000..dc63b4f59a --- /dev/null +++ b/command/agent/keyring.go @@ -0,0 +1,138 @@ +package agent + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "os" + + "github.com/hashicorp/memberlist" + "github.com/hashicorp/serf/serf" +) + +const ( + SerfLANKeyring = "serf/local.keyring" + SerfWANKeyring = "serf/remote.keyring" +) + +// loadKeyringFile will load a gossip encryption keyring out of a file. The file +// must be in JSON format and contain a list of encryption key strings. +func loadKeyringFile(c *serf.Config) error { + if c.KeyringFile == "" { + return nil + } + + if _, err := os.Stat(c.KeyringFile); err != nil { + return err + } + + // Read in the keyring file data + keyringData, err := ioutil.ReadFile(c.KeyringFile) + if err != nil { + return err + } + + // Decode keyring JSON + keys := make([]string, 0) + if err := json.Unmarshal(keyringData, &keys); err != nil { + return err + } + + // Decode base64 values + keysDecoded := make([][]byte, len(keys)) + for i, key := range keys { + keyBytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return err + } + keysDecoded[i] = keyBytes + } + + // Create the keyring + keyring, err := memberlist.NewKeyring(keysDecoded, keysDecoded[0]) + if err != nil { + return err + } + + c.MemberlistConfig.Keyring = keyring + + // Success! + return nil +} + +// ListKeysLAN returns the keys installed on the LAN gossip pool +func (a *Agent) ListKeysLAN() (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerLAN() + return km.ListKeys() + } + km := a.client.KeyManagerLAN() + return km.ListKeys() +} + +// ListKeysWAN returns the keys installed on the WAN gossip pool +func (a *Agent) ListKeysWAN() (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerWAN() + return km.ListKeys() + } + return nil, fmt.Errorf("WAN keyring not available on client node") +} + +// InstallKeyWAN installs a new WAN gossip encryption key on server nodes +func (a *Agent) InstallKeyWAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerWAN() + return km.InstallKey(key) + } + return nil, fmt.Errorf("WAN keyring not available on client node") +} + +// InstallKeyLAN installs a new LAN gossip encryption key on all nodes +func (a *Agent) InstallKeyLAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerLAN() + return km.InstallKey(key) + } + km := a.client.KeyManagerLAN() + return km.InstallKey(key) +} + +// UseKeyWAN changes the primary WAN gossip encryption key on server nodes +func (a *Agent) UseKeyWAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerWAN() + return km.UseKey(key) + } + return nil, fmt.Errorf("WAN keyring not available on client node") +} + +// UseKeyLAN changes the primary LAN gossip encryption key on all nodes +func (a *Agent) UseKeyLAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerLAN() + return km.UseKey(key) + } + km := a.client.KeyManagerLAN() + return km.UseKey(key) +} + +// RemoveKeyWAN removes a WAN gossip encryption key on server nodes +func (a *Agent) RemoveKeyWAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerWAN() + return km.RemoveKey(key) + } + return nil, fmt.Errorf("WAN keyring not available on client node") +} + +// RemoveKeyLAN removes a LAN gossip encryption key on all nodes +func (a *Agent) RemoveKeyLAN(key string) (*serf.KeyResponse, error) { + if a.server != nil { + km := a.server.KeyManagerLAN() + return km.RemoveKey(key) + } + km := a.client.KeyManagerLAN() + return km.RemoveKey(key) +} diff --git a/command/agent/keyring_test.go b/command/agent/keyring_test.go new file mode 100644 index 0000000000..7807e53761 --- /dev/null +++ b/command/agent/keyring_test.go @@ -0,0 +1,71 @@ +package agent + +import ( + "os" + "testing" +) + +func TestAgent_LoadKeyrings(t *testing.T) { + key := "tbLJg26ZJyJ9pK3qhc9jig==" + + // Should be no configured keyring file by default + conf1 := nextConfig() + dir1, agent1 := makeAgent(t, conf1) + defer os.RemoveAll(dir1) + defer agent1.Shutdown() + + c := agent1.config.ConsulConfig + if c.SerfLANConfig.KeyringFile != "" { + t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) + } + if c.SerfLANConfig.MemberlistConfig.Keyring != nil { + t.Fatalf("keyring should not be loaded") + } + if c.SerfWANConfig.KeyringFile != "" { + t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) + } + if c.SerfWANConfig.MemberlistConfig.Keyring != nil { + t.Fatalf("keyring should not be loaded") + } + + // Server should auto-load LAN and WAN keyring files + conf2 := nextConfig() + dir2, agent2 := makeAgentKeyring(t, conf2, key) + defer os.RemoveAll(dir2) + defer agent2.Shutdown() + + c = agent2.config.ConsulConfig + if c.SerfLANConfig.KeyringFile == "" { + t.Fatalf("should have keyring file") + } + if c.SerfLANConfig.MemberlistConfig.Keyring == nil { + t.Fatalf("keyring should be loaded") + } + if c.SerfWANConfig.KeyringFile == "" { + t.Fatalf("should have keyring file") + } + if c.SerfWANConfig.MemberlistConfig.Keyring == nil { + t.Fatalf("keyring should be loaded") + } + + // Client should auto-load only the LAN keyring file + conf3 := nextConfig() + conf3.Server = false + dir3, agent3 := makeAgentKeyring(t, conf3, key) + defer os.RemoveAll(dir3) + defer agent3.Shutdown() + + c = agent3.config.ConsulConfig + if c.SerfLANConfig.KeyringFile == "" { + t.Fatalf("should have keyring file") + } + if c.SerfLANConfig.MemberlistConfig.Keyring == nil { + t.Fatalf("keyring should be loaded") + } + if c.SerfWANConfig.KeyringFile != "" { + t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) + } + if c.SerfWANConfig.MemberlistConfig.Keyring != nil { + t.Fatalf("keyring should not be loaded") + } +} From cfbf2b4f9485da5ad46d799456f336ba2a0bf07d Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 21 Sep 2014 11:52:28 -0700 Subject: [PATCH 39/80] command: allow wan ring to be modified separately from lan pools --- command/agent/command_test.go | 13 ++-- command/keyring.go | 72 +++++++++++-------- command/keyring_test.go | 65 +++++++++++++---- .../docs/commands/keyring.html.markdown | 9 +-- 4 files changed, 103 insertions(+), 56 deletions(-) diff --git a/command/agent/command_test.go b/command/agent/command_test.go index db7d1c9a5c..9c1bf4db5d 100644 --- a/command/agent/command_test.go +++ b/command/agent/command_test.go @@ -1,17 +1,12 @@ package agent import ( -<<<<<<< HEAD "fmt" "io/ioutil" "log" "os" -======= - "github.com/hashicorp/consul/testutil" - "github.com/mitchellh/cli" - "io/ioutil" "path/filepath" ->>>>>>> command: test generated keyring file content and conflicting args for agent + "strings" "testing" "github.com/hashicorp/consul/testutil" @@ -45,7 +40,6 @@ func TestValidDatacenter(t *testing.T) { } } -<<<<<<< HEAD func TestRetryJoin(t *testing.T) { dir, agent := makeAgent(t, nextConfig()) defer os.RemoveAll(dir) @@ -169,7 +163,9 @@ func TestRetryJoinWanFail(t *testing.T) { if code := cmd.Run(args); code == 0 { t.Fatalf("bad: %d", code) -======= + } +} + func TestArgConflict(t *testing.T) { ui := new(cli.MockUi) c := &Command{Ui: ui} @@ -196,6 +192,5 @@ func TestArgConflict(t *testing.T) { } if !strings.Contains(ui.ErrorWriter.String(), "keyring files exist") { t.Fatalf("bad: %#v", ui.ErrorWriter.String()) ->>>>>>> command: test generated keyring file content and conflicting args for agent } } diff --git a/command/keyring.go b/command/keyring.go index 83293a644c..630f0ba12c 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -22,7 +22,7 @@ type KeyringCommand struct { func (c *KeyringCommand) Run(args []string) int { var installKey, useKey, removeKey, init, dataDir string - var listKeys bool + var listKeys, wan bool cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } @@ -33,6 +33,7 @@ func (c *KeyringCommand) Run(args []string) int { cmdFlags.BoolVar(&listKeys, "list", false, "list keys") cmdFlags.StringVar(&init, "init", "", "initialize keyring") cmdFlags.StringVar(&dataDir, "data-dir", "", "data directory") + cmdFlags.BoolVar(&wan, "wan", false, "operate on wan keyring") rpcAddr := RPCAddrFlag(cmdFlags) if err := cmdFlags.Parse(args); err != nil { @@ -105,51 +106,57 @@ func (c *KeyringCommand) Run(args []string) int { } if listKeys { - c.Ui.Info("Asking all WAN members for installed keys...") - if rval := c.listKeysOperation(client.ListKeysWAN); rval != 0 { - return rval + if wan { + c.Ui.Info("Asking all WAN members for installed keys...") + return c.listKeysOperation(client.ListKeysWAN) } c.Ui.Info("Asking all LAN members for installed keys...") - if rval := c.listKeysOperation(client.ListKeysLAN); rval != 0 { - return rval - } - return 0 + return c.listKeysOperation(client.ListKeysLAN) } if installKey != "" { - c.Ui.Info("Installing new WAN gossip encryption key...") - if rval := c.keyOperation(installKey, client.InstallKeyWAN); rval != 0 { - return rval - } - c.Ui.Info("Installing new LAN gossip encryption key...") - if rval := c.keyOperation(installKey, client.InstallKeyLAN); rval != 0 { - return rval + if wan { + c.Ui.Info("Installing new WAN gossip encryption key...") + if rval := c.keyOperation(installKey, client.InstallKeyWAN); rval != 0 { + return rval + } + } else { + c.Ui.Info("Installing new LAN gossip encryption key...") + if rval := c.keyOperation(installKey, client.InstallKeyLAN); rval != 0 { + return rval + } } c.Ui.Info("Successfully installed key!") return 0 } if useKey != "" { - c.Ui.Info("Changing primary WAN gossip encryption key...") - if rval := c.keyOperation(useKey, client.UseKeyWAN); rval != 0 { - return rval - } - c.Ui.Info("Changing primary LAN gossip encryption key...") - if rval := c.keyOperation(useKey, client.UseKeyLAN); rval != 0 { - return rval + if wan { + c.Ui.Info("Changing primary WAN gossip encryption key...") + if rval := c.keyOperation(useKey, client.UseKeyWAN); rval != 0 { + return rval + } + } else { + c.Ui.Info("Changing primary LAN gossip encryption key...") + if rval := c.keyOperation(useKey, client.UseKeyLAN); rval != 0 { + return rval + } } c.Ui.Info("Successfully changed primary key!") return 0 } if removeKey != "" { - c.Ui.Info("Removing WAN gossip encryption key...") - if rval := c.keyOperation(removeKey, client.RemoveKeyWAN); rval != 0 { - return rval - } - c.Ui.Info("Removing LAN gossip encryption key...") - if rval := c.keyOperation(removeKey, client.RemoveKeyLAN); rval != 0 { - return rval + if wan { + c.Ui.Info("Removing WAN gossip encryption key...") + if rval := c.keyOperation(removeKey, client.RemoveKeyWAN); rval != 0 { + return rval + } + } else { + c.Ui.Info("Removing LAN gossip encryption key...") + if rval := c.keyOperation(removeKey, client.RemoveKeyLAN); rval != 0 { + return rval + } } c.Ui.Info("Successfully removed key!") return 0 @@ -258,8 +265,9 @@ Usage: consul keyring [options] functionality provides the ability to perform key rotation cluster-wide, without disrupting the cluster. - With the exception of the -init argument, all other operations performed by - this command can only be run against server nodes. + With the exception of the -init argument, all operations performed by this + command can only be run against server nodes. All operations default to the + LAN gossip pool. Options: @@ -275,6 +283,8 @@ Options: -init= Create the initial keyring files for Consul to use containing the provided key. The -data-dir argument is required with this option. + -wan Operate on the WAN keyring instead of the LAN + keyring (default). -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. ` return strings.TrimSpace(helpText) diff --git a/command/keyring_test.go b/command/keyring_test.go index cc18ad7996..c7c0847f46 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -25,7 +25,7 @@ func TestKeyringCommandRun(t *testing.T) { defer a1.Shutdown() // The keyring was initialized with only the provided key - out := listKeys(t, a1.addr) + out := listKeys(t, a1.addr, false) if !strings.Contains(out, key1) { t.Fatalf("bad: %#v", out) } @@ -34,30 +34,56 @@ func TestKeyringCommandRun(t *testing.T) { } // Install the second key onto the keyring - installKey(t, a1.addr, key2) + installKey(t, a1.addr, key2, false) // Both keys should be present - out = listKeys(t, a1.addr) + out = listKeys(t, a1.addr, false) for _, key := range []string{key1, key2} { if !strings.Contains(out, key) { t.Fatalf("bad: %#v", out) } } + // WAN keyring is untouched + out = listKeys(t, a1.addr, true) + if strings.Contains(out, key2) { + t.Fatalf("bad: %#v", out) + } + // Change out the primary key - useKey(t, a1.addr, key2) + useKey(t, a1.addr, key2, false) // Remove the original key - removeKey(t, a1.addr, key1) + removeKey(t, a1.addr, key1, false) // Make sure only the new key is present - out = listKeys(t, a1.addr) + out = listKeys(t, a1.addr, false) if strings.Contains(out, key1) { t.Fatalf("bad: %#v", out) } if !strings.Contains(out, key2) { t.Fatalf("bad: %#v", out) } + + // WAN keyring is still untouched + out = listKeys(t, a1.addr, true) + if !strings.Contains(out, key1) { + t.Fatalf("bad: %#v", out) + } + + // Rotate out the WAN key + installKey(t, a1.addr, key2, true) + useKey(t, a1.addr, key2, true) + removeKey(t, a1.addr, key1, true) + + // WAN keyring now has only the proper key + out = listKeys(t, a1.addr, true) + if !strings.Contains(out, key2) { + t.Fatalf("bad: %#v", out) + } + if strings.Contains(out, key1) { + t.Fatalf("bad: %#v", out) + } } func TestKeyringCommandRun_help(t *testing.T) { @@ -87,7 +113,7 @@ func TestKeyringCommandRun_failedConnection(t *testing.T) { } } -func TestKeyCommandRun_initKeyringFail(t *testing.T) { +func TestKeyringCommandRun_initKeyringFail(t *testing.T) { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} @@ -106,7 +132,7 @@ func TestKeyCommandRun_initKeyringFail(t *testing.T) { } } -func TestKeyCommandRun_initKeyring(t *testing.T) { +func TestKeyringCommandRun_initKeyring(t *testing.T) { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} @@ -153,11 +179,14 @@ func TestKeyCommandRun_initKeyring(t *testing.T) { } } -func listKeys(t *testing.T, addr string) string { +func listKeys(t *testing.T, addr string, wan bool) string { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} args := []string{"-list", "-rpc-addr=" + addr} + if wan { + args = append(args, "-wan") + } code := c.Run(args) if code != 0 { @@ -167,33 +196,45 @@ func listKeys(t *testing.T, addr string) string { return ui.OutputWriter.String() } -func installKey(t *testing.T, addr string, key string) { +func installKey(t *testing.T, addr string, key string, wan bool) { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} args := []string{"-install=" + key, "-rpc-addr=" + addr} + if wan { + args = append(args, "-wan") + } + code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } } -func useKey(t *testing.T, addr string, key string) { +func useKey(t *testing.T, addr string, key string, wan bool) { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} args := []string{"-use=" + key, "-rpc-addr=" + addr} + if wan { + args = append(args, "-wan") + } + code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } } -func removeKey(t *testing.T, addr string, key string) { +func removeKey(t *testing.T, addr string, key string, wan bool) { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} args := []string{"-remove=" + key, "-rpc-addr=" + addr} + if wan { + args = append(args, "-wan") + } + code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown index ff3285dc68..6a635746c2 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -14,10 +14,9 @@ distributing new encryption keys to the cluster, retiring old encryption keys, and changing the keys used by the cluster to encrypt messages. Because Consul utilizes multiple gossip pools, this command will only operate -against a server node for most operations. All members in a Consul cluster, -regardless of operational mode (client or server) or datacenter, will be -modified/queried each time this command is run. This helps maintain operational -simplicity by managing the multiple pools as a single unit. +against a server node for most operations. By default, all operations carried +out by this command are run against the LAN gossip pool in the datacenter of the +agent. Consul allows multiple encryption keys to be in use simultaneously. This is intended to provide a transition state while the cluster converges. It is the @@ -60,4 +59,6 @@ The list of available flags are: * `-list` - List all keys currently in use within the cluster. +* `-wan` - Operate on the WAN keyring instead of the LAN keyring (default) + * `-rpc-addr` - RPC address of the Consul agent. From 8dec2744daa5a70cf1188c454a64bfb1c9d7bf07 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 24 Sep 2014 16:39:14 -0700 Subject: [PATCH 40/80] consul: refactor keyring, repeat RPC calls to all DC's --- command/agent/keyring.go | 101 +++++++----------- command/agent/rpc.go | 128 ++++++++-------------- command/agent/rpc_client.go | 66 +++++------- command/keyring.go | 49 +++------ consul/internal_endpoint.go | 205 ++++++++++++++++++++++++++++++++++++ consul/rpc.go | 12 +++ consul/structs/structs.go | 19 ++++ 7 files changed, 354 insertions(+), 226 deletions(-) diff --git a/command/agent/keyring.go b/command/agent/keyring.go index dc63b4f59a..e816c3d784 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" + "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/memberlist" "github.com/hashicorp/serf/serf" ) @@ -61,78 +62,48 @@ func loadKeyringFile(c *serf.Config) error { return nil } -// ListKeysLAN returns the keys installed on the LAN gossip pool -func (a *Agent) ListKeysLAN() (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerLAN() - return km.ListKeys() +// keyringProcess is used to abstract away the semantic similarities in +// performing various operations on the encryption keyring. +func (a *Agent) keyringProcess( + method string, + args *structs.KeyringRequest) (*structs.KeyringResponse, error) { + + var reply structs.KeyringResponse + if a.server == nil { + return nil, fmt.Errorf("keyring operations must run against a server node") } - km := a.client.KeyManagerLAN() - return km.ListKeys() + if err := a.RPC(method, args, &reply); err != nil { + return &reply, err + } + + return &reply, nil } -// ListKeysWAN returns the keys installed on the WAN gossip pool -func (a *Agent) ListKeysWAN() (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerWAN() - return km.ListKeys() - } - return nil, fmt.Errorf("WAN keyring not available on client node") +// 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() (*structs.KeyringResponse, error) { + args := structs.KeyringRequest{} + args.AllowStale = true + return a.keyringProcess("Internal.ListKeys", &args) } -// InstallKeyWAN installs a new WAN gossip encryption key on server nodes -func (a *Agent) InstallKeyWAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerWAN() - return km.InstallKey(key) - } - return nil, fmt.Errorf("WAN keyring not available on client node") +// InstallKey installs a new gossip encryption key +func (a *Agent) InstallKey(key string) (*structs.KeyringResponse, error) { + args := structs.KeyringRequest{Key: key} + args.AllowStale = true + return a.keyringProcess("Internal.InstallKey", &args) } -// InstallKeyLAN installs a new LAN gossip encryption key on all nodes -func (a *Agent) InstallKeyLAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerLAN() - return km.InstallKey(key) - } - km := a.client.KeyManagerLAN() - return km.InstallKey(key) +// UseKey changes the primary encryption key used to encrypt messages +func (a *Agent) UseKey(key string) (*structs.KeyringResponse, error) { + args := structs.KeyringRequest{Key: key} + args.AllowStale = true + return a.keyringProcess("Internal.UseKey", &args) } -// UseKeyWAN changes the primary WAN gossip encryption key on server nodes -func (a *Agent) UseKeyWAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerWAN() - return km.UseKey(key) - } - return nil, fmt.Errorf("WAN keyring not available on client node") -} - -// UseKeyLAN changes the primary LAN gossip encryption key on all nodes -func (a *Agent) UseKeyLAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerLAN() - return km.UseKey(key) - } - km := a.client.KeyManagerLAN() - return km.UseKey(key) -} - -// RemoveKeyWAN removes a WAN gossip encryption key on server nodes -func (a *Agent) RemoveKeyWAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerWAN() - return km.RemoveKey(key) - } - return nil, fmt.Errorf("WAN keyring not available on client node") -} - -// RemoveKeyLAN removes a LAN gossip encryption key on all nodes -func (a *Agent) RemoveKeyLAN(key string) (*serf.KeyResponse, error) { - if a.server != nil { - km := a.server.KeyManagerLAN() - return km.RemoveKey(key) - } - km := a.client.KeyManagerLAN() - return km.RemoveKey(key) +// RemoveKey will remove a gossip encryption key from the keyring +func (a *Agent) RemoveKey(key string) (*structs.KeyringResponse, error) { + args := structs.KeyringRequest{Key: key} + args.AllowStale = true + return a.keyringProcess("Internal.RemoveKey", &args) } diff --git a/command/agent/rpc.go b/command/agent/rpc.go index bf9b42d527..983cc3481c 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -24,15 +24,17 @@ package agent import ( "bufio" "fmt" - "github.com/hashicorp/go-msgpack/codec" - "github.com/hashicorp/logutils" - "github.com/hashicorp/serf/serf" "io" "log" "net" "os" "strings" "sync" + + "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/go-msgpack/codec" + "github.com/hashicorp/logutils" + "github.com/hashicorp/serf/serf" ) const ( @@ -41,24 +43,20 @@ const ( ) const ( - handshakeCommand = "handshake" - forceLeaveCommand = "force-leave" - joinCommand = "join" - membersLANCommand = "members-lan" - membersWANCommand = "members-wan" - stopCommand = "stop" - monitorCommand = "monitor" - leaveCommand = "leave" - statsCommand = "stats" - reloadCommand = "reload" - listKeysLANCommand = "list-keys-lan" - listKeysWANCommand = "list-keys-wan" - installKeyLANCommand = "install-key-lan" - installKeyWANCommand = "install-key-wan" - useKeyLANCommand = "use-key-lan" - useKeyWANCommand = "use-key-wan" - removeKeyLANCommand = "remove-key-lan" - removeKeyWANCommand = "remove-key-wan" + handshakeCommand = "handshake" + forceLeaveCommand = "force-leave" + joinCommand = "join" + membersLANCommand = "members-lan" + membersWANCommand = "members-wan" + stopCommand = "stop" + monitorCommand = "monitor" + leaveCommand = "leave" + statsCommand = "stats" + reloadCommand = "reload" + installKeyCommand = "install-key" + useKeyCommand = "use-key" + removeKeyCommand = "remove-key" + listKeysCommand = "list-keys" ) const ( @@ -117,10 +115,10 @@ type keyRequest struct { type keyResponse struct { Messages map[string]string + Keys map[string]int NumNodes int NumResp int NumErr int - Keys map[string]int } type membersResponse struct { @@ -393,17 +391,8 @@ func (i *AgentRPC) handleRequest(client *rpcClient, reqHeader *requestHeader) er case reloadCommand: return i.handleReload(client, seq) - case listKeysLANCommand, listKeysWANCommand: - return i.handleListKeys(client, seq, command) - - case installKeyLANCommand, installKeyWANCommand: - return i.handleGossipKeyChange(client, seq, command) - - case useKeyLANCommand, useKeyWANCommand: - return i.handleGossipKeyChange(client, seq, command) - - case removeKeyLANCommand, removeKeyWANCommand: - return i.handleGossipKeyChange(client, seq, command) + case installKeyCommand, useKeyCommand, removeKeyCommand, listKeysCommand: + return i.handleKeyring(client, seq, command) default: respHeader := responseHeader{Seq: seq, Error: unsupportedCommand} @@ -615,56 +604,27 @@ func (i *AgentRPC) handleReload(client *rpcClient, seq uint64) error { return client.Send(&resp, nil) } -func (i *AgentRPC) handleListKeys(client *rpcClient, seq uint64, cmd string) error { - var queryResp *serf.KeyResponse - var err error - - switch cmd { - case listKeysWANCommand: - queryResp, err = i.agent.ListKeysWAN() - default: - queryResp, err = i.agent.ListKeysLAN() - } - - header := responseHeader{ - Seq: seq, - Error: errToString(err), - } - - resp := keyResponse{ - Messages: queryResp.Messages, - Keys: queryResp.Keys, - NumResp: queryResp.NumResp, - NumErr: queryResp.NumErr, - NumNodes: queryResp.NumNodes, - } - - return client.Send(&header, &resp) -} - -func (i *AgentRPC) handleGossipKeyChange(client *rpcClient, seq uint64, cmd string) error { +func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) error { var req keyRequest + var queryResp *structs.KeyringResponse var resp keyResponse - var queryResp *serf.KeyResponse var err error - if err = client.dec.Decode(&req); err != nil { - return fmt.Errorf("decode failed: %v", err) + if cmd != listKeysCommand { + if err = client.dec.Decode(&req); err != nil { + return fmt.Errorf("decode failed: %v", err) + } } switch cmd { - case installKeyWANCommand: - queryResp, err = i.agent.InstallKeyWAN(req.Key) - case installKeyLANCommand: - queryResp, err = i.agent.InstallKeyLAN(req.Key) - case useKeyWANCommand: - queryResp, err = i.agent.UseKeyWAN(req.Key) - case useKeyLANCommand: - queryResp, err = i.agent.UseKeyLAN(req.Key) - case removeKeyWANCommand: - queryResp, err = i.agent.RemoveKeyWAN(req.Key) - case removeKeyLANCommand: - queryResp, err = i.agent.RemoveKeyLAN(req.Key) + case listKeysCommand: + queryResp, err = i.agent.ListKeys() + case installKeyCommand: + queryResp, err = i.agent.InstallKey(req.Key) + case useKeyCommand: + queryResp, err = i.agent.UseKey(req.Key) + case removeKeyCommand: + queryResp, err = i.agent.RemoveKey(req.Key) default: respHeader := responseHeader{Seq: seq, Error: unsupportedCommand} client.Send(&respHeader, nil) @@ -676,15 +636,17 @@ func (i *AgentRPC) handleGossipKeyChange(client *rpcClient, seq uint64, cmd stri Error: errToString(err), } - resp = keyResponse{ - Messages: queryResp.Messages, - Keys: queryResp.Keys, - NumResp: queryResp.NumResp, - NumErr: queryResp.NumErr, - NumNodes: queryResp.NumNodes, + if queryResp != nil { + resp = keyResponse{ + Messages: queryResp.Messages, + Keys: queryResp.Keys, + NumNodes: queryResp.NumNodes, + NumResp: queryResp.NumResp, + NumErr: queryResp.NumErr, + } } - return client.Send(&header, &resp) + return client.Send(&header, resp) } // Used to convert an error to a string representation diff --git a/command/agent/rpc_client.go b/command/agent/rpc_client.go index 5d82dc8fa8..36a54057e0 100644 --- a/command/agent/rpc_client.go +++ b/command/agent/rpc_client.go @@ -176,60 +176,44 @@ func (c *RPCClient) WANMembers() ([]Member, error) { return resp.Members, err } -func (c *RPCClient) ListKeysLAN() (map[string]int, int, map[string]string, error) { +func (c *RPCClient) ListKeys() (map[string]int, int, map[string]string, error) { header := requestHeader{ - Command: listKeysLANCommand, + Command: listKeysCommand, Seq: c.getSeq(), } resp := new(keyResponse) - err := c.genericRPC(&header, nil, resp) return resp.Keys, resp.NumNodes, resp.Messages, err } -func (c *RPCClient) ListKeysWAN() (map[string]int, int, map[string]string, error) { +func (c *RPCClient) InstallKey(key string) (map[string]string, error) { header := requestHeader{ - Command: listKeysWANCommand, + Command: installKeyCommand, Seq: c.getSeq(), } - resp := new(keyResponse) - - err := c.genericRPC(&header, nil, resp) - return resp.Keys, resp.NumNodes, resp.Messages, err -} - -func (c *RPCClient) InstallKeyWAN(key string) (map[string]string, error) { - return c.changeGossipKey(key, installKeyWANCommand) -} - -func (c *RPCClient) InstallKeyLAN(key string) (map[string]string, error) { - return c.changeGossipKey(key, installKeyLANCommand) -} - -func (c *RPCClient) UseKeyWAN(key string) (map[string]string, error) { - return c.changeGossipKey(key, useKeyWANCommand) -} - -func (c *RPCClient) UseKeyLAN(key string) (map[string]string, error) { - return c.changeGossipKey(key, useKeyLANCommand) -} - -func (c *RPCClient) RemoveKeyWAN(key string) (map[string]string, error) { - return c.changeGossipKey(key, removeKeyWANCommand) -} - -func (c *RPCClient) RemoveKeyLAN(key string) (map[string]string, error) { - return c.changeGossipKey(key, removeKeyLANCommand) -} - -func (c *RPCClient) changeGossipKey(key, cmd string) (map[string]string, error) { - header := requestHeader{ - Command: cmd, - Seq: c.getSeq(), - } - req := keyRequest{key} + resp := new(keyResponse) + err := c.genericRPC(&header, &req, resp) + return resp.Messages, err +} +func (c *RPCClient) UseKey(key string) (map[string]string, error) { + header := requestHeader{ + Command: useKeyCommand, + Seq: c.getSeq(), + } + req := keyRequest{key} + resp := new(keyResponse) + err := c.genericRPC(&header, &req, resp) + return resp.Messages, err +} + +func (c *RPCClient) RemoveKey(key string) (map[string]string, error) { + header := requestHeader{ + Command: removeKeyCommand, + Seq: c.getSeq(), + } + req := keyRequest{key} resp := new(keyResponse) err := c.genericRPC(&header, &req, resp) return resp.Messages, err diff --git a/command/keyring.go b/command/keyring.go index 630f0ba12c..eaa298dcda 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -106,59 +106,34 @@ func (c *KeyringCommand) Run(args []string) int { } if listKeys { - if wan { - c.Ui.Info("Asking all WAN members for installed keys...") - return c.listKeysOperation(client.ListKeysWAN) - } - c.Ui.Info("Asking all LAN members for installed keys...") - return c.listKeysOperation(client.ListKeysLAN) + c.Ui.Info("Asking all members for installed keys...") + return c.listKeysOperation(client.ListKeys) } if installKey != "" { - if wan { - c.Ui.Info("Installing new WAN gossip encryption key...") - if rval := c.keyOperation(installKey, client.InstallKeyWAN); rval != 0 { - return rval - } - } else { - c.Ui.Info("Installing new LAN gossip encryption key...") - if rval := c.keyOperation(installKey, client.InstallKeyLAN); rval != 0 { - return rval - } + c.Ui.Info("Installing new gossip encryption key...") + if rval := c.keyOperation(installKey, client.InstallKey); rval != 0 { + return rval } c.Ui.Info("Successfully installed key!") return 0 } if useKey != "" { - if wan { - c.Ui.Info("Changing primary WAN gossip encryption key...") - if rval := c.keyOperation(useKey, client.UseKeyWAN); rval != 0 { - return rval - } - } else { - c.Ui.Info("Changing primary LAN gossip encryption key...") - if rval := c.keyOperation(useKey, client.UseKeyLAN); rval != 0 { - return rval - } + c.Ui.Info("Changing primary gossip encryption key...") + if rval := c.keyOperation(useKey, client.UseKey); rval != 0 { + return rval } c.Ui.Info("Successfully changed primary key!") return 0 } if removeKey != "" { - if wan { - c.Ui.Info("Removing WAN gossip encryption key...") - if rval := c.keyOperation(removeKey, client.RemoveKeyWAN); rval != 0 { - return rval - } - } else { - c.Ui.Info("Removing LAN gossip encryption key...") - if rval := c.keyOperation(removeKey, client.RemoveKeyLAN); rval != 0 { - return rval - } + c.Ui.Info("Removing gossip encryption key...") + if rval := c.keyOperation(removeKey, client.RemoveKey); rval != 0 { + return rval } - c.Ui.Info("Successfully removed key!") + c.Ui.Info("Successfully removed gossip encryption key!") return 0 } diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 5a38b31a22..7cc6487478 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -1,7 +1,10 @@ package consul import ( + "fmt" + "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/serf/serf" ) // Internal endpoint is used to query the miscellaneous info that @@ -62,3 +65,205 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, // Fire the event return m.srv.UserEvent(args.Name, args.Payload) } + +// TODO(ryanuber): Clean up all of these methods +func (m *Internal) InstallKey(args *structs.KeyringRequest, + reply *structs.KeyringResponse) error { + + var respLAN, respWAN *serf.KeyResponse + var err error + + if reply.Messages == nil { + reply.Messages = make(map[string]string) + } + if reply.Keys == nil { + reply.Keys = make(map[string]int) + } + + m.srv.setQueryMeta(&reply.QueryMeta) + + // Do a LAN key install. This will be invoked in each DC once the RPC call + // is forwarded below. + respLAN, err = m.srv.KeyManagerLAN().InstallKey(args.Key) + for node, msg := range respLAN.Messages { + reply.Messages["client."+node+"."+m.srv.config.Datacenter] = msg + } + reply.NumResp += respLAN.NumResp + reply.NumErr += respLAN.NumErr + reply.NumNodes += respLAN.NumNodes + if err != nil { + return fmt.Errorf("failed rotating LAN keyring in %s: %s", + m.srv.config.Datacenter, + err) + } + + if !args.Forwarded { + // Only perform WAN key rotation once. + respWAN, err = m.srv.KeyManagerWAN().InstallKey(args.Key) + if err != nil { + return err + } + for node, msg := range respWAN.Messages { + reply.Messages["server."+node] = msg + } + reply.NumResp += respWAN.NumResp + reply.NumErr += respWAN.NumErr + reply.NumNodes += respWAN.NumNodes + + // Mark key rotation as being already forwarded, then forward. + args.Forwarded = true + return m.srv.forwardAll("Internal.InstallKey", args, reply) + } + + return nil +} + +func (m *Internal) UseKey(args *structs.KeyringRequest, + reply *structs.KeyringResponse) error { + var respLAN, respWAN *serf.KeyResponse + var err error + + if reply.Messages == nil { + reply.Messages = make(map[string]string) + } + if reply.Keys == nil { + reply.Keys = make(map[string]int) + } + + m.srv.setQueryMeta(&reply.QueryMeta) + + // Do a LAN key install. This will be invoked in each DC once the RPC call + // is forwarded below. + respLAN, err = m.srv.KeyManagerLAN().UseKey(args.Key) + for node, msg := range respLAN.Messages { + reply.Messages["client."+node+"."+m.srv.config.Datacenter] = msg + } + reply.NumResp += respLAN.NumResp + reply.NumErr += respLAN.NumErr + reply.NumNodes += respLAN.NumNodes + if err != nil { + return fmt.Errorf("failed rotating LAN keyring in %s: %s", + m.srv.config.Datacenter, + err) + } + + if !args.Forwarded { + // Only perform WAN key rotation once. + respWAN, err = m.srv.KeyManagerWAN().UseKey(args.Key) + if err != nil { + return err + } + for node, msg := range respWAN.Messages { + reply.Messages["server."+node] = msg + } + reply.NumResp += respWAN.NumResp + reply.NumErr += respWAN.NumErr + reply.NumNodes += respWAN.NumNodes + + // Mark key rotation as being already forwarded, then forward. + args.Forwarded = true + return m.srv.forwardAll("Internal.UseKey", args, reply) + } + + return nil +} + +func (m *Internal) RemoveKey(args *structs.KeyringRequest, + reply *structs.KeyringResponse) error { + var respLAN, respWAN *serf.KeyResponse + var err error + + if reply.Messages == nil { + reply.Messages = make(map[string]string) + } + if reply.Keys == nil { + reply.Keys = make(map[string]int) + } + + m.srv.setQueryMeta(&reply.QueryMeta) + + // Do a LAN key install. This will be invoked in each DC once the RPC call + // is forwarded below. + respLAN, err = m.srv.KeyManagerLAN().RemoveKey(args.Key) + for node, msg := range respLAN.Messages { + reply.Messages["client."+node+"."+m.srv.config.Datacenter] = msg + } + reply.NumResp += respLAN.NumResp + reply.NumErr += respLAN.NumErr + reply.NumNodes += respLAN.NumNodes + if err != nil { + return fmt.Errorf("failed rotating LAN keyring in %s: %s", + m.srv.config.Datacenter, + err) + } + + if !args.Forwarded { + // Only perform WAN key rotation once. + respWAN, err = m.srv.KeyManagerWAN().RemoveKey(args.Key) + if err != nil { + return err + } + for node, msg := range respWAN.Messages { + reply.Messages["server."+node] = msg + } + reply.NumResp += respWAN.NumResp + reply.NumErr += respWAN.NumErr + reply.NumNodes += respWAN.NumNodes + + // Mark key rotation as being already forwarded, then forward. + args.Forwarded = true + return m.srv.forwardAll("Internal.RemoveKey", args, reply) + } + + return nil +} + +func (m *Internal) ListKeys(args *structs.KeyringRequest, + reply *structs.KeyringResponse) error { + var respLAN, respWAN *serf.KeyResponse + var err error + + if reply.Messages == nil { + reply.Messages = make(map[string]string) + } + if reply.Keys == nil { + reply.Keys = make(map[string]int) + } + + m.srv.setQueryMeta(&reply.QueryMeta) + + // Do a LAN key install. This will be invoked in each DC once the RPC call + // is forwarded below. + respLAN, err = m.srv.KeyManagerLAN().ListKeys() + for node, msg := range respLAN.Messages { + reply.Messages["client."+node+"."+m.srv.config.Datacenter] = msg + } + reply.NumResp += respLAN.NumResp + reply.NumErr += respLAN.NumErr + reply.NumNodes += respLAN.NumNodes + if err != nil { + return fmt.Errorf("failed rotating LAN keyring in %s: %s", + m.srv.config.Datacenter, + err) + } + + if !args.Forwarded { + // Only perform WAN key rotation once. + respWAN, err = m.srv.KeyManagerWAN().ListKeys() + if err != nil { + return err + } + for node, msg := range respWAN.Messages { + reply.Messages["server."+node] = msg + } + reply.NumResp += respWAN.NumResp + reply.NumErr += respWAN.NumErr + reply.NumNodes += respWAN.NumNodes + + // Mark key rotation as being already forwarded, then forward. + args.Forwarded = true + return m.srv.forwardAll("Internal.ListKeys", args, reply) + } + + return nil +} diff --git a/consul/rpc.go b/consul/rpc.go index cd5c36ebd3..4526ca75b9 100644 --- a/consul/rpc.go +++ b/consul/rpc.go @@ -223,6 +223,18 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{ return s.connPool.RPC(server.Addr, server.Version, method, args, reply) } +// forwardAll forwards a single RPC call to every known datacenter. +func (s *Server) forwardAll(method string, args, reply interface{}) error { + for dc, _ := range s.remoteConsuls { + if dc != s.config.Datacenter { + if err := s.forwardDC(method, dc, args, reply); err != nil { + return err + } + } + } + return nil +} + // raftApply is used to encode a message, run it through raft, and return // the FSM response along with any errors func (s *Server) raftApply(t structs.MessageType, msg interface{}) (interface{}, error) { diff --git a/consul/structs/structs.go b/consul/structs/structs.go index c2585b1320..31a6e319f3 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -531,3 +531,22 @@ func Encode(t MessageType, msg interface{}) ([]byte, error) { err := codec.NewEncoder(&buf, msgpackHandle).Encode(msg) return buf.Bytes(), err } + +// KeyringRequest encapsulates a request to modify an encryption keyring. +// It can be used for install, remove, or use key type operations. +type KeyringRequest struct { + Key string + Forwarded bool + QueryOptions +} + +// KeyringResponse is a unified key response and can be used for install, +// remove, use, as well as listing key queries. +type KeyringResponse struct { + Messages map[string]string + Keys map[string]int + NumNodes int + NumResp int + NumErr int + QueryMeta +} From 2bdeaa0c6ad7d981ae8b66f3af43f1e4a50ae97a Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 24 Sep 2014 18:30:34 -0700 Subject: [PATCH 41/80] consul: restructuring --- command/agent/keyring.go | 12 +- command/agent/rpc.go | 29 ++-- consul/internal_endpoint.go | 261 ++++++++++++++---------------------- consul/structs/structs.go | 16 ++- 4 files changed, 134 insertions(+), 184 deletions(-) diff --git a/command/agent/keyring.go b/command/agent/keyring.go index e816c3d784..827aa76bc7 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -66,9 +66,9 @@ func loadKeyringFile(c *serf.Config) error { // performing various operations on the encryption keyring. func (a *Agent) keyringProcess( method string, - args *structs.KeyringRequest) (*structs.KeyringResponse, error) { + args *structs.KeyringRequest) (*structs.KeyringResponses, error) { - var reply structs.KeyringResponse + var reply structs.KeyringResponses if a.server == nil { return nil, fmt.Errorf("keyring operations must run against a server node") } @@ -81,28 +81,28 @@ func (a *Agent) keyringProcess( // 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() (*structs.KeyringResponse, error) { +func (a *Agent) ListKeys() (*structs.KeyringResponses, error) { args := structs.KeyringRequest{} args.AllowStale = true return a.keyringProcess("Internal.ListKeys", &args) } // InstallKey installs a new gossip encryption key -func (a *Agent) InstallKey(key string) (*structs.KeyringResponse, error) { +func (a *Agent) InstallKey(key string) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key} args.AllowStale = true return a.keyringProcess("Internal.InstallKey", &args) } // UseKey changes the primary encryption key used to encrypt messages -func (a *Agent) UseKey(key string) (*structs.KeyringResponse, error) { +func (a *Agent) UseKey(key string) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key} args.AllowStale = true return a.keyringProcess("Internal.UseKey", &args) } // RemoveKey will remove a gossip encryption key from the keyring -func (a *Agent) RemoveKey(key string) (*structs.KeyringResponse, error) { +func (a *Agent) RemoveKey(key string) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key} args.AllowStale = true return a.keyringProcess("Internal.RemoveKey", &args) diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 983cc3481c..5d6c5c8a7c 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -606,7 +606,7 @@ func (i *AgentRPC) handleReload(client *rpcClient, seq uint64) error { func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) error { var req keyRequest - var queryResp *structs.KeyringResponse + var queryResp *structs.KeyringResponses var resp keyResponse var err error @@ -636,14 +636,27 @@ func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) erro Error: errToString(err), } - if queryResp != nil { - resp = keyResponse{ - Messages: queryResp.Messages, - Keys: queryResp.Keys, - NumNodes: queryResp.NumNodes, - NumResp: queryResp.NumResp, - NumErr: queryResp.NumErr, + if resp.Messages == nil { + resp.Messages = make(map[string]string) + } + if resp.Keys == nil { + resp.Keys = make(map[string]int) + } + + for _, kr := range queryResp.Responses { + for node, msg := range kr.Messages { + resp.Messages[node+"."+kr.Datacenter] = msg } + for key, qty := range kr.Keys { + if _, ok := resp.Keys[key]; ok { + resp.Keys[key] += qty + } else { + resp.Keys[key] = qty + } + } + resp.NumNodes += kr.NumNodes + resp.NumResp += kr.NumResp + resp.NumErr += kr.NumErr } return client.Send(&header, resp) diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 7cc6487478..e7dafb319f 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -1,10 +1,7 @@ package consul import ( - "fmt" - "github.com/hashicorp/consul/consul/structs" - "github.com/hashicorp/serf/serf" ) // Internal endpoint is used to query the miscellaneous info that @@ -66,49 +63,69 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, return m.srv.UserEvent(args.Name, args.Payload) } -// TODO(ryanuber): Clean up all of these methods -func (m *Internal) InstallKey(args *structs.KeyringRequest, - reply *structs.KeyringResponse) error { +func (m *Internal) ListKeys( + args *structs.KeyringRequest, + reply *structs.KeyringResponses) error { - var respLAN, respWAN *serf.KeyResponse - var err error - - if reply.Messages == nil { - reply.Messages = make(map[string]string) - } - if reply.Keys == nil { - reply.Keys = make(map[string]int) - } - - m.srv.setQueryMeta(&reply.QueryMeta) - - // Do a LAN key install. This will be invoked in each DC once the RPC call - // is forwarded below. - respLAN, err = m.srv.KeyManagerLAN().InstallKey(args.Key) - for node, msg := range respLAN.Messages { - reply.Messages["client."+node+"."+m.srv.config.Datacenter] = msg - } - reply.NumResp += respLAN.NumResp - reply.NumErr += respLAN.NumErr - reply.NumNodes += respLAN.NumNodes + respLAN, err := m.srv.KeyManagerLAN().ListKeys() if err != nil { - return fmt.Errorf("failed rotating LAN keyring in %s: %s", - m.srv.config.Datacenter, - err) + return err } + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + Datacenter: m.srv.config.Datacenter, + Messages: respLAN.Messages, + Keys: respLAN.Keys, + NumResp: respLAN.NumResp, + NumNodes: respLAN.NumNodes, + NumErr: respLAN.NumErr, + }) if !args.Forwarded { - // Only perform WAN key rotation once. - respWAN, err = m.srv.KeyManagerWAN().InstallKey(args.Key) + respWAN, err := m.srv.KeyManagerWAN().ListKeys() if err != nil { return err } - for node, msg := range respWAN.Messages { - reply.Messages["server."+node] = msg - } - reply.NumResp += respWAN.NumResp - reply.NumErr += respWAN.NumErr - reply.NumNodes += respWAN.NumNodes + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + Datacenter: m.srv.config.Datacenter, + Messages: respWAN.Messages, + Keys: respWAN.Keys, + NumResp: respWAN.NumResp, + NumNodes: respWAN.NumNodes, + NumErr: respWAN.NumErr, + }) + + // Mark key rotation as being already forwarded, then forward. + args.Forwarded = true + return m.srv.forwardAll("Internal.ListKeys", args, reply) + } + + return nil +} + +func (m *Internal) InstallKey( + args *structs.KeyringRequest, + reply *structs.KeyringResponses) error { + + respLAN, _ := m.srv.KeyManagerLAN().InstallKey(args.Key) + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + Datacenter: m.srv.config.Datacenter, + Messages: respLAN.Messages, + Keys: respLAN.Keys, + NumResp: respLAN.NumResp, + NumNodes: respLAN.NumNodes, + NumErr: respLAN.NumErr, + }) + + if !args.Forwarded { + respWAN, _ := m.srv.KeyManagerWAN().InstallKey(args.Key) + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + Datacenter: m.srv.config.Datacenter, + Messages: respWAN.Messages, + Keys: respWAN.Keys, + NumResp: respWAN.NumResp, + NumNodes: respWAN.NumNodes, + NumErr: respWAN.NumErr, + }) // Mark key rotation as being already forwarded, then forward. args.Forwarded = true @@ -118,47 +135,30 @@ func (m *Internal) InstallKey(args *structs.KeyringRequest, return nil } -func (m *Internal) UseKey(args *structs.KeyringRequest, - reply *structs.KeyringResponse) error { - var respLAN, respWAN *serf.KeyResponse - var err error +func (m *Internal) UseKey( + args *structs.KeyringRequest, + reply *structs.KeyringResponses) error { - if reply.Messages == nil { - reply.Messages = make(map[string]string) - } - if reply.Keys == nil { - reply.Keys = make(map[string]int) - } - - m.srv.setQueryMeta(&reply.QueryMeta) - - // Do a LAN key install. This will be invoked in each DC once the RPC call - // is forwarded below. - respLAN, err = m.srv.KeyManagerLAN().UseKey(args.Key) - for node, msg := range respLAN.Messages { - reply.Messages["client."+node+"."+m.srv.config.Datacenter] = msg - } - reply.NumResp += respLAN.NumResp - reply.NumErr += respLAN.NumErr - reply.NumNodes += respLAN.NumNodes - if err != nil { - return fmt.Errorf("failed rotating LAN keyring in %s: %s", - m.srv.config.Datacenter, - err) - } + respLAN, _ := m.srv.KeyManagerLAN().UseKey(args.Key) + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + Datacenter: m.srv.config.Datacenter, + Messages: respLAN.Messages, + Keys: respLAN.Keys, + NumResp: respLAN.NumResp, + NumNodes: respLAN.NumNodes, + NumErr: respLAN.NumErr, + }) if !args.Forwarded { - // Only perform WAN key rotation once. - respWAN, err = m.srv.KeyManagerWAN().UseKey(args.Key) - if err != nil { - return err - } - for node, msg := range respWAN.Messages { - reply.Messages["server."+node] = msg - } - reply.NumResp += respWAN.NumResp - reply.NumErr += respWAN.NumErr - reply.NumNodes += respWAN.NumNodes + respWAN, _ := m.srv.KeyManagerWAN().UseKey(args.Key) + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + Datacenter: m.srv.config.Datacenter, + Messages: respWAN.Messages, + Keys: respWAN.Keys, + NumResp: respWAN.NumResp, + NumNodes: respWAN.NumNodes, + NumErr: respWAN.NumErr, + }) // Mark key rotation as being already forwarded, then forward. args.Forwarded = true @@ -168,47 +168,30 @@ func (m *Internal) UseKey(args *structs.KeyringRequest, return nil } -func (m *Internal) RemoveKey(args *structs.KeyringRequest, - reply *structs.KeyringResponse) error { - var respLAN, respWAN *serf.KeyResponse - var err error +func (m *Internal) RemoveKey( + args *structs.KeyringRequest, + reply *structs.KeyringResponses) error { - if reply.Messages == nil { - reply.Messages = make(map[string]string) - } - if reply.Keys == nil { - reply.Keys = make(map[string]int) - } - - m.srv.setQueryMeta(&reply.QueryMeta) - - // Do a LAN key install. This will be invoked in each DC once the RPC call - // is forwarded below. - respLAN, err = m.srv.KeyManagerLAN().RemoveKey(args.Key) - for node, msg := range respLAN.Messages { - reply.Messages["client."+node+"."+m.srv.config.Datacenter] = msg - } - reply.NumResp += respLAN.NumResp - reply.NumErr += respLAN.NumErr - reply.NumNodes += respLAN.NumNodes - if err != nil { - return fmt.Errorf("failed rotating LAN keyring in %s: %s", - m.srv.config.Datacenter, - err) - } + respLAN, _ := m.srv.KeyManagerLAN().RemoveKey(args.Key) + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + Datacenter: m.srv.config.Datacenter, + Messages: respLAN.Messages, + Keys: respLAN.Keys, + NumResp: respLAN.NumResp, + NumNodes: respLAN.NumNodes, + NumErr: respLAN.NumErr, + }) if !args.Forwarded { - // Only perform WAN key rotation once. - respWAN, err = m.srv.KeyManagerWAN().RemoveKey(args.Key) - if err != nil { - return err - } - for node, msg := range respWAN.Messages { - reply.Messages["server."+node] = msg - } - reply.NumResp += respWAN.NumResp - reply.NumErr += respWAN.NumErr - reply.NumNodes += respWAN.NumNodes + respWAN, _ := m.srv.KeyManagerWAN().RemoveKey(args.Key) + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + Datacenter: m.srv.config.Datacenter, + Messages: respWAN.Messages, + Keys: respWAN.Keys, + NumResp: respWAN.NumResp, + NumNodes: respWAN.NumNodes, + NumErr: respWAN.NumErr, + }) // Mark key rotation as being already forwarded, then forward. args.Forwarded = true @@ -217,53 +200,3 @@ func (m *Internal) RemoveKey(args *structs.KeyringRequest, return nil } - -func (m *Internal) ListKeys(args *structs.KeyringRequest, - reply *structs.KeyringResponse) error { - var respLAN, respWAN *serf.KeyResponse - var err error - - if reply.Messages == nil { - reply.Messages = make(map[string]string) - } - if reply.Keys == nil { - reply.Keys = make(map[string]int) - } - - m.srv.setQueryMeta(&reply.QueryMeta) - - // Do a LAN key install. This will be invoked in each DC once the RPC call - // is forwarded below. - respLAN, err = m.srv.KeyManagerLAN().ListKeys() - for node, msg := range respLAN.Messages { - reply.Messages["client."+node+"."+m.srv.config.Datacenter] = msg - } - reply.NumResp += respLAN.NumResp - reply.NumErr += respLAN.NumErr - reply.NumNodes += respLAN.NumNodes - if err != nil { - return fmt.Errorf("failed rotating LAN keyring in %s: %s", - m.srv.config.Datacenter, - err) - } - - if !args.Forwarded { - // Only perform WAN key rotation once. - respWAN, err = m.srv.KeyManagerWAN().ListKeys() - if err != nil { - return err - } - for node, msg := range respWAN.Messages { - reply.Messages["server."+node] = msg - } - reply.NumResp += respWAN.NumResp - reply.NumErr += respWAN.NumErr - reply.NumNodes += respWAN.NumNodes - - // Mark key rotation as being already forwarded, then forward. - args.Forwarded = true - return m.srv.forwardAll("Internal.ListKeys", args, reply) - } - - return nil -} diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 31a6e319f3..3f029ee812 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -543,10 +543,14 @@ type KeyringRequest struct { // KeyringResponse is a unified key response and can be used for install, // remove, use, as well as listing key queries. type KeyringResponse struct { - Messages map[string]string - Keys map[string]int - NumNodes int - NumResp int - NumErr int - QueryMeta + Datacenter string + Messages map[string]string + Keys map[string]int + NumNodes int + NumResp int + NumErr int +} + +type KeyringResponses struct { + Responses []*KeyringResponse } From ef2aabc54492137494ad21a64474da360353b589 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 25 Sep 2014 00:22:06 -0700 Subject: [PATCH 42/80] consul: use a function for ingesting responses --- consul/internal_endpoint.go | 127 +++++++++++++++--------------------- consul/rpc.go | 7 +- 2 files changed, 58 insertions(+), 76 deletions(-) diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index e7dafb319f..6907076116 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -2,6 +2,7 @@ package consul import ( "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/serf/serf" ) // Internal endpoint is used to query the miscellaneous info that @@ -63,6 +64,22 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, return m.srv.UserEvent(args.Name, args.Payload) } +func (m *Internal) ingestKeyringResponse( + resp *serf.KeyResponse, + reply *structs.KeyringResponses) { + + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + Datacenter: m.srv.config.Datacenter, + Messages: resp.Messages, + Keys: resp.Keys, + NumResp: resp.NumResp, + NumNodes: resp.NumNodes, + NumErr: resp.NumErr, + }) +} + +// ListKeys will query the WAN and LAN gossip keyrings of all nodes, adding +// results into a collective response as we go. func (m *Internal) ListKeys( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { @@ -71,28 +88,14 @@ func (m *Internal) ListKeys( if err != nil { return err } - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - Datacenter: m.srv.config.Datacenter, - Messages: respLAN.Messages, - Keys: respLAN.Keys, - NumResp: respLAN.NumResp, - NumNodes: respLAN.NumNodes, - NumErr: respLAN.NumErr, - }) + m.ingestKeyringResponse(respLAN, reply) if !args.Forwarded { respWAN, err := m.srv.KeyManagerWAN().ListKeys() if err != nil { return err } - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - Datacenter: m.srv.config.Datacenter, - Messages: respWAN.Messages, - Keys: respWAN.Keys, - NumResp: respWAN.NumResp, - NumNodes: respWAN.NumNodes, - NumErr: respWAN.NumErr, - }) + m.ingestKeyringResponse(respWAN, reply) // Mark key rotation as being already forwarded, then forward. args.Forwarded = true @@ -102,32 +105,25 @@ func (m *Internal) ListKeys( return nil } +// InstallKey broadcasts a new encryption key to all nodes. This involves +// installing a new key on every node across all datacenters. func (m *Internal) InstallKey( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { - respLAN, _ := m.srv.KeyManagerLAN().InstallKey(args.Key) - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - Datacenter: m.srv.config.Datacenter, - Messages: respLAN.Messages, - Keys: respLAN.Keys, - NumResp: respLAN.NumResp, - NumNodes: respLAN.NumNodes, - NumErr: respLAN.NumErr, - }) + respLAN, err := m.srv.KeyManagerLAN().InstallKey(args.Key) + if err != nil { + return err + } + m.ingestKeyringResponse(respLAN, reply) if !args.Forwarded { - respWAN, _ := m.srv.KeyManagerWAN().InstallKey(args.Key) - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - Datacenter: m.srv.config.Datacenter, - Messages: respWAN.Messages, - Keys: respWAN.Keys, - NumResp: respWAN.NumResp, - NumNodes: respWAN.NumNodes, - NumErr: respWAN.NumErr, - }) + respWAN, err := m.srv.KeyManagerWAN().InstallKey(args.Key) + if err != nil { + return err + } + m.ingestKeyringResponse(respWAN, reply) - // Mark key rotation as being already forwarded, then forward. args.Forwarded = true return m.srv.forwardAll("Internal.InstallKey", args, reply) } @@ -135,32 +131,25 @@ func (m *Internal) InstallKey( return nil } +// UseKey instructs all nodes to change the key they are using to +// encrypt gossip messages. func (m *Internal) UseKey( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { - respLAN, _ := m.srv.KeyManagerLAN().UseKey(args.Key) - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - Datacenter: m.srv.config.Datacenter, - Messages: respLAN.Messages, - Keys: respLAN.Keys, - NumResp: respLAN.NumResp, - NumNodes: respLAN.NumNodes, - NumErr: respLAN.NumErr, - }) + respLAN, err := m.srv.KeyManagerLAN().UseKey(args.Key) + if err != nil { + return err + } + m.ingestKeyringResponse(respLAN, reply) if !args.Forwarded { - respWAN, _ := m.srv.KeyManagerWAN().UseKey(args.Key) - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - Datacenter: m.srv.config.Datacenter, - Messages: respWAN.Messages, - Keys: respWAN.Keys, - NumResp: respWAN.NumResp, - NumNodes: respWAN.NumNodes, - NumErr: respWAN.NumErr, - }) + respWAN, err := m.srv.KeyManagerWAN().UseKey(args.Key) + if err != nil { + return err + } + m.ingestKeyringResponse(respWAN, reply) - // Mark key rotation as being already forwarded, then forward. args.Forwarded = true return m.srv.forwardAll("Internal.UseKey", args, reply) } @@ -168,32 +157,24 @@ func (m *Internal) UseKey( return nil } +// RemoveKey instructs all nodes to drop the specified key from the keyring. func (m *Internal) RemoveKey( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { - respLAN, _ := m.srv.KeyManagerLAN().RemoveKey(args.Key) - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - Datacenter: m.srv.config.Datacenter, - Messages: respLAN.Messages, - Keys: respLAN.Keys, - NumResp: respLAN.NumResp, - NumNodes: respLAN.NumNodes, - NumErr: respLAN.NumErr, - }) + respLAN, err := m.srv.KeyManagerLAN().RemoveKey(args.Key) + if err != nil { + return err + } + m.ingestKeyringResponse(respLAN, reply) if !args.Forwarded { - respWAN, _ := m.srv.KeyManagerWAN().RemoveKey(args.Key) - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - Datacenter: m.srv.config.Datacenter, - Messages: respWAN.Messages, - Keys: respWAN.Keys, - NumResp: respWAN.NumResp, - NumNodes: respWAN.NumNodes, - NumErr: respWAN.NumErr, - }) + respWAN, err := m.srv.KeyManagerWAN().RemoveKey(args.Key) + if err != nil { + return err + } + m.ingestKeyringResponse(respWAN, reply) - // Mark key rotation as being already forwarded, then forward. args.Forwarded = true return m.srv.forwardAll("Internal.RemoveKey", args, reply) } diff --git a/consul/rpc.go b/consul/rpc.go index 4526ca75b9..e5b5be6c5e 100644 --- a/consul/rpc.go +++ b/consul/rpc.go @@ -227,9 +227,10 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{ func (s *Server) forwardAll(method string, args, reply interface{}) error { for dc, _ := range s.remoteConsuls { if dc != s.config.Datacenter { - if err := s.forwardDC(method, dc, args, reply); err != nil { - return err - } + // Forward the RPC call. Even if an error is returned here, we still + // want to continue broadcasting to the remaining DC's to avoid + // network partitions completely killing us. + go s.forwardDC(method, dc, args, reply) } } return nil From 9056e617cbc6a14c9b4491a324792e1c03648a29 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 28 Sep 2014 12:35:51 -0700 Subject: [PATCH 43/80] consul: cross-dc key rotation works --- command/agent/rpc.go | 80 +++++++++++++++------- command/agent/rpc_client.go | 32 ++++----- command/keyring.go | 131 +++++++++++++++++++++++++----------- consul/internal_endpoint.go | 98 +++++++++++++++------------ consul/rpc.go | 13 ---- consul/structs/structs.go | 3 + 6 files changed, 222 insertions(+), 135 deletions(-) diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 5d6c5c8a7c..288a97cf33 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -113,12 +113,33 @@ type keyRequest struct { Key string } +type KeyringEntry struct { + Datacenter string + Pool string + Key string + Count int +} + +type KeyringMessage struct { + Datacenter string + Pool string + Node string + Message string +} + +type KeyringInfo struct { + Datacenter string + Pool string + NumNodes int + NumResp int + NumErr int + Error string +} + type keyResponse struct { - Messages map[string]string - Keys map[string]int - NumNodes int - NumResp int - NumErr int + Keys []KeyringEntry + Messages []KeyringMessage + Info []KeyringInfo } type membersResponse struct { @@ -607,7 +628,7 @@ func (i *AgentRPC) handleReload(client *rpcClient, seq uint64) error { func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) error { var req keyRequest var queryResp *structs.KeyringResponses - var resp keyResponse + var r keyResponse var err error if cmd != listKeysCommand { @@ -636,30 +657,43 @@ func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) erro Error: errToString(err), } - if resp.Messages == nil { - resp.Messages = make(map[string]string) - } - if resp.Keys == nil { - resp.Keys = make(map[string]int) - } - for _, kr := range queryResp.Responses { - for node, msg := range kr.Messages { - resp.Messages[node+"."+kr.Datacenter] = msg + var pool string + if kr.WAN { + pool = "WAN" + } else { + pool = "LAN" + } + for node, message := range kr.Messages { + msg := KeyringMessage{ + Datacenter: kr.Datacenter, + Pool: pool, + Node: node, + Message: message, + } + r.Messages = append(r.Messages, msg) } for key, qty := range kr.Keys { - if _, ok := resp.Keys[key]; ok { - resp.Keys[key] += qty - } else { - resp.Keys[key] = qty + k := KeyringEntry{ + Datacenter: kr.Datacenter, + Pool: pool, + Key: key, + Count: qty, } + r.Keys = append(r.Keys, k) } - resp.NumNodes += kr.NumNodes - resp.NumResp += kr.NumResp - resp.NumErr += kr.NumErr + info := KeyringInfo{ + Datacenter: kr.Datacenter, + Pool: pool, + NumNodes: kr.NumNodes, + NumResp: kr.NumResp, + NumErr: kr.NumErr, + Error: kr.Error, + } + r.Info = append(r.Info, info) } - return client.Send(&header, resp) + return client.Send(&header, r) } // Used to convert an error to a string representation diff --git a/command/agent/rpc_client.go b/command/agent/rpc_client.go index 36a54057e0..454f427a80 100644 --- a/command/agent/rpc_client.go +++ b/command/agent/rpc_client.go @@ -176,47 +176,47 @@ func (c *RPCClient) WANMembers() ([]Member, error) { return resp.Members, err } -func (c *RPCClient) ListKeys() (map[string]int, int, map[string]string, error) { +func (c *RPCClient) ListKeys() (keyResponse, error) { header := requestHeader{ Command: listKeysCommand, Seq: c.getSeq(), } - resp := new(keyResponse) - err := c.genericRPC(&header, nil, resp) - return resp.Keys, resp.NumNodes, resp.Messages, err + var resp keyResponse + err := c.genericRPC(&header, nil, &resp) + return resp, err } -func (c *RPCClient) InstallKey(key string) (map[string]string, error) { +func (c *RPCClient) InstallKey(key string) (keyResponse, error) { header := requestHeader{ Command: installKeyCommand, Seq: c.getSeq(), } req := keyRequest{key} - resp := new(keyResponse) - err := c.genericRPC(&header, &req, resp) - return resp.Messages, err + var resp keyResponse + err := c.genericRPC(&header, &req, &resp) + return resp, err } -func (c *RPCClient) UseKey(key string) (map[string]string, error) { +func (c *RPCClient) UseKey(key string) (keyResponse, error) { header := requestHeader{ Command: useKeyCommand, Seq: c.getSeq(), } req := keyRequest{key} - resp := new(keyResponse) - err := c.genericRPC(&header, &req, resp) - return resp.Messages, err + var resp keyResponse + err := c.genericRPC(&header, &req, &resp) + return resp, err } -func (c *RPCClient) RemoveKey(key string) (map[string]string, error) { +func (c *RPCClient) RemoveKey(key string) (keyResponse, error) { header := requestHeader{ Command: removeKeyCommand, Seq: c.getSeq(), } req := keyRequest{key} - resp := new(keyResponse) - err := c.genericRPC(&header, &req, resp) - return resp.Messages, err + var resp keyResponse + err := c.genericRPC(&header, &req, &resp) + return resp, err } // Leave is used to trigger a graceful leave and shutdown diff --git a/command/keyring.go b/command/keyring.go index eaa298dcda..8d0dc95451 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -14,6 +14,12 @@ import ( "github.com/ryanuber/columnize" ) +const ( + installKeyCommand = "install" + useKeyCommand = "use" + removeKeyCommand = "remove" +) + // KeyringCommand is a Command implementation that handles querying, installing, // and removing gossip encryption keys from a keyring. type KeyringCommand struct { @@ -107,79 +113,106 @@ func (c *KeyringCommand) Run(args []string) int { if listKeys { c.Ui.Info("Asking all members for installed keys...") - return c.listKeysOperation(client.ListKeys) + return c.listKeysOperation(client) } if installKey != "" { c.Ui.Info("Installing new gossip encryption key...") - if rval := c.keyOperation(installKey, client.InstallKey); rval != 0 { - return rval + r, err := client.InstallKey(installKey) + if err != nil { + c.Ui.Error(fmt.Sprintf("error: %s", err)) + return 1 } - c.Ui.Info("Successfully installed key!") - return 0 + rval := c.handleResponse(r.Info, r.Messages, r.Keys) + if rval == 0 { + c.Ui.Info("Successfully installed new key!") + } + return rval } if useKey != "" { c.Ui.Info("Changing primary gossip encryption key...") - if rval := c.keyOperation(useKey, client.UseKey); rval != 0 { - return rval + r, err := client.UseKey(useKey) + if err != nil { + c.Ui.Error(fmt.Sprintf("error: %s", err)) + return 1 } - c.Ui.Info("Successfully changed primary key!") - return 0 + rval := c.handleResponse(r.Info, r.Messages, r.Keys) + if rval == 0 { + c.Ui.Info("Successfully changed primary encryption key!") + } + return rval } if removeKey != "" { c.Ui.Info("Removing gossip encryption key...") - if rval := c.keyOperation(removeKey, client.RemoveKey); rval != 0 { - return rval + r, err := client.RemoveKey(removeKey) + if err != nil { + c.Ui.Error(fmt.Sprintf("error: %s", err)) + return 1 } - c.Ui.Info("Successfully removed gossip encryption key!") - return 0 + rval := c.handleResponse(r.Info, r.Messages, r.Keys) + if rval == 0 { + c.Ui.Info("Successfully removed encryption key!") + } + return rval } // Should never make it here return 0 } -// keyFunc is a function which manipulates gossip encryption keyrings. This is -// used for key installation, removal, and primary key changes. -type keyFunc func(string) (map[string]string, error) +func (c *KeyringCommand) handleResponse( + info []agent.KeyringInfo, + messages []agent.KeyringMessage, + entries []agent.KeyringEntry) int { -// keyOperation is a unified process for manipulating the gossip keyrings. -func (c *KeyringCommand) keyOperation(key string, fn keyFunc) int { - var out []string + var rval int - failures, err := fn(key) - - if err != nil { - if len(failures) > 0 { - for node, msg := range failures { - out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + for _, i := range info { + if i.Error != "" { + pool := i.Pool + if pool != "WAN" { + pool = i.Datacenter + " (LAN)" } - c.Ui.Error(columnize.SimpleFormat(out)) + + c.Ui.Error("") + c.Ui.Error(fmt.Sprintf("%s error: %s", pool, i.Error)) + + var errors []string + for _, msg := range messages { + if msg.Datacenter != i.Datacenter || msg.Pool != i.Pool { + continue + } + errors = append(errors, fmt.Sprintf( + "failed: %s | %s", + msg.Node, + msg.Message)) + } + c.Ui.Error(columnize.SimpleFormat(errors)) + rval = 1 } - c.Ui.Error("") - c.Ui.Error(fmt.Sprintf("Error: %s", err)) - return 1 } - return 0 + return rval } -// listKeysFunc is a function which handles querying lists of gossip keys -type listKeysFunc func() (map[string]int, int, map[string]string, error) - // listKeysOperation is a unified process for querying and // displaying gossip keys. -func (c *KeyringCommand) listKeysOperation(fn listKeysFunc) int { +func (c *KeyringCommand) listKeysOperation(client *agent.RPCClient) int { var out []string - keys, numNodes, failures, err := fn() + resp, err := client.ListKeys() if err != nil { - if len(failures) > 0 { - for node, msg := range failures { - out = append(out, fmt.Sprintf("failed: %s | %s", node, msg)) + if len(resp.Messages) > 0 { + for _, msg := range resp.Messages { + out = append(out, fmt.Sprintf( + "failed: %s | %s | %s | %s", + msg.Datacenter, + msg.Pool, + msg.Node, + msg.Message)) } c.Ui.Error(columnize.SimpleFormat(out)) } @@ -187,8 +220,26 @@ func (c *KeyringCommand) listKeysOperation(fn listKeysFunc) int { c.Ui.Error(fmt.Sprintf("Failed gathering member keys: %s", err)) return 1 } - for key, num := range keys { - out = append(out, fmt.Sprintf("%s | [%d/%d]", key, num, numNodes)) + + entries := make(map[string]map[string]int) + for _, key := range resp.Keys { + var dc string + if key.Pool == "WAN" { + dc = key.Pool + } else { + dc = key.Datacenter + } + if _, ok := entries[dc]; !ok { + entries[dc] = make(map[string]int) + } + entries[dc][key.Key] = key.Count + } + for dc, keys := range entries { + out = append(out, "") + out = append(out, dc) + for key, count := range keys { + out = append(out, fmt.Sprintf("%s|[%d/%d]", key, count, count)) + } } c.Ui.Output(columnize.SimpleFormat(out)) diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 6907076116..2cd08a2c65 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -64,42 +64,69 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, return m.srv.UserEvent(args.Name, args.Payload) } +// ingestKeyringResponse is a helper method to pick the relative information +// from a Serf message and stuff it into a KeyringResponse. func (m *Internal) ingestKeyringResponse( - resp *serf.KeyResponse, - reply *structs.KeyringResponses) { + serfResp *serf.KeyResponse, + reply *structs.KeyringResponses, + err error, wan bool) { + + errStr := "" + if err != nil { + errStr = err.Error() + } reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + WAN: wan, Datacenter: m.srv.config.Datacenter, - Messages: resp.Messages, - Keys: resp.Keys, - NumResp: resp.NumResp, - NumNodes: resp.NumNodes, - NumErr: resp.NumErr, + Messages: serfResp.Messages, + Keys: serfResp.Keys, + NumResp: serfResp.NumResp, + NumNodes: serfResp.NumNodes, + NumErr: serfResp.NumErr, + Error: errStr, }) } +func (m *Internal) forwardKeyring( + method string, + args *structs.KeyringRequest, + replies *structs.KeyringResponses) error { + + for dc, _ := range m.srv.remoteConsuls { + if dc == m.srv.config.Datacenter { + continue + } + rr := structs.KeyringResponses{} + if err := m.srv.forwardDC(method, dc, args, &rr); err != nil { + return err + } + for _, r := range rr.Responses { + replies.Responses = append(replies.Responses, r) + } + } + + return nil +} + // ListKeys will query the WAN and LAN gossip keyrings of all nodes, adding // results into a collective response as we go. func (m *Internal) ListKeys( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { + m.srv.setQueryMeta(&reply.QueryMeta) + respLAN, err := m.srv.KeyManagerLAN().ListKeys() - if err != nil { - return err - } - m.ingestKeyringResponse(respLAN, reply) + m.ingestKeyringResponse(respLAN, reply, err, false) if !args.Forwarded { respWAN, err := m.srv.KeyManagerWAN().ListKeys() - if err != nil { - return err - } - m.ingestKeyringResponse(respWAN, reply) + m.ingestKeyringResponse(respWAN, reply, err, true) // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - return m.srv.forwardAll("Internal.ListKeys", args, reply) + m.forwardKeyring("Internal.ListKeys", args, reply) } return nil @@ -112,20 +139,15 @@ func (m *Internal) InstallKey( reply *structs.KeyringResponses) error { respLAN, err := m.srv.KeyManagerLAN().InstallKey(args.Key) - if err != nil { - return err - } - m.ingestKeyringResponse(respLAN, reply) + m.ingestKeyringResponse(respLAN, reply, err, false) if !args.Forwarded { respWAN, err := m.srv.KeyManagerWAN().InstallKey(args.Key) - if err != nil { - return err - } - m.ingestKeyringResponse(respWAN, reply) + m.ingestKeyringResponse(respWAN, reply, err, true) + // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - return m.srv.forwardAll("Internal.InstallKey", args, reply) + m.forwardKeyring("Internal.InstallKey", args, reply) } return nil @@ -138,20 +160,15 @@ func (m *Internal) UseKey( reply *structs.KeyringResponses) error { respLAN, err := m.srv.KeyManagerLAN().UseKey(args.Key) - if err != nil { - return err - } - m.ingestKeyringResponse(respLAN, reply) + m.ingestKeyringResponse(respLAN, reply, err, false) if !args.Forwarded { respWAN, err := m.srv.KeyManagerWAN().UseKey(args.Key) - if err != nil { - return err - } - m.ingestKeyringResponse(respWAN, reply) + m.ingestKeyringResponse(respWAN, reply, err, true) + // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - return m.srv.forwardAll("Internal.UseKey", args, reply) + m.forwardKeyring("Internal.UseKey", args, reply) } return nil @@ -163,20 +180,15 @@ func (m *Internal) RemoveKey( reply *structs.KeyringResponses) error { respLAN, err := m.srv.KeyManagerLAN().RemoveKey(args.Key) - if err != nil { - return err - } - m.ingestKeyringResponse(respLAN, reply) + m.ingestKeyringResponse(respLAN, reply, err, false) if !args.Forwarded { respWAN, err := m.srv.KeyManagerWAN().RemoveKey(args.Key) - if err != nil { - return err - } - m.ingestKeyringResponse(respWAN, reply) + m.ingestKeyringResponse(respWAN, reply, err, true) + // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - return m.srv.forwardAll("Internal.RemoveKey", args, reply) + m.forwardKeyring("Internal.RemoveKey", args, reply) } return nil diff --git a/consul/rpc.go b/consul/rpc.go index e5b5be6c5e..cd5c36ebd3 100644 --- a/consul/rpc.go +++ b/consul/rpc.go @@ -223,19 +223,6 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{ return s.connPool.RPC(server.Addr, server.Version, method, args, reply) } -// forwardAll forwards a single RPC call to every known datacenter. -func (s *Server) forwardAll(method string, args, reply interface{}) error { - for dc, _ := range s.remoteConsuls { - if dc != s.config.Datacenter { - // Forward the RPC call. Even if an error is returned here, we still - // want to continue broadcasting to the remaining DC's to avoid - // network partitions completely killing us. - go s.forwardDC(method, dc, args, reply) - } - } - return nil -} - // raftApply is used to encode a message, run it through raft, and return // the FSM response along with any errors func (s *Server) raftApply(t structs.MessageType, msg interface{}) (interface{}, error) { diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 3f029ee812..6e752ea4f3 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -543,14 +543,17 @@ type KeyringRequest struct { // KeyringResponse is a unified key response and can be used for install, // remove, use, as well as listing key queries. type KeyringResponse struct { + WAN bool Datacenter string Messages map[string]string Keys map[string]int NumNodes int NumResp int NumErr int + Error string } type KeyringResponses struct { Responses []*KeyringResponse + QueryMeta } From e9f8f7f2d7bade29f089a40a3a5a0868936f49f1 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 28 Sep 2014 13:33:37 -0700 Subject: [PATCH 44/80] command/keyring: clean up output --- command/keyring.go | 105 ++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 58 deletions(-) diff --git a/command/keyring.go b/command/keyring.go index 8d0dc95451..0e0bbc19bc 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -113,7 +113,16 @@ func (c *KeyringCommand) Run(args []string) int { if listKeys { c.Ui.Info("Asking all members for installed keys...") - return c.listKeysOperation(client) + r, err := client.ListKeys() + if err != nil { + c.Ui.Error(fmt.Sprintf("error: %s", err)) + return 1 + } + if rval := c.handleResponse(r.Info, r.Messages, r.Keys); rval != 0 { + return rval + } + c.handleList(r.Info, r.Messages, r.Keys) + return 0 } if installKey != "" { @@ -123,11 +132,7 @@ func (c *KeyringCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - rval := c.handleResponse(r.Info, r.Messages, r.Keys) - if rval == 0 { - c.Ui.Info("Successfully installed new key!") - } - return rval + return c.handleResponse(r.Info, r.Messages, r.Keys) } if useKey != "" { @@ -137,11 +142,7 @@ func (c *KeyringCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - rval := c.handleResponse(r.Info, r.Messages, r.Keys) - if rval == 0 { - c.Ui.Info("Successfully changed primary encryption key!") - } - return rval + return c.handleResponse(r.Info, r.Messages, r.Keys) } if removeKey != "" { @@ -151,11 +152,7 @@ func (c *KeyringCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - rval := c.handleResponse(r.Info, r.Messages, r.Keys) - if rval == 0 { - c.Ui.Info("Successfully removed encryption key!") - } - return rval + return c.handleResponse(r.Info, r.Messages, r.Keys) } // Should never make it here @@ -165,7 +162,7 @@ func (c *KeyringCommand) Run(args []string) int { func (c *KeyringCommand) handleResponse( info []agent.KeyringInfo, messages []agent.KeyringMessage, - entries []agent.KeyringEntry) int { + keys []agent.KeyringEntry) int { var rval int @@ -194,57 +191,49 @@ func (c *KeyringCommand) handleResponse( } } + if rval == 0 { + c.Ui.Info("Done!") + } + return rval } -// listKeysOperation is a unified process for querying and -// displaying gossip keys. -func (c *KeyringCommand) listKeysOperation(client *agent.RPCClient) int { - var out []string +func (c *KeyringCommand) handleList( + info []agent.KeyringInfo, + messages []agent.KeyringMessage, + keys []agent.KeyringEntry) { - resp, err := client.ListKeys() - - if err != nil { - if len(resp.Messages) > 0 { - for _, msg := range resp.Messages { - out = append(out, fmt.Sprintf( - "failed: %s | %s | %s | %s", - msg.Datacenter, - msg.Pool, - msg.Node, - msg.Message)) + installed := make(map[string]map[string][]int) + for _, key := range keys { + var nodes int + for _, i := range info { + if i.Datacenter == key.Datacenter && i.Pool == key.Pool { + nodes = i.NumNodes } - c.Ui.Error(columnize.SimpleFormat(out)) } - c.Ui.Error("") - c.Ui.Error(fmt.Sprintf("Failed gathering member keys: %s", err)) - return 1 - } - entries := make(map[string]map[string]int) - for _, key := range resp.Keys { - var dc string - if key.Pool == "WAN" { - dc = key.Pool + pool := key.Pool + if pool != "WAN" { + pool = key.Datacenter + " (LAN)" + } + + if _, ok := installed[pool]; !ok { + installed[pool] = map[string][]int{key.Key: []int{key.Count, nodes}} } else { - dc = key.Datacenter - } - if _, ok := entries[dc]; !ok { - entries[dc] = make(map[string]int) - } - entries[dc][key.Key] = key.Count - } - for dc, keys := range entries { - out = append(out, "") - out = append(out, dc) - for key, count := range keys { - out = append(out, fmt.Sprintf("%s|[%d/%d]", key, count, count)) + installed[pool][key.Key] = []int{key.Count, nodes} } } - c.Ui.Output(columnize.SimpleFormat(out)) - - c.Ui.Output("") - return 0 + for pool, keys := range installed { + c.Ui.Output("") + c.Ui.Output(pool + ":") + var out []string + for key, num := range keys { + out = append(out, fmt.Sprintf( + "%s | [%d/%d]", + key, num[0], num[1])) + } + c.Ui.Output(columnize.SimpleFormat(out)) + } } // initKeyring will create a keyring file at a given path. From fcba072246e42e2633d47cd4074536c6baddde42 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 29 Sep 2014 09:31:55 -0700 Subject: [PATCH 45/80] command: fixing test cases for keyring --- command/agent/rpc_client_test.go | 157 +++++++++++-------------------- 1 file changed, 53 insertions(+), 104 deletions(-) diff --git a/command/agent/rpc_client_test.go b/command/agent/rpc_client_test.go index a042d85e85..94a9f07d31 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -284,19 +284,16 @@ OUTER2: func TestRPCClientListKeys(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" - conf := Config{EncryptKey: key1} + conf := Config{EncryptKey: key1, Datacenter: "dc1"} p1 := testRPCClientWithConfig(t, &conf) defer p1.Close() - // Check WAN keys - keys := listKeys(t, p1.client, false) - if _, ok := keys[key1]; !ok { + // Key is initially installed to both wan/lan + keys := listKeys(t, p1.client) + if _, ok := keys["dc1"][key1]; !ok { t.Fatalf("bad: %#v", keys) } - - // Check LAN keys - keys = listKeys(t, p1.client, true) - if _, ok := keys[key1]; !ok { + if _, ok := keys["wan"][key1]; !ok { t.Fatalf("bad: %#v", keys) } } @@ -308,74 +305,64 @@ func TestRPCClientInstallKey(t *testing.T) { p1 := testRPCClientWithConfig(t, &conf) defer p1.Close() - // Test WAN keys - keys := listKeys(t, p1.client, true) - if _, ok := keys[key2]; ok { + // key2 is not installed yet + keys := listKeys(t, p1.client) + if num, ok := keys["dc1"][key2]; ok || num != 0 { + t.Fatalf("bad: %#v", keys) + } + if num, ok := keys["wan"][key2]; ok || num != 0 { t.Fatalf("bad: %#v", keys) } - installKey(t, p1.client, key2, true) - - keys = listKeys(t, p1.client, true) - if _, ok := keys[key2]; !ok { - t.Fatalf("bad: %#v", keys) + // install key2 + if _, err := p1.client.InstallKey(key2); err != nil { + t.Fatalf("err: %s", err) } - // Test LAN keys - keys = listKeys(t, p1.client, false) - if _, ok := keys[key2]; ok { + // key2 should now be installed + keys = listKeys(t, p1.client) + if num, ok := keys["dc1"][key2]; !ok || num != 1 { t.Fatalf("bad: %#v", keys) } - - installKey(t, p1.client, key2, false) - - keys = listKeys(t, p1.client, false) - if _, ok := keys[key2]; !ok { + if num, ok := keys["wan"][key2]; !ok || num != 1 { t.Fatalf("bad: %#v", keys) } } -func TestRPCClientRotateKey(t *testing.T) { +func TestRPCClientUseKey(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" key2 := "xAEZ3uVHRMZD9GcYMZaRQw==" conf := Config{EncryptKey: key1} p1 := testRPCClientWithConfig(t, &conf) defer p1.Close() - // Test WAN keys - keys := listKeys(t, p1.client, true) - if _, ok := keys[key2]; ok { + // add a second key to the ring + if _, err := p1.client.InstallKey(key2); err != nil { + t.Fatalf("err: %s", err) + } + + // key2 is installed + keys := listKeys(t, p1.client) + if num, ok := keys["dc1"][key2]; !ok || num != 1 { + t.Fatalf("bad: %#v", keys) + } + if num, ok := keys["wan"][key2]; !ok || num != 1 { t.Fatalf("bad: %#v", keys) } - installKey(t, p1.client, key2, true) - useKey(t, p1.client, key2, true) - removeKey(t, p1.client, key1, true) - - keys = listKeys(t, p1.client, true) - if _, ok := keys[key1]; ok { - t.Fatalf("bad: %#v", keys) - } - if _, ok := keys[key2]; !ok { - t.Fatalf("bad: %#v", keys) + // can't remove key1 yet + if _, err := p1.client.RemoveKey(key1); err == nil { + t.Fatalf("expected error removing primary key") } - // Test LAN keys - keys = listKeys(t, p1.client, false) - if _, ok := keys[key2]; ok { - t.Fatalf("bad: %#v", keys) + // change primary key + if _, err := p1.client.UseKey(key2); err != nil { + t.Fatalf("err: %s", err) } - installKey(t, p1.client, key2, false) - useKey(t, p1.client, key2, false) - removeKey(t, p1.client, key1, false) - - keys = listKeys(t, p1.client, false) - if _, ok := keys[key1]; ok { - t.Fatalf("bad: %#v", keys) - } - if _, ok := keys[key2]; !ok { - t.Fatalf("bad: %#v", keys) + // can remove key1 now + if _, err := p1.client.RemoveKey(key1); err != nil { + t.Fatalf("err: %s", err) } } @@ -383,66 +370,28 @@ func TestRPCClientKeyOperation_encryptionDisabled(t *testing.T) { p1 := testRPCClient(t) defer p1.Close() - _, _, failures, err := p1.client.ListKeysLAN() + r, err := p1.client.ListKeys() if err == nil { t.Fatalf("no error listing keys with encryption disabled") } - if len(failures) != 1 { - t.Fatalf("bad: %#v", failures) + if len(r.Messages) != 1 { + t.Fatalf("bad: %#v", r) } } -func listKeys(t *testing.T, c *RPCClient, wan bool) (keys map[string]int) { - var err error - - if wan { - keys, _, _, err = c.ListKeysWAN() - } else { - keys, _, _, err = c.ListKeysLAN() - } - if err != nil { - t.Fatalf("err: %s", err) - } - - return -} - -func installKey(t *testing.T, c *RPCClient, key string, wan bool) { - var err error - - if wan { - _, err = c.InstallKeyWAN(key) - } else { - _, err = c.InstallKeyLAN(key) - } - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func useKey(t *testing.T, c *RPCClient, key string, wan bool) { - var err error - - if wan { - _, err = c.UseKeyWAN(key) - } else { - _, err = c.UseKeyLAN(key) - } - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func removeKey(t *testing.T, c *RPCClient, key string, wan bool) { - var err error - - if wan { - _, err = c.RemoveKeyWAN(key) - } else { - _, err = c.RemoveKeyLAN(key) - } +func listKeys(t *testing.T, c *RPCClient) map[string]map[string]int { + resp, err := c.ListKeys() if err != nil { t.Fatalf("err: %s", err) } + out := make(map[string]map[string]int) + for _, k := range resp.Keys { + respID := k.Datacenter + if k.Pool == "WAN" { + respID = "wan" + } + out[respID] = map[string]int{k.Key: k.Count} + } + return out } From f86904ee596e33a757c233d28d6dbe8d509b8780 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 29 Sep 2014 09:54:46 -0700 Subject: [PATCH 46/80] agent: adjust rpc client tests for keyring --- command/agent/rpc_client_test.go | 47 ++++++++++++++++++++++++-------- command/util_test.go | 2 +- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/command/agent/rpc_client_test.go b/command/agent/rpc_client_test.go index 94a9f07d31..3b08aa733a 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -315,9 +315,11 @@ func TestRPCClientInstallKey(t *testing.T) { } // install key2 - if _, err := p1.client.InstallKey(key2); err != nil { + r, err := p1.client.InstallKey(key2) + if err != nil { t.Fatalf("err: %s", err) } + keyringSuccess(t, r) // key2 should now be installed keys = listKeys(t, p1.client) @@ -337,9 +339,11 @@ func TestRPCClientUseKey(t *testing.T) { defer p1.Close() // add a second key to the ring - if _, err := p1.client.InstallKey(key2); err != nil { + r, err := p1.client.InstallKey(key2) + if err != nil { t.Fatalf("err: %s", err) } + keyringSuccess(t, r) // key2 is installed keys := listKeys(t, p1.client) @@ -351,19 +355,25 @@ func TestRPCClientUseKey(t *testing.T) { } // can't remove key1 yet - if _, err := p1.client.RemoveKey(key1); err == nil { - t.Fatalf("expected error removing primary key") + r, err = p1.client.RemoveKey(key1) + if err != nil { + t.Fatalf("err: %s", err) } + keyringError(t, r) // change primary key - if _, err := p1.client.UseKey(key2); err != nil { + r, err = p1.client.UseKey(key2) + if err != nil { t.Fatalf("err: %s", err) } + keyringSuccess(t, r) // can remove key1 now - if _, err := p1.client.RemoveKey(key1); err != nil { + r, err = p1.client.RemoveKey(key1) + if err != nil { t.Fatalf("err: %s", err) } + keyringSuccess(t, r) } func TestRPCClientKeyOperation_encryptionDisabled(t *testing.T) { @@ -371,13 +381,10 @@ func TestRPCClientKeyOperation_encryptionDisabled(t *testing.T) { defer p1.Close() r, err := p1.client.ListKeys() - if err == nil { - t.Fatalf("no error listing keys with encryption disabled") - } - - if len(r.Messages) != 1 { - t.Fatalf("bad: %#v", r) + if err != nil { + t.Fatalf("err: %s", err) } + keyringError(t, r) } func listKeys(t *testing.T, c *RPCClient) map[string]map[string]int { @@ -395,3 +402,19 @@ func listKeys(t *testing.T, c *RPCClient) map[string]map[string]int { } return out } + +func keyringError(t *testing.T, r keyResponse) { + for _, i := range r.Info { + if i.Error == "" { + t.Fatalf("no error reported from %s (%s)", i.Datacenter, i.Pool) + } + } +} + +func keyringSuccess(t *testing.T, r keyResponse) { + for _, i := range r.Info { + if i.Error != "" { + t.Fatalf("error from %s (%s): %s", i.Datacenter, i.Pool, i.Error) + } + } +} diff --git a/command/util_test.go b/command/util_test.go index 388c1e62b5..586489233e 100644 --- a/command/util_test.go +++ b/command/util_test.go @@ -53,7 +53,7 @@ func testAgentWithConfig(c *agent.Config, t *testing.T) *agentWrapper { conf := nextConfig() if c != nil { - conf = agent.MergeConfig(conf, c) + conf = agent.MergeConfig(c, conf) } dir, err := ioutil.TempDir("", "agent") From a163db2269544b5929838d98b99cbf9cb8e00652 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 29 Sep 2014 12:35:15 -0700 Subject: [PATCH 47/80] command/keyring: remove unneeded -wan arg, fix tests --- command/agent/rpc.go | 8 ++-- command/agent/rpc_client.go | 22 ++++----- command/agent/rpc_client_test.go | 4 +- command/keyring.go | 9 ++-- command/keyring_test.go | 78 ++++++++------------------------ 5 files changed, 40 insertions(+), 81 deletions(-) diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 288a97cf33..81beff6620 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -109,7 +109,7 @@ type joinResponse struct { Num int32 } -type keyRequest struct { +type keyringRequest struct { Key string } @@ -136,7 +136,7 @@ type KeyringInfo struct { Error string } -type keyResponse struct { +type keyringResponse struct { Keys []KeyringEntry Messages []KeyringMessage Info []KeyringInfo @@ -626,9 +626,9 @@ func (i *AgentRPC) handleReload(client *rpcClient, seq uint64) error { } func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) error { - var req keyRequest + var req keyringRequest var queryResp *structs.KeyringResponses - var r keyResponse + var r keyringResponse var err error if cmd != listKeysCommand { diff --git a/command/agent/rpc_client.go b/command/agent/rpc_client.go index 454f427a80..7ba1907b24 100644 --- a/command/agent/rpc_client.go +++ b/command/agent/rpc_client.go @@ -176,45 +176,45 @@ func (c *RPCClient) WANMembers() ([]Member, error) { return resp.Members, err } -func (c *RPCClient) ListKeys() (keyResponse, error) { +func (c *RPCClient) ListKeys() (keyringResponse, error) { header := requestHeader{ Command: listKeysCommand, Seq: c.getSeq(), } - var resp keyResponse + var resp keyringResponse err := c.genericRPC(&header, nil, &resp) return resp, err } -func (c *RPCClient) InstallKey(key string) (keyResponse, error) { +func (c *RPCClient) InstallKey(key string) (keyringResponse, error) { header := requestHeader{ Command: installKeyCommand, Seq: c.getSeq(), } - req := keyRequest{key} - var resp keyResponse + req := keyringRequest{key} + var resp keyringResponse err := c.genericRPC(&header, &req, &resp) return resp, err } -func (c *RPCClient) UseKey(key string) (keyResponse, error) { +func (c *RPCClient) UseKey(key string) (keyringResponse, error) { header := requestHeader{ Command: useKeyCommand, Seq: c.getSeq(), } - req := keyRequest{key} - var resp keyResponse + req := keyringRequest{key} + var resp keyringResponse err := c.genericRPC(&header, &req, &resp) return resp, err } -func (c *RPCClient) RemoveKey(key string) (keyResponse, error) { +func (c *RPCClient) RemoveKey(key string) (keyringResponse, error) { header := requestHeader{ Command: removeKeyCommand, Seq: c.getSeq(), } - req := keyRequest{key} - var resp keyResponse + req := keyringRequest{key} + 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 3b08aa733a..e5aed38983 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -403,7 +403,7 @@ func listKeys(t *testing.T, c *RPCClient) map[string]map[string]int { return out } -func keyringError(t *testing.T, r keyResponse) { +func keyringError(t *testing.T, r keyringResponse) { for _, i := range r.Info { if i.Error == "" { t.Fatalf("no error reported from %s (%s)", i.Datacenter, i.Pool) @@ -411,7 +411,7 @@ func keyringError(t *testing.T, r keyResponse) { } } -func keyringSuccess(t *testing.T, r keyResponse) { +func keyringSuccess(t *testing.T, r keyringResponse) { for _, i := range r.Info { if i.Error != "" { t.Fatalf("error from %s (%s): %s", i.Datacenter, i.Pool, i.Error) diff --git a/command/keyring.go b/command/keyring.go index 0e0bbc19bc..e06248d42b 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -28,7 +28,7 @@ type KeyringCommand struct { func (c *KeyringCommand) Run(args []string) int { var installKey, useKey, removeKey, init, dataDir string - var listKeys, wan bool + var listKeys bool cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } @@ -39,7 +39,6 @@ func (c *KeyringCommand) Run(args []string) int { cmdFlags.BoolVar(&listKeys, "list", false, "list keys") cmdFlags.StringVar(&init, "init", "", "initialize keyring") cmdFlags.StringVar(&dataDir, "data-dir", "", "data directory") - cmdFlags.BoolVar(&wan, "wan", false, "operate on wan keyring") rpcAddr := RPCAddrFlag(cmdFlags) if err := cmdFlags.Parse(args); err != nil { @@ -281,8 +280,8 @@ Usage: consul keyring [options] without disrupting the cluster. With the exception of the -init argument, all operations performed by this - command can only be run against server nodes. All operations default to the - LAN gossip pool. + command can only be run against server nodes, and affect both the LAN and + WAN keyrings in lock-step. Options: @@ -298,8 +297,6 @@ Options: -init= Create the initial keyring files for Consul to use containing the provided key. The -data-dir argument is required with this option. - -wan Operate on the WAN keyring instead of the LAN - keyring (default). -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. ` return strings.TrimSpace(helpText) diff --git a/command/keyring_test.go b/command/keyring_test.go index c7c0847f46..98f8ba3dd1 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -24,9 +24,12 @@ func TestKeyringCommandRun(t *testing.T) { a1 := testAgentWithConfig(&conf, t) defer a1.Shutdown() - // The keyring was initialized with only the provided key - out := listKeys(t, a1.addr, false) - if !strings.Contains(out, key1) { + // The LAN and WAN keyrings were initialized with key1 + out := listKeys(t, a1.addr) + if !strings.Contains(out, "dc1 (LAN):\n"+key1) { + t.Fatalf("bad: %#v", out) + } + if !strings.Contains(out, "WAN:\n"+key1) { t.Fatalf("bad: %#v", out) } if strings.Contains(out, key2) { @@ -34,51 +37,26 @@ func TestKeyringCommandRun(t *testing.T) { } // Install the second key onto the keyring - installKey(t, a1.addr, key2, false) + installKey(t, a1.addr, key2) // Both keys should be present - out = listKeys(t, a1.addr, false) + out = listKeys(t, a1.addr) for _, key := range []string{key1, key2} { if !strings.Contains(out, key) { t.Fatalf("bad: %#v", out) } } - // WAN keyring is untouched - out = listKeys(t, a1.addr, true) - if strings.Contains(out, key2) { + // Rotate to key2, remove key1 + useKey(t, a1.addr, key2) + removeKey(t, a1.addr, key1) + + // Only key2 is present now + out = listKeys(t, a1.addr) + if !strings.Contains(out, "dc1 (LAN):\n"+key2) { t.Fatalf("bad: %#v", out) } - - // Change out the primary key - useKey(t, a1.addr, key2, false) - - // Remove the original key - removeKey(t, a1.addr, key1, false) - - // Make sure only the new key is present - out = listKeys(t, a1.addr, false) - if strings.Contains(out, key1) { - t.Fatalf("bad: %#v", out) - } - if !strings.Contains(out, key2) { - t.Fatalf("bad: %#v", out) - } - - // WAN keyring is still untouched - out = listKeys(t, a1.addr, true) - if !strings.Contains(out, key1) { - t.Fatalf("bad: %#v", out) - } - - // Rotate out the WAN key - installKey(t, a1.addr, key2, true) - useKey(t, a1.addr, key2, true) - removeKey(t, a1.addr, key1, true) - - // WAN keyring now has only the proper key - out = listKeys(t, a1.addr, true) - if !strings.Contains(out, key2) { + if !strings.Contains(out, "WAN:\n"+key2) { t.Fatalf("bad: %#v", out) } if strings.Contains(out, key1) { @@ -179,15 +157,11 @@ func TestKeyringCommandRun_initKeyring(t *testing.T) { } } -func listKeys(t *testing.T, addr string, wan bool) string { +func listKeys(t *testing.T, addr string) string { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} args := []string{"-list", "-rpc-addr=" + addr} - if wan { - args = append(args, "-wan") - } - code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) @@ -196,45 +170,33 @@ func listKeys(t *testing.T, addr string, wan bool) string { return ui.OutputWriter.String() } -func installKey(t *testing.T, addr string, key string, wan bool) { +func installKey(t *testing.T, addr string, key string) { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} args := []string{"-install=" + key, "-rpc-addr=" + addr} - if wan { - args = append(args, "-wan") - } - code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } } -func useKey(t *testing.T, addr string, key string, wan bool) { +func useKey(t *testing.T, addr string, key string) { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} args := []string{"-use=" + key, "-rpc-addr=" + addr} - if wan { - args = append(args, "-wan") - } - code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } } -func removeKey(t *testing.T, addr string, key string, wan bool) { +func removeKey(t *testing.T, addr string, key string) { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} args := []string{"-remove=" + key, "-rpc-addr=" + addr} - if wan { - args = append(args, "-wan") - } - code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) From 9d01174521dd6953c956db2578f6f118e2d93b59 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 29 Sep 2014 13:01:26 -0700 Subject: [PATCH 48/80] consul: kill unused struct fields --- command/agent/rpc.go | 4 ---- consul/internal_endpoint.go | 2 -- consul/structs/structs.go | 2 -- website/source/docs/commands/keyring.html.markdown | 4 +--- 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 81beff6620..e64084d177 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -131,8 +131,6 @@ type KeyringInfo struct { Datacenter string Pool string NumNodes int - NumResp int - NumErr int Error string } @@ -686,8 +684,6 @@ func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) erro Datacenter: kr.Datacenter, Pool: pool, NumNodes: kr.NumNodes, - NumResp: kr.NumResp, - NumErr: kr.NumErr, Error: kr.Error, } r.Info = append(r.Info, info) diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 2cd08a2c65..a1a170f772 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -81,9 +81,7 @@ func (m *Internal) ingestKeyringResponse( Datacenter: m.srv.config.Datacenter, Messages: serfResp.Messages, Keys: serfResp.Keys, - NumResp: serfResp.NumResp, NumNodes: serfResp.NumNodes, - NumErr: serfResp.NumErr, Error: errStr, }) } diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 6e752ea4f3..2557bfc399 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -548,8 +548,6 @@ type KeyringResponse struct { Messages map[string]string Keys map[string]int NumNodes int - NumResp int - NumErr int Error string } diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown index 6a635746c2..03b1d6c25c 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -14,9 +14,7 @@ distributing new encryption keys to the cluster, retiring old encryption keys, and changing the keys used by the cluster to encrypt messages. Because Consul utilizes multiple gossip pools, this command will only operate -against a server node for most operations. By default, all operations carried -out by this command are run against the LAN gossip pool in the datacenter of the -agent. +against a server node for most operations. Consul allows multiple encryption keys to be in use simultaneously. This is intended to provide a transition state while the cluster converges. It is the From 91ad3461f4b7117927cd4df0020e93dd34e8e570 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 29 Sep 2014 13:44:51 -0700 Subject: [PATCH 49/80] command: fix panic when client RPC is asked for a keyring operation --- command/agent/rpc.go | 5 +++++ command/keyring.go | 14 -------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/command/agent/rpc.go b/command/agent/rpc.go index e64084d177..c4fe71f70a 100644 --- a/command/agent/rpc.go +++ b/command/agent/rpc.go @@ -655,6 +655,10 @@ func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) erro Error: errToString(err), } + if queryResp == nil { + goto SEND + } + for _, kr := range queryResp.Responses { var pool string if kr.WAN { @@ -689,6 +693,7 @@ func (i *AgentRPC) handleKeyring(client *rpcClient, seq uint64, cmd string) erro r.Info = append(r.Info, info) } +SEND: return client.Send(&header, r) } diff --git a/command/keyring.go b/command/keyring.go index e06248d42b..cc6d211a83 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -96,20 +96,6 @@ func (c *KeyringCommand) Run(args []string) int { } defer client.Close() - // For all key-related operations, we must be querying a server node. It is - // probably better to enforce this even for LAN pool changes, because other- - // wise, the same exact command syntax will have different results depending - // on where it was run. - s, err := client.Stats() - if err != nil { - c.Ui.Error(fmt.Sprintf("Error: %s", err)) - return 1 - } - if s["consul"]["server"] != "true" { - c.Ui.Error("Error: Key modification can only be handled by a server") - return 1 - } - if listKeys { c.Ui.Info("Asking all members for installed keys...") r, err := client.ListKeys() From 6a3271980e334588aab7cab5f40faf70f0f55ea5 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 29 Sep 2014 15:49:47 -0700 Subject: [PATCH 50/80] command/keyring: refactor, adjust tests --- command/keyring.go | 17 ++++------------- command/keyring_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/command/keyring.go b/command/keyring.go index cc6d211a83..cde37f3e6c 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -11,7 +11,6 @@ import ( "github.com/hashicorp/consul/command/agent" "github.com/mitchellh/cli" - "github.com/ryanuber/columnize" ) const ( @@ -97,7 +96,7 @@ func (c *KeyringCommand) Run(args []string) int { defer client.Close() if listKeys { - c.Ui.Info("Asking all members for installed keys...") + c.Ui.Info("Gathering installed encryption keys...") r, err := client.ListKeys() if err != nil { c.Ui.Error(fmt.Sprintf("error: %s", err)) @@ -161,17 +160,12 @@ func (c *KeyringCommand) handleResponse( c.Ui.Error("") c.Ui.Error(fmt.Sprintf("%s error: %s", pool, i.Error)) - var errors []string for _, msg := range messages { if msg.Datacenter != i.Datacenter || msg.Pool != i.Pool { continue } - errors = append(errors, fmt.Sprintf( - "failed: %s | %s", - msg.Node, - msg.Message)) + c.Ui.Error(fmt.Sprintf(" %s: %s", msg.Node, msg.Message)) } - c.Ui.Error(columnize.SimpleFormat(errors)) rval = 1 } } @@ -208,16 +202,13 @@ func (c *KeyringCommand) handleList( installed[pool][key.Key] = []int{key.Count, nodes} } } + for pool, keys := range installed { c.Ui.Output("") c.Ui.Output(pool + ":") - var out []string for key, num := range keys { - out = append(out, fmt.Sprintf( - "%s | [%d/%d]", - key, num[0], num[1])) + c.Ui.Output(fmt.Sprintf(" %s [%d/%d]", key, num[0], num[1])) } - c.Ui.Output(columnize.SimpleFormat(out)) } } diff --git a/command/keyring_test.go b/command/keyring_test.go index 98f8ba3dd1..7e975b6ccc 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -26,10 +26,10 @@ func TestKeyringCommandRun(t *testing.T) { // The LAN and WAN keyrings were initialized with key1 out := listKeys(t, a1.addr) - if !strings.Contains(out, "dc1 (LAN):\n"+key1) { + if !strings.Contains(out, "dc1 (LAN):\n "+key1) { t.Fatalf("bad: %#v", out) } - if !strings.Contains(out, "WAN:\n"+key1) { + if !strings.Contains(out, "WAN:\n "+key1) { t.Fatalf("bad: %#v", out) } if strings.Contains(out, key2) { @@ -53,10 +53,10 @@ func TestKeyringCommandRun(t *testing.T) { // Only key2 is present now out = listKeys(t, a1.addr) - if !strings.Contains(out, "dc1 (LAN):\n"+key2) { + if !strings.Contains(out, "dc1 (LAN):\n "+key2) { t.Fatalf("bad: %#v", out) } - if !strings.Contains(out, "WAN:\n"+key2) { + if !strings.Contains(out, "WAN:\n "+key2) { t.Fatalf("bad: %#v", out) } if strings.Contains(out, key1) { From 74c7b1239b15252530764c669acdff87c0e1a649 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 29 Sep 2014 23:19:53 -0700 Subject: [PATCH 51/80] consul: break rpc forwarding and response ingestion out of internal endpoints --- consul/internal_endpoint.go | 77 ++++++++----------------------------- consul/keyring.go | 55 ++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 61 deletions(-) create mode 100644 consul/keyring.go diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index a1a170f772..9d8d000e9a 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -2,7 +2,6 @@ package consul import ( "github.com/hashicorp/consul/consul/structs" - "github.com/hashicorp/serf/serf" ) // Internal endpoint is used to query the miscellaneous info that @@ -64,67 +63,23 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, return m.srv.UserEvent(args.Name, args.Payload) } -// ingestKeyringResponse is a helper method to pick the relative information -// from a Serf message and stuff it into a KeyringResponse. -func (m *Internal) ingestKeyringResponse( - serfResp *serf.KeyResponse, - reply *structs.KeyringResponses, - err error, wan bool) { - - errStr := "" - if err != nil { - errStr = err.Error() - } - - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - WAN: wan, - Datacenter: m.srv.config.Datacenter, - Messages: serfResp.Messages, - Keys: serfResp.Keys, - NumNodes: serfResp.NumNodes, - Error: errStr, - }) -} - -func (m *Internal) forwardKeyring( - method string, - args *structs.KeyringRequest, - replies *structs.KeyringResponses) error { - - for dc, _ := range m.srv.remoteConsuls { - if dc == m.srv.config.Datacenter { - continue - } - rr := structs.KeyringResponses{} - if err := m.srv.forwardDC(method, dc, args, &rr); err != nil { - return err - } - for _, r := range rr.Responses { - replies.Responses = append(replies.Responses, r) - } - } - - return nil -} - // ListKeys will query the WAN and LAN gossip keyrings of all nodes, adding // results into a collective response as we go. func (m *Internal) ListKeys( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { - m.srv.setQueryMeta(&reply.QueryMeta) - + dc := m.srv.config.Datacenter respLAN, err := m.srv.KeyManagerLAN().ListKeys() - m.ingestKeyringResponse(respLAN, reply, err, false) + ingestKeyringResponse(respLAN, reply, dc, false, err) if !args.Forwarded { respWAN, err := m.srv.KeyManagerWAN().ListKeys() - m.ingestKeyringResponse(respWAN, reply, err, true) + ingestKeyringResponse(respWAN, reply, dc, true, err) // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - m.forwardKeyring("Internal.ListKeys", args, reply) + m.srv.forwardKeyringRPC("Internal.ListKeys", args, reply) } return nil @@ -136,16 +91,16 @@ func (m *Internal) InstallKey( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { + dc := m.srv.config.Datacenter respLAN, err := m.srv.KeyManagerLAN().InstallKey(args.Key) - m.ingestKeyringResponse(respLAN, reply, err, false) + ingestKeyringResponse(respLAN, reply, dc, false, err) if !args.Forwarded { respWAN, err := m.srv.KeyManagerWAN().InstallKey(args.Key) - m.ingestKeyringResponse(respWAN, reply, err, true) + ingestKeyringResponse(respWAN, reply, dc, true, err) - // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - m.forwardKeyring("Internal.InstallKey", args, reply) + m.srv.forwardKeyringRPC("Internal.InstallKey", args, reply) } return nil @@ -157,16 +112,16 @@ func (m *Internal) UseKey( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { + dc := m.srv.config.Datacenter respLAN, err := m.srv.KeyManagerLAN().UseKey(args.Key) - m.ingestKeyringResponse(respLAN, reply, err, false) + ingestKeyringResponse(respLAN, reply, dc, false, err) if !args.Forwarded { respWAN, err := m.srv.KeyManagerWAN().UseKey(args.Key) - m.ingestKeyringResponse(respWAN, reply, err, true) + ingestKeyringResponse(respWAN, reply, dc, true, err) - // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - m.forwardKeyring("Internal.UseKey", args, reply) + m.srv.forwardKeyringRPC("Internal.UseKey", args, reply) } return nil @@ -177,16 +132,16 @@ func (m *Internal) RemoveKey( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { + dc := m.srv.config.Datacenter respLAN, err := m.srv.KeyManagerLAN().RemoveKey(args.Key) - m.ingestKeyringResponse(respLAN, reply, err, false) + ingestKeyringResponse(respLAN, reply, dc, false, err) if !args.Forwarded { respWAN, err := m.srv.KeyManagerWAN().RemoveKey(args.Key) - m.ingestKeyringResponse(respWAN, reply, err, true) + ingestKeyringResponse(respWAN, reply, dc, true, err) - // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - m.forwardKeyring("Internal.RemoveKey", args, reply) + m.srv.forwardKeyringRPC("Internal.RemoveKey", args, reply) } return nil diff --git a/consul/keyring.go b/consul/keyring.go new file mode 100644 index 0000000000..373523118e --- /dev/null +++ b/consul/keyring.go @@ -0,0 +1,55 @@ +package consul + +import ( + "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/serf/serf" +) + +// ingestKeyringResponse is a helper method to pick the relative information +// from a Serf message and stuff it into a KeyringResponse. +func ingestKeyringResponse( + serfResp *serf.KeyResponse, reply *structs.KeyringResponses, + dc string, wan bool, err error) { + + errStr := "" + if err != nil { + errStr = err.Error() + } + + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + WAN: wan, + Datacenter: dc, + Messages: serfResp.Messages, + Keys: serfResp.Keys, + NumNodes: serfResp.NumNodes, + Error: errStr, + }) +} + +// forwardKeyringRPC is used to forward a keyring-related RPC request to one +// server in each datacenter. Since the net/rpc package writes replies in-place, +// we use this specialized method for dealing with keyring-related replies +// specifically by appending them to a wrapper response struct. +// +// This will only error for RPC-related errors. Otherwise, application-level +// errors are returned inside of the inner response objects. +func (s *Server) forwardKeyringRPC( + method string, + args *structs.KeyringRequest, + replies *structs.KeyringResponses) error { + + for dc, _ := range s.remoteConsuls { + if dc == s.config.Datacenter { + continue + } + rr := structs.KeyringResponses{} + if err := s.forwardDC(method, dc, args, &rr); err != nil { + return err + } + for _, r := range rr.Responses { + replies.Responses = append(replies.Responses, r) + } + } + + return nil +} From 9b8e753b1534005b7135a3754eb0cf0262208dd2 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Tue, 30 Sep 2014 10:03:47 -0700 Subject: [PATCH 52/80] consul: make forwarding to multiple datacenters parallel --- consul/internal_endpoint.go | 8 ++-- consul/keyring.go | 55 -------------------------- consul/serf.go | 79 +++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 59 deletions(-) delete mode 100644 consul/keyring.go diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 9d8d000e9a..ee22707002 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -79,7 +79,7 @@ func (m *Internal) ListKeys( // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - m.srv.forwardKeyringRPC("Internal.ListKeys", args, reply) + m.srv.keyringRPC("Internal.ListKeys", args, reply) } return nil @@ -100,7 +100,7 @@ func (m *Internal) InstallKey( ingestKeyringResponse(respWAN, reply, dc, true, err) args.Forwarded = true - m.srv.forwardKeyringRPC("Internal.InstallKey", args, reply) + m.srv.keyringRPC("Internal.InstallKey", args, reply) } return nil @@ -121,7 +121,7 @@ func (m *Internal) UseKey( ingestKeyringResponse(respWAN, reply, dc, true, err) args.Forwarded = true - m.srv.forwardKeyringRPC("Internal.UseKey", args, reply) + m.srv.keyringRPC("Internal.UseKey", args, reply) } return nil @@ -141,7 +141,7 @@ func (m *Internal) RemoveKey( ingestKeyringResponse(respWAN, reply, dc, true, err) args.Forwarded = true - m.srv.forwardKeyringRPC("Internal.RemoveKey", args, reply) + m.srv.keyringRPC("Internal.RemoveKey", args, reply) } return nil diff --git a/consul/keyring.go b/consul/keyring.go deleted file mode 100644 index 373523118e..0000000000 --- a/consul/keyring.go +++ /dev/null @@ -1,55 +0,0 @@ -package consul - -import ( - "github.com/hashicorp/consul/consul/structs" - "github.com/hashicorp/serf/serf" -) - -// ingestKeyringResponse is a helper method to pick the relative information -// from a Serf message and stuff it into a KeyringResponse. -func ingestKeyringResponse( - serfResp *serf.KeyResponse, reply *structs.KeyringResponses, - dc string, wan bool, err error) { - - errStr := "" - if err != nil { - errStr = err.Error() - } - - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - WAN: wan, - Datacenter: dc, - Messages: serfResp.Messages, - Keys: serfResp.Keys, - NumNodes: serfResp.NumNodes, - Error: errStr, - }) -} - -// forwardKeyringRPC is used to forward a keyring-related RPC request to one -// server in each datacenter. Since the net/rpc package writes replies in-place, -// we use this specialized method for dealing with keyring-related replies -// specifically by appending them to a wrapper response struct. -// -// This will only error for RPC-related errors. Otherwise, application-level -// errors are returned inside of the inner response objects. -func (s *Server) forwardKeyringRPC( - method string, - args *structs.KeyringRequest, - replies *structs.KeyringResponses) error { - - for dc, _ := range s.remoteConsuls { - if dc == s.config.Datacenter { - continue - } - rr := structs.KeyringResponses{} - if err := s.forwardDC(method, dc, args, &rr); err != nil { - return err - } - for _, r := range rr.Responses { - replies.Responses = append(replies.Responses, r) - } - } - - return nil -} diff --git a/consul/serf.go b/consul/serf.go index ae1dacc33e..bfdf5668e8 100644 --- a/consul/serf.go +++ b/consul/serf.go @@ -4,6 +4,7 @@ import ( "net" "strings" + "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/serf/serf" ) @@ -276,3 +277,81 @@ func (s *Server) nodeFailed(me serf.MemberEvent, wan bool) { } } } + +// ingestKeyringResponse is a helper method to pick the relative information +// from a Serf message and stuff it into a KeyringResponse. +func ingestKeyringResponse( + serfResp *serf.KeyResponse, reply *structs.KeyringResponses, + dc string, wan bool, err error) { + + errStr := "" + if err != nil { + errStr = err.Error() + } + + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + WAN: wan, + Datacenter: dc, + Messages: serfResp.Messages, + Keys: serfResp.Keys, + NumNodes: serfResp.NumNodes, + Error: errStr, + }) +} + +// forwardKeyring handles sending an RPC request to a remote datacenter and +// funneling any errors or responses back through the provided channels. +func (s *Server) forwardKeyringRPC( + method, dc string, + args *structs.KeyringRequest, + errorCh chan<- error, + respCh chan<- *structs.KeyringResponses) { + + rr := structs.KeyringResponses{} + if err := s.forwardDC(method, dc, args, &rr); err != nil { + errorCh <- err + return + } + respCh <- &rr + return +} + +// keyringRPC is used to forward a keyring-related RPC request to one +// server in each datacenter. This will only error for RPC-related errors. +// Otherwise, application-level errors are returned inside of the inner +// response objects. +func (s *Server) keyringRPC( + method string, + args *structs.KeyringRequest, + replies *structs.KeyringResponses) error { + + errorCh := make(chan error) + respCh := make(chan *structs.KeyringResponses) + + for dc, _ := range s.remoteConsuls { + if dc == s.config.Datacenter { + continue + } + go s.forwardKeyringRPC(method, dc, args, errorCh, respCh) + } + + rlen := len(s.remoteConsuls) - 1 + done := 0 + for { + select { + case err := <-errorCh: + return err + case rr := <-respCh: + for _, r := range rr.Responses { + replies.Responses = append(replies.Responses, r) + } + done++ + } + + if done == rlen { + break + } + } + + return nil +} From 61b0908e3a2d54274d724364bfafee28969f0f60 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Tue, 30 Sep 2014 14:19:25 -0700 Subject: [PATCH 53/80] consul: test rpc errors returned from remote datacenters --- consul/serf.go | 6 +++++- consul/serf_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/consul/serf.go b/consul/serf.go index bfdf5668e8..02d0683741 100644 --- a/consul/serf.go +++ b/consul/serf.go @@ -325,6 +325,11 @@ func (s *Server) keyringRPC( args *structs.KeyringRequest, replies *structs.KeyringResponses) error { + rlen := len(s.remoteConsuls) - 1 + if rlen == 0 { + return nil + } + errorCh := make(chan error) respCh := make(chan *structs.KeyringResponses) @@ -335,7 +340,6 @@ func (s *Server) keyringRPC( go s.forwardKeyringRPC(method, dc, args, errorCh, respCh) } - rlen := len(s.remoteConsuls) - 1 done := 0 for { select { diff --git a/consul/serf_test.go b/consul/serf_test.go index 07225a7293..b30b4fe821 100644 --- a/consul/serf_test.go +++ b/consul/serf_test.go @@ -1,6 +1,8 @@ package consul import ( + "fmt" + "os" "testing" ) @@ -19,3 +21,25 @@ func TestUserEventNames(t *testing.T) { t.Fatalf("bad: %v", raw) } } + +func TestKeyringRPCError(t *testing.T) { + dir1, s1 := testServerDC(t, "dc1") + defer os.RemoveAll(dir1) + defer s1.Shutdown() + + dir2, s2 := testServerDC(t, "dc2") + defer os.RemoveAll(dir2) + defer s2.Shutdown() + + // Try to join + addr := fmt.Sprintf("127.0.0.1:%d", + s1.config.SerfWANConfig.MemberlistConfig.BindPort) + if _, err := s2.JoinWAN([]string{addr}); err != nil { + t.Fatalf("err: %v", err) + } + + // RPC error from remote datacenter is returned + if err := s1.keyringRPC("Bad.Method", nil, nil); err == nil { + t.Fatalf("bad") + } +} From daf77c3f5bab73db6737e19058284ad4085102ce Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Tue, 30 Sep 2014 15:31:07 -0700 Subject: [PATCH 54/80] command/keyring: cleanup --- command/keyring.go | 22 +++++++------------ consul/structs/structs.go | 3 +++ .../docs/commands/keyring.html.markdown | 13 +++++++---- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/command/keyring.go b/command/keyring.go index cde37f3e6c..78c56fd2b9 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -13,12 +13,6 @@ import ( "github.com/mitchellh/cli" ) -const ( - installKeyCommand = "install" - useKeyCommand = "use" - removeKeyCommand = "remove" -) - // KeyringCommand is a Command implementation that handles querying, installing, // and removing gossip encryption keys from a keyring. type KeyringCommand struct { @@ -102,10 +96,10 @@ func (c *KeyringCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - if rval := c.handleResponse(r.Info, r.Messages, r.Keys); rval != 0 { + if rval := c.handleResponse(r.Info, r.Messages); rval != 0 { return rval } - c.handleList(r.Info, r.Messages, r.Keys) + c.handleList(r.Info, r.Keys) return 0 } @@ -116,7 +110,7 @@ func (c *KeyringCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - return c.handleResponse(r.Info, r.Messages, r.Keys) + return c.handleResponse(r.Info, r.Messages) } if useKey != "" { @@ -126,7 +120,7 @@ func (c *KeyringCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - return c.handleResponse(r.Info, r.Messages, r.Keys) + return c.handleResponse(r.Info, r.Messages) } if removeKey != "" { @@ -136,7 +130,7 @@ func (c *KeyringCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - return c.handleResponse(r.Info, r.Messages, r.Keys) + return c.handleResponse(r.Info, r.Messages) } // Should never make it here @@ -145,8 +139,7 @@ func (c *KeyringCommand) Run(args []string) int { func (c *KeyringCommand) handleResponse( info []agent.KeyringInfo, - messages []agent.KeyringMessage, - keys []agent.KeyringEntry) int { + messages []agent.KeyringMessage) int { var rval int @@ -179,7 +172,6 @@ func (c *KeyringCommand) handleResponse( func (c *KeyringCommand) handleList( info []agent.KeyringInfo, - messages []agent.KeyringMessage, keys []agent.KeyringEntry) { installed := make(map[string]map[string][]int) @@ -274,6 +266,8 @@ Options: -init= Create the initial keyring files for Consul to use containing the provided key. The -data-dir argument is required with this option. + -data-dir= The path to the Consul agent's data directory. This + argument is only needed for keyring initialization. -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. ` return strings.TrimSpace(helpText) diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 2557bfc399..dfaadbaa71 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -551,6 +551,9 @@ type KeyringResponse struct { Error string } +// KeyringResponses holds multiple responses to keyring queries. Each +// datacenter replies independently, and KeyringResponses is used as a +// container for the set of all responses. type KeyringResponses struct { Responses []*KeyringResponse QueryMeta diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown index 03b1d6c25c..518ccefc9a 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -22,6 +22,10 @@ responsibility of the operator to ensure that only the required encryption keys are installed on the cluster. You can ensure that a key is not installed using the `-list` and `-remove` options. +With the exception of the `-init` argument, all operations performed by this +command can only be run against server nodes, and affect both the LAN and +WAN keyrings in lock-step. + All variations of the `keyring` command, unless otherwise specified below, will return 0 if all nodes reply and there are no errors. If any node fails to reply or reports failure, the exit code will be 1. @@ -38,13 +42,14 @@ The list of available flags are: * `-init` - Creates the keyring file(s). This is useful to configure initial encryption keyrings, which can later be mutated using the other arguments in this command. This argument accepts an ASCII key, which can be generated using - the [keygen command](/docs/commands/keygen.html). + the [keygen command](/docs/commands/keygen.html). Requires the `-data-dir` + argument. This operation can be run on both client and server nodes and requires no network connectivity. - Returns 0 if the key is successfully configured, or 1 if there were any - problems. + Returns 0 if the key is successfully configured, or 1 if there were any + problems. * `-install` - Install a new encryption key. This will broadcast the new key to all members in the cluster. @@ -57,6 +62,6 @@ The list of available flags are: * `-list` - List all keys currently in use within the cluster. -* `-wan` - Operate on the WAN keyring instead of the LAN keyring (default) +* `-data-dir` - The path to Consul's data directory. Used with `-init` only. * `-rpc-addr` - RPC address of the Consul agent. From 7e0af5153ff20d36baaca74effc066e20fee942c Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Tue, 30 Sep 2014 17:31:16 -0700 Subject: [PATCH 55/80] website: clean up keyring command docs and add output examples --- .../docs/commands/keyring.html.markdown | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown index 518ccefc9a..6caa172dd7 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -13,14 +13,11 @@ Consul's [Gossip Pools](/docs/internals/gossip.html). It is capable of distributing new encryption keys to the cluster, retiring old encryption keys, and changing the keys used by the cluster to encrypt messages. -Because Consul utilizes multiple gossip pools, this command will only operate -against a server node for most operations. - Consul allows multiple encryption keys to be in use simultaneously. This is intended to provide a transition state while the cluster converges. It is the responsibility of the operator to ensure that only the required encryption keys -are installed on the cluster. You can ensure that a key is not installed using -the `-list` and `-remove` options. +are installed on the cluster. You can review the installed keys using the +`-list` argument, and remove unneeded keys with `-remove`. With the exception of the `-init` argument, all operations performed by this command can only be run against server nodes, and affect both the LAN and @@ -65,3 +62,55 @@ The list of available flags are: * `-data-dir` - The path to Consul's data directory. Used with `-init` only. * `-rpc-addr` - RPC address of the Consul agent. + +## Output + +The output of the `consul keyring -list` command consolidates information from +all nodes and all datacenters to provide a simple and easy to understand view of +the cluster. The following is some example output from a cluster with two +datacenters, each which consist of one server and one client: + +``` +==> Gathering installed encryption keys... +==> Done! + +WAN: + a1i101sMY8rxB+0eAKD/gw== [2/2] + +dc2 (LAN): + a1i101sMY8rxB+0eAKD/gw== [2/2] + +dc1 (LAN): + a1i101sMY8rxB+0eAKD/gw== [2/2] +``` + +As you can see, the output above is divided first by gossip pool, and then by +encryption key. The indicator to the right of each key displays the number of +nodes the key is installed on over the total number of nodes in the pool. + +## Errors + +If any errors are encountered while performing a keyring operation, no key +information is displayed, but instead only error information. The error +information is arranged in a similar fashion, organized first by datacenter, +followed by a simple list of nodes which had errors, and the actual text of the +error. Below is sample output from the same cluster as above, if we try to do +something that causes an error; in this case, trying to remove the primary key: + +``` +==> Removing gossip encryption key... + +dc1 (LAN) error: 2/2 nodes reported failure + server1: Removing the primary key is not allowed + client1: Removing the primary key is not allowed + +WAN error: 2/2 nodes reported failure + server1.dc1: Removing the primary key is not allowed + server2.dc2: Removing the primary key is not allowed + +dc2 (LAN) error: 2/2 nodes reported failure + server2: Removing the primary key is not allowed + client2: Removing the primary key is not allowed +``` + +As you can see, each node with a failure reported what went wrong. From c11f6b5152e088bbff0df9d1683260a702cc243c Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Tue, 30 Sep 2014 18:23:55 -0700 Subject: [PATCH 56/80] agent: fix install key test --- command/agent/rpc_client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/agent/rpc_client_test.go b/command/agent/rpc_client_test.go index e5aed38983..518423a69a 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -301,7 +301,7 @@ func TestRPCClientListKeys(t *testing.T) { func TestRPCClientInstallKey(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" key2 := "xAEZ3uVHRMZD9GcYMZaRQw==" - conf := Config{EncryptKey: key1} + conf := Config{EncryptKey: key1, Server: true} p1 := testRPCClientWithConfig(t, &conf) defer p1.Close() From 9556347609ff95f7d85bc0c9c69d29948dbda62c Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 1 Oct 2014 23:09:00 -0700 Subject: [PATCH 57/80] consul: generalize multi-DC RPC call broadcasts --- consul/internal_endpoint.go | 31 +++++++++++--- consul/rpc.go | 43 +++++++++++++++++++ consul/serf.go | 83 ------------------------------------- consul/structs/structs.go | 40 +++++++++++++++++- 4 files changed, 107 insertions(+), 90 deletions(-) diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index ee22707002..223b8eeff8 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -2,6 +2,7 @@ package consul import ( "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/serf/serf" ) // Internal endpoint is used to query the miscellaneous info that @@ -77,9 +78,8 @@ func (m *Internal) ListKeys( respWAN, err := m.srv.KeyManagerWAN().ListKeys() ingestKeyringResponse(respWAN, reply, dc, true, err) - // Mark key rotation as being already forwarded, then forward. args.Forwarded = true - m.srv.keyringRPC("Internal.ListKeys", args, reply) + m.srv.globalRPC("Internal.ListKeys", args, reply) } return nil @@ -100,7 +100,7 @@ func (m *Internal) InstallKey( ingestKeyringResponse(respWAN, reply, dc, true, err) args.Forwarded = true - m.srv.keyringRPC("Internal.InstallKey", args, reply) + m.srv.globalRPC("Internal.InstallKey", args, reply) } return nil @@ -121,7 +121,7 @@ func (m *Internal) UseKey( ingestKeyringResponse(respWAN, reply, dc, true, err) args.Forwarded = true - m.srv.keyringRPC("Internal.UseKey", args, reply) + m.srv.globalRPC("Internal.UseKey", args, reply) } return nil @@ -141,8 +141,29 @@ func (m *Internal) RemoveKey( ingestKeyringResponse(respWAN, reply, dc, true, err) args.Forwarded = true - m.srv.keyringRPC("Internal.RemoveKey", args, reply) + m.srv.globalRPC("Internal.RemoveKey", args, reply) } return nil } + +// ingestKeyringResponse is a helper method to pick the relative information +// from a Serf message and stuff it into a KeyringResponse. +func ingestKeyringResponse( + serfResp *serf.KeyResponse, reply *structs.KeyringResponses, + dc string, wan bool, err error) { + + errStr := "" + if err != nil { + errStr = err.Error() + } + + reply.Responses = append(reply.Responses, &structs.KeyringResponse{ + WAN: wan, + Datacenter: dc, + Messages: serfResp.Messages, + Keys: serfResp.Keys, + NumNodes: serfResp.NumNodes, + Error: errStr, + }) +} diff --git a/consul/rpc.go b/consul/rpc.go index cd5c36ebd3..6fcb07d820 100644 --- a/consul/rpc.go +++ b/consul/rpc.go @@ -223,6 +223,49 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{ return s.connPool.RPC(server.Addr, server.Version, method, args, reply) } +// globalRPC is used to forward an RPC request to one server in each datacenter. +// This will only error for RPC-related errors. Otherwise, application-level +// errors are returned inside of the inner response objects. +func (s *Server) globalRPC(method string, args interface{}, + reply structs.CompoundResponse) error { + + rlen := len(s.remoteConsuls) + if rlen < 2 { + return nil + } + + errorCh := make(chan error) + respCh := make(chan interface{}) + + // Make a new request into each datacenter + for dc, _ := range s.remoteConsuls { + info := &structs.GenericRPC{Datacenter: dc} + go func() { + rr := reply.New() + if _, err := s.forward(method, info, args, &rr); err != nil { + errorCh <- err + return + } + respCh <- rr + }() + } + + done := 0 + for { + select { + case err := <-errorCh: + return err + case rr := <-respCh: + reply.Add(rr) + done++ + } + if done == rlen { + break + } + } + return nil +} + // raftApply is used to encode a message, run it through raft, and return // the FSM response along with any errors func (s *Server) raftApply(t structs.MessageType, msg interface{}) (interface{}, error) { diff --git a/consul/serf.go b/consul/serf.go index 02d0683741..ae1dacc33e 100644 --- a/consul/serf.go +++ b/consul/serf.go @@ -4,7 +4,6 @@ import ( "net" "strings" - "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/serf/serf" ) @@ -277,85 +276,3 @@ func (s *Server) nodeFailed(me serf.MemberEvent, wan bool) { } } } - -// ingestKeyringResponse is a helper method to pick the relative information -// from a Serf message and stuff it into a KeyringResponse. -func ingestKeyringResponse( - serfResp *serf.KeyResponse, reply *structs.KeyringResponses, - dc string, wan bool, err error) { - - errStr := "" - if err != nil { - errStr = err.Error() - } - - reply.Responses = append(reply.Responses, &structs.KeyringResponse{ - WAN: wan, - Datacenter: dc, - Messages: serfResp.Messages, - Keys: serfResp.Keys, - NumNodes: serfResp.NumNodes, - Error: errStr, - }) -} - -// forwardKeyring handles sending an RPC request to a remote datacenter and -// funneling any errors or responses back through the provided channels. -func (s *Server) forwardKeyringRPC( - method, dc string, - args *structs.KeyringRequest, - errorCh chan<- error, - respCh chan<- *structs.KeyringResponses) { - - rr := structs.KeyringResponses{} - if err := s.forwardDC(method, dc, args, &rr); err != nil { - errorCh <- err - return - } - respCh <- &rr - return -} - -// keyringRPC is used to forward a keyring-related RPC request to one -// server in each datacenter. This will only error for RPC-related errors. -// Otherwise, application-level errors are returned inside of the inner -// response objects. -func (s *Server) keyringRPC( - method string, - args *structs.KeyringRequest, - replies *structs.KeyringResponses) error { - - rlen := len(s.remoteConsuls) - 1 - if rlen == 0 { - return nil - } - - errorCh := make(chan error) - respCh := make(chan *structs.KeyringResponses) - - for dc, _ := range s.remoteConsuls { - if dc == s.config.Datacenter { - continue - } - go s.forwardKeyringRPC(method, dc, args, errorCh, respCh) - } - - done := 0 - for { - select { - case err := <-errorCh: - return err - case rr := <-respCh: - for _, r := range rr.Responses { - replies.Responses = append(replies.Responses, r) - } - done++ - } - - if done == rlen { - break - } - } - - return nil -} diff --git a/consul/structs/structs.go b/consul/structs/structs.go index dfaadbaa71..1b3ead9607 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -127,6 +127,16 @@ type QueryMeta struct { KnownLeader bool } +// GenericRPC is the simplest possible RPCInfo implementation +type GenericRPC struct { + Datacenter string + QueryOptions +} + +func (r *GenericRPC) RequestDatacenter() string { + return r.Datacenter +} + // RegisterRequest is used for the Catalog.Register endpoint // to register a node as providing a service. If no service // is provided, the node is registered. @@ -532,14 +542,31 @@ func Encode(t MessageType, msg interface{}) ([]byte, error) { return buf.Bytes(), err } +// CompoundResponse is an interface for gathering multiple responses. It is +// used in cross-datacenter RPC calls where more than 1 datacenter is +// expected to reply. +type CompoundResponse interface { + // Add adds a new response to the compound response + Add(interface{}) + + // New returns an empty response object which can be passed around by + // reference, and then passed to Add() later on. + New() interface{} +} + // KeyringRequest encapsulates a request to modify an encryption keyring. // It can be used for install, remove, or use key type operations. type KeyringRequest struct { - Key string - Forwarded bool + Key string + Datacenter string + Forwarded bool QueryOptions } +func (r *KeyringRequest) RequestDatacenter() string { + return r.Datacenter +} + // KeyringResponse is a unified key response and can be used for install, // remove, use, as well as listing key queries. type KeyringResponse struct { @@ -558,3 +585,12 @@ type KeyringResponses struct { Responses []*KeyringResponse QueryMeta } + +func (r *KeyringResponses) Add(v interface{}) { + val := v.(*KeyringResponses) + r.Responses = append(r.Responses, val.Responses...) +} + +func (r *KeyringResponses) New() interface{} { + return new(KeyringResponses) +} From 7b4b87ccf1cbf51620904e4d8f3914f016229584 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 2 Oct 2014 17:10:54 -0700 Subject: [PATCH 58/80] consul: use keyring operation type to cut out duplicated logic --- command/agent/keyring.go | 12 ++++-- consul/internal_endpoint.go | 86 +++++++++++-------------------------- consul/structs/structs.go | 10 +++++ 3 files changed, 44 insertions(+), 64 deletions(-) diff --git a/command/agent/keyring.go b/command/agent/keyring.go index 827aa76bc7..f6d80a9dce 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -84,26 +84,30 @@ func (a *Agent) keyringProcess( func (a *Agent) ListKeys() (*structs.KeyringResponses, error) { args := structs.KeyringRequest{} args.AllowStale = true - return a.keyringProcess("Internal.ListKeys", &args) + args.Operation = structs.KeyringList + return a.keyringProcess("Internal.KeyringOperation", &args) } // InstallKey installs a new gossip encryption key func (a *Agent) InstallKey(key string) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key} args.AllowStale = true - return a.keyringProcess("Internal.InstallKey", &args) + args.Operation = structs.KeyringInstall + return a.keyringProcess("Internal.KeyringOperation", &args) } // UseKey changes the primary encryption key used to encrypt messages func (a *Agent) UseKey(key string) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key} args.AllowStale = true - return a.keyringProcess("Internal.UseKey", &args) + args.Operation = structs.KeyringUse + return a.keyringProcess("Internal.KeyringOperation", &args) } // RemoveKey will remove a gossip encryption key from the keyring func (a *Agent) RemoveKey(key string) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key} args.AllowStale = true - return a.keyringProcess("Internal.RemoveKey", &args) + args.Operation = structs.KeyringRemove + return a.keyringProcess("Internal.KeyringOperation", &args) } diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 223b8eeff8..f9bed288ca 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -12,6 +12,15 @@ type Internal struct { srv *Server } +type KeyringOperation uint8 + +const ( + listKeysOperation KeyringOperation = iota + installKeyOperation + useKeyOperation + removeKeyOperation +) + // ChecksInState is used to get all the checks in a given state func (m *Internal) NodeInfo(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeDump) error { @@ -66,85 +75,42 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, // ListKeys will query the WAN and LAN gossip keyrings of all nodes, adding // results into a collective response as we go. -func (m *Internal) ListKeys( +func (m *Internal) KeyringOperation( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { dc := m.srv.config.Datacenter - respLAN, err := m.srv.KeyManagerLAN().ListKeys() + + respLAN, err := m.doKeyringOperation(args, m.srv.KeyManagerLAN()) ingestKeyringResponse(respLAN, reply, dc, false, err) if !args.Forwarded { - respWAN, err := m.srv.KeyManagerWAN().ListKeys() + respWAN, err := m.doKeyringOperation(args, m.srv.KeyManagerWAN()) ingestKeyringResponse(respWAN, reply, dc, true, err) args.Forwarded = true - m.srv.globalRPC("Internal.ListKeys", args, reply) + return m.srv.globalRPC("Internal.KeyringOperation", args, reply) } return nil } -// InstallKey broadcasts a new encryption key to all nodes. This involves -// installing a new key on every node across all datacenters. -func (m *Internal) InstallKey( +func (m *Internal) doKeyringOperation( args *structs.KeyringRequest, - reply *structs.KeyringResponses) error { + mgr *serf.KeyManager) (r *serf.KeyResponse, err error) { - dc := m.srv.config.Datacenter - respLAN, err := m.srv.KeyManagerLAN().InstallKey(args.Key) - ingestKeyringResponse(respLAN, reply, dc, false, err) - - if !args.Forwarded { - respWAN, err := m.srv.KeyManagerWAN().InstallKey(args.Key) - ingestKeyringResponse(respWAN, reply, dc, true, err) - - args.Forwarded = true - m.srv.globalRPC("Internal.InstallKey", args, reply) + switch args.Operation { + case structs.KeyringList: + r, err = mgr.ListKeys() + case structs.KeyringInstall: + r, err = mgr.InstallKey(args.Key) + case structs.KeyringUse: + r, err = mgr.UseKey(args.Key) + case structs.KeyringRemove: + r, err = mgr.RemoveKey(args.Key) } - return nil -} - -// UseKey instructs all nodes to change the key they are using to -// encrypt gossip messages. -func (m *Internal) UseKey( - args *structs.KeyringRequest, - reply *structs.KeyringResponses) error { - - dc := m.srv.config.Datacenter - respLAN, err := m.srv.KeyManagerLAN().UseKey(args.Key) - ingestKeyringResponse(respLAN, reply, dc, false, err) - - if !args.Forwarded { - respWAN, err := m.srv.KeyManagerWAN().UseKey(args.Key) - ingestKeyringResponse(respWAN, reply, dc, true, err) - - args.Forwarded = true - m.srv.globalRPC("Internal.UseKey", args, reply) - } - - return nil -} - -// RemoveKey instructs all nodes to drop the specified key from the keyring. -func (m *Internal) RemoveKey( - args *structs.KeyringRequest, - reply *structs.KeyringResponses) error { - - dc := m.srv.config.Datacenter - respLAN, err := m.srv.KeyManagerLAN().RemoveKey(args.Key) - ingestKeyringResponse(respLAN, reply, dc, false, err) - - if !args.Forwarded { - respWAN, err := m.srv.KeyManagerWAN().RemoveKey(args.Key) - ingestKeyringResponse(respWAN, reply, dc, true, err) - - args.Forwarded = true - m.srv.globalRPC("Internal.RemoveKey", args, reply) - } - - return nil + return r, err } // ingestKeyringResponse is a helper method to pick the relative information diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 1b3ead9607..d655adf79a 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -554,9 +554,19 @@ type CompoundResponse interface { New() interface{} } +type KeyringOp string + +const ( + KeyringList KeyringOp = "list" + KeyringInstall = "install" + KeyringUse = "use" + KeyringRemove = "remove" +) + // 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 From 59a7938d1f3c972e76ddf9e645b226e8ccb7ffed Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 2 Oct 2014 17:14:52 -0700 Subject: [PATCH 59/80] agent: guard against empty keyring files --- command/agent/keyring.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/command/agent/keyring.go b/command/agent/keyring.go index f6d80a9dce..6df94d3c9b 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -50,6 +50,11 @@ func loadKeyringFile(c *serf.Config) error { keysDecoded[i] = keyBytes } + // Guard against empty keyring + if len(keysDecoded) == 0 { + return fmt.Errorf("no keys present in keyring file: %s", c.KeyringFile) + } + // Create the keyring keyring, err := memberlist.NewKeyring(keysDecoded, keysDecoded[0]) if err != nil { From 25845a39d3dbf470b192b49b22f01a6618f14b14 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 2 Oct 2014 18:03:05 -0700 Subject: [PATCH 60/80] consul: detach executeKeyringOp() from *Internal --- consul/internal_endpoint.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index f9bed288ca..50109f88d3 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -81,11 +81,11 @@ func (m *Internal) KeyringOperation( dc := m.srv.config.Datacenter - respLAN, err := m.doKeyringOperation(args, m.srv.KeyManagerLAN()) + respLAN, err := executeKeyringOp(args, m.srv.KeyManagerLAN()) ingestKeyringResponse(respLAN, reply, dc, false, err) if !args.Forwarded { - respWAN, err := m.doKeyringOperation(args, m.srv.KeyManagerWAN()) + respWAN, err := executeKeyringOp(args, m.srv.KeyManagerWAN()) ingestKeyringResponse(respWAN, reply, dc, true, err) args.Forwarded = true @@ -95,7 +95,10 @@ func (m *Internal) KeyringOperation( return nil } -func (m *Internal) doKeyringOperation( +// executeKeyringOp executes the appropriate keyring-related function based on +// the type of keyring operation in the request. It takes the KeyManager as an +// argument, so it can handle any operation for either LAN or WAN pools. +func executeKeyringOp( args *structs.KeyringRequest, mgr *serf.KeyManager) (r *serf.KeyResponse, err error) { From 1f9d13dc73e16331264326f92b97f177713afaff Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 2 Oct 2014 18:12:01 -0700 Subject: [PATCH 61/80] agent: squash some more common keyring semantics --- command/agent/keyring.go | 20 ++++++++------------ consul/internal_endpoint.go | 9 --------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/command/agent/keyring.go b/command/agent/keyring.go index 6df94d3c9b..2f1835d634 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -73,6 +73,10 @@ func (a *Agent) keyringProcess( method string, args *structs.KeyringRequest) (*structs.KeyringResponses, error) { + // Allow any server to handle the request, since this is + // done over the gossip protocol. + args.AllowStale = true + var reply structs.KeyringResponses if a.server == nil { return nil, fmt.Errorf("keyring operations must run against a server node") @@ -87,32 +91,24 @@ func (a *Agent) keyringProcess( // 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() (*structs.KeyringResponses, error) { - args := structs.KeyringRequest{} - args.AllowStale = true - args.Operation = structs.KeyringList + args := structs.KeyringRequest{Operation: structs.KeyringList} return a.keyringProcess("Internal.KeyringOperation", &args) } // InstallKey installs a new gossip encryption key func (a *Agent) InstallKey(key string) (*structs.KeyringResponses, error) { - args := structs.KeyringRequest{Key: key} - args.AllowStale = true - args.Operation = structs.KeyringInstall + args := structs.KeyringRequest{Key: key, Operation: structs.KeyringInstall} return a.keyringProcess("Internal.KeyringOperation", &args) } // UseKey changes the primary encryption key used to encrypt messages func (a *Agent) UseKey(key string) (*structs.KeyringResponses, error) { - args := structs.KeyringRequest{Key: key} - args.AllowStale = true - args.Operation = structs.KeyringUse + args := structs.KeyringRequest{Key: key, Operation: structs.KeyringUse} return a.keyringProcess("Internal.KeyringOperation", &args) } // RemoveKey will remove a gossip encryption key from the keyring func (a *Agent) RemoveKey(key string) (*structs.KeyringResponses, error) { - args := structs.KeyringRequest{Key: key} - args.AllowStale = true - args.Operation = structs.KeyringRemove + args := structs.KeyringRequest{Key: key, Operation: structs.KeyringRemove} return a.keyringProcess("Internal.KeyringOperation", &args) } diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 50109f88d3..055e450c71 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -12,15 +12,6 @@ type Internal struct { srv *Server } -type KeyringOperation uint8 - -const ( - listKeysOperation KeyringOperation = iota - installKeyOperation - useKeyOperation - removeKeyOperation -) - // ChecksInState is used to get all the checks in a given state func (m *Internal) NodeInfo(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeDump) error { From 7a74f559b962918234d0604998db986154201ab8 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 2 Oct 2014 22:05:00 -0700 Subject: [PATCH 62/80] command: remove -init argument from keyring, auto-persist keyrings when using agent -encrypt --- command/agent/agent.go | 13 ++-- command/agent/command.go | 55 +++++++++++---- command/agent/config.go | 19 ----- command/agent/keyring.go | 39 ++++++++++- command/keyring.go | 69 +------------------ .../docs/commands/keyring.html.markdown | 31 +++------ 6 files changed, 94 insertions(+), 132 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index f8b477147c..b6f3a299a7 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -264,17 +264,18 @@ func (a *Agent) setupServer() error { config := a.consulConfig() // Load a keyring file, if present - keyfileLAN := filepath.Join(config.DataDir, SerfLANKeyring) + keyfileLAN := filepath.Join(config.DataDir, serfLANKeyring) if _, err := os.Stat(keyfileLAN); err == nil { config.SerfLANConfig.KeyringFile = keyfileLAN } - keyfileWAN := filepath.Join(config.DataDir, SerfWANKeyring) - if _, err := os.Stat(keyfileWAN); err == nil { - config.SerfWANConfig.KeyringFile = keyfileWAN - } if err := loadKeyringFile(config.SerfLANConfig); err != nil { return err } + + keyfileWAN := filepath.Join(config.DataDir, serfWANKeyring) + if _, err := os.Stat(keyfileWAN); err == nil { + config.SerfWANConfig.KeyringFile = keyfileWAN + } if err := loadKeyringFile(config.SerfWANConfig); err != nil { return err } @@ -292,7 +293,7 @@ func (a *Agent) setupClient() error { config := a.consulConfig() // Load a keyring file, if present - keyfileLAN := filepath.Join(config.DataDir, SerfLANKeyring) + keyfileLAN := filepath.Join(config.DataDir, serfLANKeyring) if _, err := os.Stat(keyfileLAN); err == nil { config.SerfLANConfig.KeyringFile = keyfileLAN } diff --git a/command/agent/command.go b/command/agent/command.go index f15cea41fa..089f0a88ab 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -143,17 +143,35 @@ func (c *Command) readConfig() *Config { config.NodeName = hostname } + // Ensure we have a data directory + if config.DataDir == "" { + c.Ui.Error("Must specify data directory using -data-dir") + return nil + } + if config.EncryptKey != "" { if _, err := config.EncryptBytes(); err != nil { c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err)) return nil } - } - // Ensure we have a data directory - if config.DataDir == "" { - c.Ui.Error("Must specify data directory using -data-dir") - return nil + fileLAN := filepath.Join(config.DataDir, serfLANKeyring) + if _, err := os.Stat(fileLAN); err != nil { + initKeyring(fileLAN, config.EncryptKey) + } else { + c.Ui.Error(fmt.Sprintf("WARNING: %s exists, not using key: %s", + fileLAN, config.EncryptKey)) + } + + if config.Server { + fileWAN := filepath.Join(config.DataDir, serfWANKeyring) + if _, err := os.Stat(fileWAN); err != nil { + initKeyring(fileWAN, config.EncryptKey) + } else { + c.Ui.Error(fmt.Sprintf("WARNING: %s exists, not using key: %s", + fileWAN, config.EncryptKey)) + } + } } // Verify data center is valid @@ -218,12 +236,6 @@ func (c *Command) readConfig() *Config { c.Ui.Error("WARNING: Windows is not recommended as a Consul server. Do not use in production.") } - // Error if an encryption key is passed while a keyring already exists - if config.EncryptKey != "" && config.keyringFileExists() { - c.Ui.Error(fmt.Sprintf("Error: -encrypt specified but keyring files exist")) - return nil - } - // Set the version info config.Revision = c.Revision config.Version = c.Version @@ -465,6 +477,22 @@ func (c *Command) retryJoinWan(config *Config, errCh chan<- struct{}) { } } +// gossipEncrypted determines if the consul instance is using symmetric +// encryption keys to protect gossip protocol messages. +func (c *Command) gossipEncrypted() bool { + if c.agent.config.EncryptKey != "" { + return true + } + + server := c.agent.server + if server != nil { + return server.KeyManagerLAN() != nil || server.KeyManagerWAN() != nil + } + + client := c.agent.client + return client != nil && client.KeyManagerLAN() != nil +} + func (c *Command) Run(args []string) int { c.Ui = &cli.PrefixedUi{ OutputPrefix: "==> ", @@ -591,9 +619,6 @@ func (c *Command) Run(args []string) int { }(wp) } - // Determine if gossip is encrypted - gossipEncrypted := config.EncryptKey != "" || config.keyringFileExists() - // Let the agent know we've finished registration c.agent.StartSync() @@ -606,7 +631,7 @@ func (c *Command) Run(args []string) int { c.Ui.Info(fmt.Sprintf(" Cluster Addr: %v (LAN: %d, WAN: %d)", config.AdvertiseAddr, config.Ports.SerfLan, config.Ports.SerfWan)) c.Ui.Info(fmt.Sprintf("Gossip encrypt: %v, RPC-TLS: %v, TLS-Incoming: %v", - gossipEncrypted, config.VerifyOutgoing, config.VerifyIncoming)) + c.gossipEncrypted(), config.VerifyOutgoing, config.VerifyIncoming)) // Enable log streaming c.Ui.Info("") diff --git a/command/agent/config.go b/command/agent/config.go index 1188c7be9a..dce299eace 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -411,25 +411,6 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { return addr.String(), nil } -// keyringFileExists determines if there are encryption key files present -// in the data directory. On client nodes, this returns true if a LAN keyring -// is present. On server nodes, it returns true if either keyring file exists. -func (c *Config) keyringFileExists() bool { - fileLAN := filepath.Join(c.DataDir, SerfLANKeyring) - fileWAN := filepath.Join(c.DataDir, SerfWANKeyring) - - if _, err := os.Stat(fileLAN); err == nil { - return true - } - if !c.Server { - return false - } - if _, err := os.Stat(fileWAN); err == nil { - return true - } - return false -} - // DecodeConfig reads the configuration from the given reader in JSON // format and decodes it into a proper Config structure. func DecodeConfig(r io.Reader) (*Config, error) { diff --git a/command/agent/keyring.go b/command/agent/keyring.go index 2f1835d634..d4253b5e1c 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/memberlist" @@ -13,10 +14,44 @@ import ( ) const ( - SerfLANKeyring = "serf/local.keyring" - SerfWANKeyring = "serf/remote.keyring" + serfLANKeyring = "serf/local.keyring" + serfWANKeyring = "serf/remote.keyring" ) +// initKeyring will create a keyring file at a given path. +func initKeyring(path, key string) error { + if _, err := base64.StdEncoding.DecodeString(key); err != nil { + return fmt.Errorf("Invalid key: %s", err) + } + + keys := []string{key} + keyringBytes, err := json.Marshal(keys) + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return err + } + + if _, err := os.Stat(path); err == nil { + return fmt.Errorf("File already exists: %s", path) + } + + fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer fh.Close() + + if _, err := fh.Write(keyringBytes); err != nil { + os.Remove(path) + return err + } + + return nil +} + // loadKeyringFile will load a gossip encryption keyring out of a file. The file // must be in JSON format and contain a list of encryption key strings. func loadKeyringFile(c *serf.Config) error { diff --git a/command/keyring.go b/command/keyring.go index 78c56fd2b9..41c0b6ff76 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -1,12 +1,8 @@ package command import ( - "encoding/base64" - "encoding/json" "flag" "fmt" - "os" - "path/filepath" "strings" "github.com/hashicorp/consul/command/agent" @@ -20,7 +16,7 @@ type KeyringCommand struct { } func (c *KeyringCommand) Run(args []string) int { - var installKey, useKey, removeKey, init, dataDir string + var installKey, useKey, removeKey string var listKeys bool cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) @@ -30,8 +26,6 @@ func (c *KeyringCommand) Run(args []string) int { cmdFlags.StringVar(&useKey, "use", "", "use key") cmdFlags.StringVar(&removeKey, "remove", "", "remove key") cmdFlags.BoolVar(&listKeys, "list", false, "list keys") - cmdFlags.StringVar(&init, "init", "", "initialize keyring") - cmdFlags.StringVar(&dataDir, "data-dir", "", "data directory") rpcAddr := RPCAddrFlag(cmdFlags) if err := cmdFlags.Parse(args); err != nil { @@ -47,7 +41,7 @@ func (c *KeyringCommand) Run(args []string) int { // Only accept a single argument found := listKeys - for _, arg := range []string{installKey, useKey, removeKey, init} { + for _, arg := range []string{installKey, useKey, removeKey} { if found && len(arg) > 0 { c.Ui.Error("Only a single action is allowed") return 1 @@ -61,26 +55,6 @@ func (c *KeyringCommand) Run(args []string) int { return 1 } - if init != "" { - if dataDir == "" { - c.Ui.Error("Must provide -data-dir") - return 1 - } - - fileLAN := filepath.Join(dataDir, agent.SerfLANKeyring) - if err := initKeyring(fileLAN, init); err != nil { - c.Ui.Error(fmt.Sprintf("Error: %s", err)) - return 1 - } - fileWAN := filepath.Join(dataDir, agent.SerfWANKeyring) - if err := initKeyring(fileWAN, init); err != nil { - c.Ui.Error(fmt.Sprintf("Error: %s", err)) - return 1 - } - - return 0 - } - // All other operations will require a client connection client, err := RPCClient(*rpcAddr) if err != nil { @@ -204,40 +178,6 @@ func (c *KeyringCommand) handleList( } } -// initKeyring will create a keyring file at a given path. -func initKeyring(path, key string) error { - if _, err := base64.StdEncoding.DecodeString(key); err != nil { - return fmt.Errorf("Invalid key: %s", err) - } - - keys := []string{key} - keyringBytes, err := json.Marshal(keys) - if err != nil { - return err - } - - if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { - return err - } - - if _, err := os.Stat(path); err == nil { - return fmt.Errorf("File already exists: %s", path) - } - - fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer fh.Close() - - if _, err := fh.Write(keyringBytes); err != nil { - os.Remove(path) - return err - } - - return nil -} - func (c *KeyringCommand) Help() string { helpText := ` Usage: consul keyring [options] @@ -263,11 +203,6 @@ Options: operation may only be performed on keys which are not currently the primary key. -list List all keys currently in use within the cluster. - -init= Create the initial keyring files for Consul to use - containing the provided key. The -data-dir argument - is required with this option. - -data-dir= The path to the Consul agent's data directory. This - argument is only needed for keyring initialization. -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. ` return strings.TrimSpace(helpText) diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown index 6caa172dd7..3ecbaebc90 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -19,34 +19,23 @@ responsibility of the operator to ensure that only the required encryption keys are installed on the cluster. You can review the installed keys using the `-list` argument, and remove unneeded keys with `-remove`. -With the exception of the `-init` argument, all operations performed by this -command can only be run against server nodes, and affect both the LAN and -WAN keyrings in lock-step. +All operations performed by this command can only be run against server nodes, +and affect both the LAN and WAN keyrings in lock-step. -All variations of the `keyring` command, unless otherwise specified below, will -return 0 if all nodes reply and there are no errors. If any node fails to reply -or reports failure, the exit code will be 1. +All variations of the `keyring` command return 0 if all nodes reply and there +are no errors. If any node fails to reply or reports failure, the exit code +will be 1. ## Usage Usage: `consul keyring [options]` -Only one actionable argument may be specified per run, including `-init`, -`-list`, `-install`, `-remove`, and `-use`. +Only one actionable argument may be specified per run, including `-list`, +`-install`, `-remove`, and `-use`. The list of available flags are: -* `-init` - Creates the keyring file(s). This is useful to configure initial - encryption keyrings, which can later be mutated using the other arguments in - this command. This argument accepts an ASCII key, which can be generated using - the [keygen command](/docs/commands/keygen.html). Requires the `-data-dir` - argument. - - This operation can be run on both client and server nodes and requires no - network connectivity. - - Returns 0 if the key is successfully configured, or 1 if there were any - problems. +* `-list` - List all keys currently in use within the cluster. * `-install` - Install a new encryption key. This will broadcast the new key to all members in the cluster. @@ -57,10 +46,6 @@ The list of available flags are: * `-remove` - Remove the given key from the cluster. This operation may only be performed on keys which are not currently the primary key. -* `-list` - List all keys currently in use within the cluster. - -* `-data-dir` - The path to Consul's data directory. Used with `-init` only. - * `-rpc-addr` - RPC address of the Consul agent. ## Output From 22b2c633045efbe1a095660e1d0253c5420228f4 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 3 Oct 2014 19:20:58 -0700 Subject: [PATCH 63/80] command/agent: fix up gossip encryption indicator --- command/agent/command.go | 7 ++++++- consul/client.go | 5 +++++ consul/server.go | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/command/agent/command.go b/command/agent/command.go index 089f0a88ab..620389767e 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -619,6 +619,11 @@ func (c *Command) Run(args []string) int { }(wp) } + // Figure out if gossip is encrypted + gossipEncrypted := (config.Server && + c.agent.server.Encrypted() || + c.agent.client.Encrypted()) + // Let the agent know we've finished registration c.agent.StartSync() @@ -631,7 +636,7 @@ func (c *Command) Run(args []string) int { c.Ui.Info(fmt.Sprintf(" Cluster Addr: %v (LAN: %d, WAN: %d)", config.AdvertiseAddr, config.Ports.SerfLan, config.Ports.SerfWan)) c.Ui.Info(fmt.Sprintf("Gossip encrypt: %v, RPC-TLS: %v, TLS-Incoming: %v", - c.gossipEncrypted(), config.VerifyOutgoing, config.VerifyIncoming)) + gossipEncrypted, config.VerifyOutgoing, config.VerifyIncoming)) // Enable log streaming c.Ui.Info("") diff --git a/consul/client.go b/consul/client.go index be18541017..2c053513a3 100644 --- a/consul/client.go +++ b/consul/client.go @@ -211,6 +211,11 @@ func (c *Client) KeyManagerLAN() *serf.KeyManager { return c.serf.KeyManager() } +// Encrypted determines if gossip is encrypted +func (c *Client) Encrypted() bool { + return c.serf.EncryptionEnabled() +} + // lanEventHandler is used to handle events from the lan Serf cluster func (c *Client) lanEventHandler() { for { diff --git a/consul/server.go b/consul/server.go index 8f913ed464..cba7f11bad 100644 --- a/consul/server.go +++ b/consul/server.go @@ -561,6 +561,11 @@ func (s *Server) KeyManagerWAN() *serf.KeyManager { return s.serfWAN.KeyManager() } +// Encrypted determines if gossip is encrypted +func (s *Server) Encrypted() bool { + return s.serfLAN.EncryptionEnabled() && s.serfWAN.EncryptionEnabled() +} + // inmemCodec is used to do an RPC call without going over a network type inmemCodec struct { method string From bc2b2120fa95e95491ca869ed5a1cee2987ee6ba Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 4 Oct 2014 13:43:10 -0700 Subject: [PATCH 64/80] agent: -encrypt appends to keyring if one exists --- command/agent/agent_test.go | 4 +- command/agent/command.go | 16 +++---- command/agent/command_test.go | 4 ++ command/agent/config_test.go | 61 --------------------------- command/agent/keyring.go | 23 +++++++--- command/agent/keyring_test.go | 72 ++++++++++++++++++++++++++++++++ command/agent/rpc_client_test.go | 2 +- command/keyring_test.go | 4 +- consul/rpc.go | 3 ++ consul/serf_test.go | 24 ----------- consul/server_test.go | 42 ++++++++++++++++++- 11 files changed, 149 insertions(+), 106 deletions(-) diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 8240b4854d..10ee331b8c 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -80,11 +80,11 @@ func makeAgentKeyring(t *testing.T, conf *Config, key string) (string, *Agent) { conf.DataDir = dir - fileLAN := filepath.Join(dir, SerfLANKeyring) + fileLAN := filepath.Join(dir, serfLANKeyring) if err := testutil.InitKeyring(fileLAN, key); err != nil { t.Fatalf("err: %s", err) } - fileWAN := filepath.Join(dir, SerfWANKeyring) + fileWAN := filepath.Join(dir, serfWANKeyring) if err := testutil.InitKeyring(fileWAN, key); err != nil { t.Fatalf("err: %s", err) } diff --git a/command/agent/command.go b/command/agent/command.go index 620389767e..7bb9496189 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -156,20 +156,16 @@ func (c *Command) readConfig() *Config { } fileLAN := filepath.Join(config.DataDir, serfLANKeyring) - if _, err := os.Stat(fileLAN); err != nil { - initKeyring(fileLAN, config.EncryptKey) - } else { - c.Ui.Error(fmt.Sprintf("WARNING: %s exists, not using key: %s", - fileLAN, config.EncryptKey)) + if err := initKeyring(fileLAN, config.EncryptKey); err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing keyring: %s", err)) + return nil } if config.Server { fileWAN := filepath.Join(config.DataDir, serfWANKeyring) - if _, err := os.Stat(fileWAN); err != nil { - initKeyring(fileWAN, config.EncryptKey) - } else { - c.Ui.Error(fmt.Sprintf("WARNING: %s exists, not using key: %s", - fileWAN, config.EncryptKey)) + if err := initKeyring(fileWAN, config.EncryptKey); err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing keyring: %s", err)) + return nil } } } diff --git a/command/agent/command_test.go b/command/agent/command_test.go index 9c1bf4db5d..703d476ac9 100644 --- a/command/agent/command_test.go +++ b/command/agent/command_test.go @@ -1,12 +1,16 @@ package agent import ( +<<<<<<< HEAD "fmt" "io/ioutil" "log" "os" "path/filepath" "strings" +======= + "github.com/mitchellh/cli" +>>>>>>> agent: -encrypt appends to keyring if one exists "testing" "github.com/hashicorp/consul/testutil" diff --git a/command/agent/config_test.go b/command/agent/config_test.go index 52c0c58f76..13e8634ce4 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -1030,64 +1030,3 @@ func TestReadConfigPaths_dir(t *testing.T) { t.Fatalf("bad: %#v", config) } } - -func TestKeyringFileExists(t *testing.T) { - tempDir, err := ioutil.TempDir("", "consul") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.RemoveAll(tempDir) - - fileLAN := filepath.Join(tempDir, SerfLANKeyring) - fileWAN := filepath.Join(tempDir, SerfWANKeyring) - - if err := os.MkdirAll(filepath.Dir(fileLAN), 0700); err != nil { - t.Fatalf("err: %s", err) - } - if err := os.MkdirAll(filepath.Dir(fileWAN), 0700); err != nil { - t.Fatalf("err: %s", err) - } - - config := &Config{DataDir: tempDir, Server: true} - - // Returns false if we are a server and no keyring files present - if config.keyringFileExists() { - t.Fatalf("should return false") - } - - // Returns false if we are a client and no keyring files present - config.Server = false - if config.keyringFileExists() { - t.Fatalf("should return false") - } - - // Returns true if we are a client and the lan file exists - if err := ioutil.WriteFile(fileLAN, nil, 0600); err != nil { - t.Fatalf("err: %s", err) - } - if !config.keyringFileExists() { - t.Fatalf("should return true") - } - - // Returns true if we are a server and only the lan file exists - config.Server = true - if !config.keyringFileExists() { - t.Fatalf("should return true") - } - - // Returns true if we are a server and both files exist - if err := ioutil.WriteFile(fileWAN, nil, 0600); err != nil { - t.Fatalf("err: %s", err) - } - if !config.keyringFileExists() { - t.Fatalf("should return true") - } - - // Returns true if we are a server and only the wan file exists - if err := os.Remove(fileLAN); err != nil { - t.Fatalf("err: %s", err) - } - if !config.keyringFileExists() { - t.Fatalf("should return true") - } -} diff --git a/command/agent/keyring.go b/command/agent/keyring.go index d4253b5e1c..524968605d 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -20,11 +20,28 @@ const ( // initKeyring will create a keyring file at a given path. func initKeyring(path, key string) error { + var keys []string + if _, err := base64.StdEncoding.DecodeString(key); err != nil { return fmt.Errorf("Invalid key: %s", err) } - keys := []string{key} + if _, err := os.Stat(path); err == nil { + content, err := ioutil.ReadFile(path) + if err != nil { + return err + } + if err := json.Unmarshal(content, &keys); err != nil { + return err + } + for _, existing := range keys { + if key == existing { + return nil + } + } + } + + keys = append(keys, key) keyringBytes, err := json.Marshal(keys) if err != nil { return err @@ -34,10 +51,6 @@ func initKeyring(path, key string) error { return err } - if _, err := os.Stat(path); err == nil { - return fmt.Errorf("File already exists: %s", path) - } - fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { return err diff --git a/command/agent/keyring_test.go b/command/agent/keyring_test.go index 7807e53761..734a67dc14 100644 --- a/command/agent/keyring_test.go +++ b/command/agent/keyring_test.go @@ -1,7 +1,12 @@ package agent import ( + "bytes" + "encoding/json" + "io/ioutil" "os" + "path/filepath" + "strings" "testing" ) @@ -69,3 +74,70 @@ func TestAgent_LoadKeyrings(t *testing.T) { t.Fatalf("keyring should not be loaded") } } + +func TestAgent_InitKeyring(t *testing.T) { + key1 := "tbLJg26ZJyJ9pK3qhc9jig==" + key2 := "4leC33rgtXKIVUr9Nr0snQ==" + + dir, err := ioutil.TempDir("", "consul") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(dir) + + file := filepath.Join(dir, "keyring") + + // First initialize the keyring + if err := initKeyring(file, key1); err != nil { + t.Fatalf("err: %s", err) + } + + content1, err := ioutil.ReadFile(file) + if err != nil { + t.Fatalf("err: %s", err) + } + if !strings.Contains(string(content1), key1) { + t.Fatalf("bad: %s", content1) + } + if strings.Contains(string(content1), key2) { + t.Fatalf("bad: %s", content1) + } + + // Now initialize again with the same key + if err := initKeyring(file, key1); err != nil { + t.Fatalf("err: %s", err) + } + + content2, err := ioutil.ReadFile(file) + if err != nil { + t.Fatalf("err: %s", err) + } + if !bytes.Equal(content1, content2) { + t.Fatalf("bad: %s", content2) + } + + // Initialize an existing keyring with a new key + if err := initKeyring(file, key2); err != nil { + t.Fatalf("err: %s", err) + } + + content3, err := ioutil.ReadFile(file) + if err != nil { + t.Fatalf("err: %s", err) + } + if !strings.Contains(string(content3), key1) { + t.Fatalf("bad: %s", content3) + } + if !strings.Contains(string(content3), key2) { + t.Fatalf("bad: %s", content3) + } + + // Unmarshal and make sure that key1 is still primary + var keys []string + if err := json.Unmarshal(content3, &keys); err != nil { + t.Fatalf("err: %s", err) + } + if keys[0] != key1 { + t.Fatalf("bad: %#v", keys) + } +} diff --git a/command/agent/rpc_client_test.go b/command/agent/rpc_client_test.go index 518423a69a..2eed8f8a87 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -334,7 +334,7 @@ func TestRPCClientInstallKey(t *testing.T) { func TestRPCClientUseKey(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" key2 := "xAEZ3uVHRMZD9GcYMZaRQw==" - conf := Config{EncryptKey: key1} + conf := Config{EncryptKey: key1, Server: true} p1 := testRPCClientWithConfig(t, &conf) defer p1.Close() diff --git a/command/keyring_test.go b/command/keyring_test.go index 7e975b6ccc..cc75d9797e 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -129,8 +129,8 @@ func TestKeyringCommandRun_initKeyring(t *testing.T) { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } - fileLAN := filepath.Join(tempDir, agent.SerfLANKeyring) - fileWAN := filepath.Join(tempDir, agent.SerfWANKeyring) + fileLAN := filepath.Join(tempDir, agent.serfLANKeyring) + fileWAN := filepath.Join(tempDir, agent.serfWANKeyring) if _, err := os.Stat(fileLAN); err != nil { t.Fatalf("err: %s", err) } diff --git a/consul/rpc.go b/consul/rpc.go index 6fcb07d820..f400b2be95 100644 --- a/consul/rpc.go +++ b/consul/rpc.go @@ -229,6 +229,9 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{ func (s *Server) globalRPC(method string, args interface{}, reply structs.CompoundResponse) error { + if reply == nil { + return fmt.Errorf("nil reply struct") + } rlen := len(s.remoteConsuls) if rlen < 2 { return nil diff --git a/consul/serf_test.go b/consul/serf_test.go index b30b4fe821..07225a7293 100644 --- a/consul/serf_test.go +++ b/consul/serf_test.go @@ -1,8 +1,6 @@ package consul import ( - "fmt" - "os" "testing" ) @@ -21,25 +19,3 @@ func TestUserEventNames(t *testing.T) { t.Fatalf("bad: %v", raw) } } - -func TestKeyringRPCError(t *testing.T) { - dir1, s1 := testServerDC(t, "dc1") - defer os.RemoveAll(dir1) - defer s1.Shutdown() - - dir2, s2 := testServerDC(t, "dc2") - defer os.RemoveAll(dir2) - defer s2.Shutdown() - - // Try to join - addr := fmt.Sprintf("127.0.0.1:%d", - s1.config.SerfWANConfig.MemberlistConfig.BindPort) - if _, err := s2.JoinWAN([]string{addr}); err != nil { - t.Fatalf("err: %v", err) - } - - // RPC error from remote datacenter is returned - if err := s1.keyringRPC("Bad.Method", nil, nil); err == nil { - t.Fatalf("bad") - } -} diff --git a/consul/server_test.go b/consul/server_test.go index 76b7d4ed44..50627837e6 100644 --- a/consul/server_test.go +++ b/consul/server_test.go @@ -6,9 +6,11 @@ import ( "io/ioutil" "net" "os" + "strings" "testing" "time" + "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/testutil" ) @@ -471,5 +473,43 @@ func TestServer_BadExpect(t *testing.T) { }, func(err error) { t.Fatalf("should have 0 peers: %v", err) }) - +} + +func TestServer_globalRPC(t *testing.T) { + dir1, s1 := testServerDC(t, "dc1") + defer os.RemoveAll(dir1) + defer s1.Shutdown() + + dir2, s2 := testServerDC(t, "dc2") + defer os.RemoveAll(dir2) + defer s2.Shutdown() + + // Try to join + addr := fmt.Sprintf("127.0.0.1:%d", + s1.config.SerfLANConfig.MemberlistConfig.BindPort) + if _, err := s2.JoinLAN([]string{addr}); err != nil { + t.Fatalf("err: %v", err) + } + + testutil.WaitForLeader(t, s1.RPC, "dc1") + + // Check that replies from each DC come in + resp := &structs.KeyringResponses{} + args := &structs.KeyringRequest{Operation: structs.KeyringList} + if err := s1.globalRPC("Internal.KeyringOperation", args, resp); err != nil { + t.Fatalf("err: %s", err) + } + if len(resp.Responses) != 3 { + t.Fatalf("bad: %#v", resp.Responses) + } + + // Check that error from remote DC is returned + resp = &structs.KeyringResponses{} + err := s1.globalRPC("Bad.Method", nil, resp) + if err == nil { + t.Fatalf("should have errored") + } + if !strings.Contains(err.Error(), "Bad.Method") { + t.Fatalf("unexpcted error: %s", err) + } } From 80c25e33201f54b9682b039c6290f423705f59ea Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 4 Oct 2014 14:10:57 -0700 Subject: [PATCH 65/80] website: document new behavior of the -encrypt option --- website/source/docs/agent/options.html.markdown | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown index 01149ef590..9046b03d1d 100644 --- a/website/source/docs/agent/options.html.markdown +++ b/website/source/docs/agent/options.html.markdown @@ -89,6 +89,13 @@ The options below are all specified on the command-line. network traffic. This key must be 16-bytes that are base64 encoded. The easiest way to create an encryption key is to use `consul keygen`. All nodes within a cluster must share the same encryption key to communicate. + The provided key is automatically persisted to the data directory, and loaded + automatically whenever the agent is restarted. This means that to encrypt + Consul's gossip protocol, this option only needs to be provided once on each + agent's initial startup sequence. If it is provided after Consul has been + initialized with an encryption key, then the provided key is simply added + as a secondary encryption key. More information on how keys can be changed + is available on the [keyring command](/docs/commands/keyring.html) page. * `-join` - Address of another agent to join upon starting up. This can be specified multiple times to specify multiple agents to join. If Consul is From c945311baf2bf4ae4c54d6bf7fa3a0aaef39c84d Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 4 Oct 2014 15:35:18 -0700 Subject: [PATCH 66/80] agent: make rpc tests more reliable --- command/agent/rpc_client_test.go | 65 ++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/command/agent/rpc_client_test.go b/command/agent/rpc_client_test.go index 2eed8f8a87..ef082838ae 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -293,7 +293,7 @@ func TestRPCClientListKeys(t *testing.T) { if _, ok := keys["dc1"][key1]; !ok { t.Fatalf("bad: %#v", keys) } - if _, ok := keys["wan"][key1]; !ok { + if _, ok := keys["WAN"][key1]; !ok { t.Fatalf("bad: %#v", keys) } } @@ -301,18 +301,23 @@ func TestRPCClientListKeys(t *testing.T) { func TestRPCClientInstallKey(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" key2 := "xAEZ3uVHRMZD9GcYMZaRQw==" - conf := Config{EncryptKey: key1, Server: true} + conf := Config{EncryptKey: key1} p1 := testRPCClientWithConfig(t, &conf) defer p1.Close() // key2 is not installed yet - keys := listKeys(t, p1.client) - if num, ok := keys["dc1"][key2]; ok || num != 0 { - t.Fatalf("bad: %#v", keys) - } - if num, ok := keys["wan"][key2]; ok || num != 0 { - t.Fatalf("bad: %#v", keys) - } + testutil.WaitForResult(func() (bool, error) { + keys := listKeys(t, p1.client) + if num, ok := keys["dc1"][key2]; ok || num != 0 { + return false, fmt.Errorf("bad: %#v", keys) + } + if num, ok := keys["WAN"][key2]; ok || num != 0 { + return false, fmt.Errorf("bad: %#v", keys) + } + return true, nil + }, func(err error) { + t.Fatal(err.Error()) + }) // install key2 r, err := p1.client.InstallKey(key2) @@ -322,19 +327,24 @@ func TestRPCClientInstallKey(t *testing.T) { keyringSuccess(t, r) // key2 should now be installed - keys = listKeys(t, p1.client) - if num, ok := keys["dc1"][key2]; !ok || num != 1 { - t.Fatalf("bad: %#v", keys) - } - if num, ok := keys["wan"][key2]; !ok || num != 1 { - t.Fatalf("bad: %#v", keys) - } + testutil.WaitForResult(func() (bool, error) { + keys := listKeys(t, p1.client) + if num, ok := keys["dc1"][key2]; !ok || num != 1 { + return false, fmt.Errorf("bad: %#v", keys) + } + if num, ok := keys["WAN"][key2]; !ok || num != 1 { + return false, fmt.Errorf("bad: %#v", keys) + } + return true, nil + }, func(err error) { + t.Fatal(err.Error()) + }) } func TestRPCClientUseKey(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" key2 := "xAEZ3uVHRMZD9GcYMZaRQw==" - conf := Config{EncryptKey: key1, Server: true} + conf := Config{EncryptKey: key1} p1 := testRPCClientWithConfig(t, &conf) defer p1.Close() @@ -346,13 +356,18 @@ func TestRPCClientUseKey(t *testing.T) { keyringSuccess(t, r) // key2 is installed - keys := listKeys(t, p1.client) - if num, ok := keys["dc1"][key2]; !ok || num != 1 { - t.Fatalf("bad: %#v", keys) - } - if num, ok := keys["wan"][key2]; !ok || num != 1 { - t.Fatalf("bad: %#v", keys) - } + testutil.WaitForResult(func() (bool, error) { + keys := listKeys(t, p1.client) + if num, ok := keys["dc1"][key2]; !ok || num != 1 { + return false, fmt.Errorf("bad: %#v", keys) + } + if num, ok := keys["WAN"][key2]; !ok || num != 1 { + return false, fmt.Errorf("bad: %#v", keys) + } + return true, nil + }, func(err error) { + t.Fatal(err.Error()) + }) // can't remove key1 yet r, err = p1.client.RemoveKey(key1) @@ -396,7 +411,7 @@ func listKeys(t *testing.T, c *RPCClient) map[string]map[string]int { for _, k := range resp.Keys { respID := k.Datacenter if k.Pool == "WAN" { - respID = "wan" + respID = k.Pool } out[respID] = map[string]int{k.Key: k.Count} } From 0b8fe6cc83a44d485d57c21cd734f461baaa8435 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 4 Oct 2014 15:45:26 -0700 Subject: [PATCH 67/80] agent: fix test cases --- command/agent/agent_test.go | 4 +-- command/keyring_test.go | 50 ------------------------------------- testutil/keyring.go | 43 ------------------------------- testutil/keyring_test.go | 42 ------------------------------- 4 files changed, 2 insertions(+), 137 deletions(-) delete mode 100644 testutil/keyring.go delete mode 100644 testutil/keyring_test.go diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 10ee331b8c..1e4aec000c 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -81,11 +81,11 @@ func makeAgentKeyring(t *testing.T, conf *Config, key string) (string, *Agent) { conf.DataDir = dir fileLAN := filepath.Join(dir, serfLANKeyring) - if err := testutil.InitKeyring(fileLAN, key); err != nil { + if err := initKeyring(fileLAN, key); err != nil { t.Fatalf("err: %s", err) } fileWAN := filepath.Join(dir, serfWANKeyring) - if err := testutil.InitKeyring(fileWAN, key); err != nil { + if err := initKeyring(fileWAN, key); err != nil { t.Fatalf("err: %s", err) } diff --git a/command/keyring_test.go b/command/keyring_test.go index cc75d9797e..7f2d4c0748 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -1,9 +1,6 @@ package command import ( - "io/ioutil" - "os" - "path/filepath" "strings" "testing" @@ -110,53 +107,6 @@ func TestKeyringCommandRun_initKeyringFail(t *testing.T) { } } -func TestKeyringCommandRun_initKeyring(t *testing.T) { - ui := new(cli.MockUi) - c := &KeyringCommand{Ui: ui} - - tempDir, err := ioutil.TempDir("", "consul") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.RemoveAll(tempDir) - - args := []string{ - "-init=HS5lJ+XuTlYKWaeGYyG+/A==", - "-data-dir=" + tempDir, - } - code := c.Run(args) - if code != 0 { - t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) - } - - fileLAN := filepath.Join(tempDir, agent.serfLANKeyring) - fileWAN := filepath.Join(tempDir, agent.serfWANKeyring) - if _, err := os.Stat(fileLAN); err != nil { - t.Fatalf("err: %s", err) - } - if _, err := os.Stat(fileWAN); err != nil { - t.Fatalf("err: %s", err) - } - - expected := `["HS5lJ+XuTlYKWaeGYyG+/A=="]` - - contentLAN, err := ioutil.ReadFile(fileLAN) - if err != nil { - t.Fatalf("err: %s", err) - } - if string(contentLAN) != expected { - t.Fatalf("bad: %#v", string(contentLAN)) - } - - contentWAN, err := ioutil.ReadFile(fileWAN) - if err != nil { - t.Fatalf("err: %s", err) - } - if string(contentWAN) != expected { - t.Fatalf("bad: %#v", string(contentWAN)) - } -} - func listKeys(t *testing.T, addr string) string { ui := new(cli.MockUi) c := &KeyringCommand{Ui: ui} diff --git a/testutil/keyring.go b/testutil/keyring.go deleted file mode 100644 index 60486bbb88..0000000000 --- a/testutil/keyring.go +++ /dev/null @@ -1,43 +0,0 @@ -package testutil - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "os" - "path/filepath" -) - -// InitKeyring will create a keyring file at a given path. -func InitKeyring(path, key string) error { - if _, err := base64.StdEncoding.DecodeString(key); err != nil { - return fmt.Errorf("Invalid key: %s", err) - } - - keys := []string{key} - keyringBytes, err := json.Marshal(keys) - if err != nil { - return err - } - - if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { - return err - } - - if _, err := os.Stat(path); err == nil { - return fmt.Errorf("File already exists: %s", path) - } - - fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer fh.Close() - - if _, err := fh.Write(keyringBytes); err != nil { - os.Remove(path) - return err - } - - return nil -} diff --git a/testutil/keyring_test.go b/testutil/keyring_test.go deleted file mode 100644 index 7e5b3e63fe..0000000000 --- a/testutil/keyring_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package testutil - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" -) - -func TestAgent_InitKeyring(t *testing.T) { - key := "tbLJg26ZJyJ9pK3qhc9jig==" - - dir, err := ioutil.TempDir("", "agent") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.RemoveAll(dir) - keyFile := filepath.Join(dir, "test/keyring") - - if err := InitKeyring(keyFile, key); err != nil { - t.Fatalf("err: %s", err) - } - - fi, err := os.Stat(filepath.Dir(keyFile)) - if err != nil { - t.Fatalf("err: %s", err) - } - - if !fi.IsDir() { - t.Fatalf("bad: %#v", fi) - } - - data, err := ioutil.ReadFile(keyFile) - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := `["tbLJg26ZJyJ9pK3qhc9jig=="]` - if string(data) != expected { - t.Fatalf("bad: %#v", string(data)) - } -} From e60c90927620b0e0542e5f141c510150f3382b96 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 5 Oct 2014 13:15:59 -0700 Subject: [PATCH 68/80] command/keyring: clean up tests --- command/keyring_test.go | 19 ------------------- consul/internal_endpoint.go | 5 +++-- consul/rpc.go | 2 +- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/command/keyring_test.go b/command/keyring_test.go index 7f2d4c0748..25e20599cc 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -88,25 +88,6 @@ func TestKeyringCommandRun_failedConnection(t *testing.T) { } } -func TestKeyringCommandRun_initKeyringFail(t *testing.T) { - ui := new(cli.MockUi) - c := &KeyringCommand{Ui: ui} - - // Should error if no data-dir given - args := []string{"-init=HS5lJ+XuTlYKWaeGYyG+/A=="} - code := c.Run(args) - if code != 1 { - t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) - } - - // Errors on invalid key - args = []string{"-init=xyz", "-data-dir=/tmp"} - code = c.Run(args) - if code != 1 { - t.Fatalf("should have errored") - } -} - 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 055e450c71..fe3c68162f 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -64,8 +64,9 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, return m.srv.UserEvent(args.Name, args.Payload) } -// ListKeys will query the WAN and LAN gossip keyrings of all nodes, adding -// results into a collective response as we go. +// KeyringOperation will query the WAN and LAN gossip keyrings of all nodes, +// adding results into a collective response as we go. It can describe requests +// for all keyring-related operations. func (m *Internal) KeyringOperation( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { diff --git a/consul/rpc.go b/consul/rpc.go index f400b2be95..fbb73ce7b3 100644 --- a/consul/rpc.go +++ b/consul/rpc.go @@ -225,7 +225,7 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{ // globalRPC is used to forward an RPC request to one server in each datacenter. // This will only error for RPC-related errors. Otherwise, application-level -// errors are returned inside of the inner response objects. +// errors can be sent in the response objects. func (s *Server) globalRPC(method string, args interface{}, reply structs.CompoundResponse) error { From 1398538ae268fccfd90b5d5c9ca6c5afb2c5ed1e Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 5 Oct 2014 13:43:56 -0700 Subject: [PATCH 69/80] command/keyring: adjust command help --- command/keyring.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/command/keyring.go b/command/keyring.go index 41c0b6ff76..ee072b8792 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -188,9 +188,12 @@ Usage: consul keyring [options] functionality provides the ability to perform key rotation cluster-wide, without disrupting the cluster. - With the exception of the -init argument, all operations performed by this - command can only be run against server nodes, and affect both the LAN and - WAN keyrings in lock-step. + All operations performed by this command can only be run against server nodes, + and affect both the LAN and WAN keyrings in lock-step. + + All variations of the keyring command return 0 if all nodes reply and there + are no errors. If any node fails to reply or reports failure, the exit code + will be 1. Options: From a79176d314e75a6631084c4b29d8d8eb15edb0ce Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 5 Oct 2014 13:59:27 -0700 Subject: [PATCH 70/80] consul: simplify keyring operations --- consul/internal_endpoint.go | 43 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index fe3c68162f..967d10706b 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -71,15 +71,10 @@ func (m *Internal) KeyringOperation( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { - dc := m.srv.config.Datacenter - - respLAN, err := executeKeyringOp(args, m.srv.KeyManagerLAN()) - ingestKeyringResponse(respLAN, reply, dc, false, err) + m.executeKeyringOp(args, reply, false) if !args.Forwarded { - respWAN, err := executeKeyringOp(args, m.srv.KeyManagerWAN()) - ingestKeyringResponse(respWAN, reply, dc, true, err) - + m.executeKeyringOp(args, reply, true) args.Forwarded = true return m.srv.globalRPC("Internal.KeyringOperation", args, reply) } @@ -90,30 +85,34 @@ func (m *Internal) KeyringOperation( // executeKeyringOp executes the appropriate keyring-related function based on // the type of keyring operation in the request. It takes the KeyManager as an // argument, so it can handle any operation for either LAN or WAN pools. -func executeKeyringOp( +func (m *Internal) executeKeyringOp( args *structs.KeyringRequest, - mgr *serf.KeyManager) (r *serf.KeyResponse, err error) { + reply *structs.KeyringResponses, + wan bool) { + + var serfResp *serf.KeyResponse + var err error + + dc := m.srv.config.Datacenter + + var mgr *serf.KeyManager + if wan { + mgr = m.srv.KeyManagerWAN() + } else { + mgr = m.srv.KeyManagerLAN() + } switch args.Operation { case structs.KeyringList: - r, err = mgr.ListKeys() + serfResp, err = mgr.ListKeys() case structs.KeyringInstall: - r, err = mgr.InstallKey(args.Key) + serfResp, err = mgr.InstallKey(args.Key) case structs.KeyringUse: - r, err = mgr.UseKey(args.Key) + serfResp, err = mgr.UseKey(args.Key) case structs.KeyringRemove: - r, err = mgr.RemoveKey(args.Key) + serfResp, err = mgr.RemoveKey(args.Key) } - return r, err -} - -// ingestKeyringResponse is a helper method to pick the relative information -// from a Serf message and stuff it into a KeyringResponse. -func ingestKeyringResponse( - serfResp *serf.KeyResponse, reply *structs.KeyringResponses, - dc string, wan bool, err error) { - errStr := "" if err != nil { errStr = err.Error() From ee03c6a4b857f7141fdd06617623774401cd86d7 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 5 Oct 2014 18:33:12 -0700 Subject: [PATCH 71/80] consul: add test for internal keyring rpc endpoint --- consul/internal_endpoint_test.go | 88 ++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/consul/internal_endpoint_test.go b/consul/internal_endpoint_test.go index e3c33fe925..45b57f21ca 100644 --- a/consul/internal_endpoint_test.go +++ b/consul/internal_endpoint_test.go @@ -1,6 +1,8 @@ package consul import ( + "encoding/base64" + "fmt" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/testutil" "os" @@ -150,3 +152,89 @@ func TestInternal_NodeDump(t *testing.T) { t.Fatalf("missing foo or bar") } } + +func TestInternal_KeyringOperation(t *testing.T) { + key1 := "H1dfkSZOVnP/JUnaBfTzXg==" + keyBytes1, err := base64.StdEncoding.DecodeString(key1) + if err != nil { + t.Fatalf("err: %s", err) + } + dir1, s1 := testServerWithConfig(t, func(c *Config) { + c.SerfLANConfig.MemberlistConfig.SecretKey = keyBytes1 + c.SerfWANConfig.MemberlistConfig.SecretKey = keyBytes1 + }) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + client := rpcClient(t, s1) + defer client.Close() + + testutil.WaitForLeader(t, client.Call, "dc1") + + var out structs.KeyringResponses + req := structs.KeyringRequest{ + Operation: structs.KeyringList, + Datacenter: "dc1", + } + if err := client.Call("Internal.KeyringOperation", &req, &out); err != nil { + t.Fatalf("err: %v", err) + } + + // Two responses (local lan/wan pools) from single-node cluster + if len(out.Responses) != 2 { + t.Fatalf("bad: %#v", out) + } + if _, ok := out.Responses[0].Keys[key1]; !ok { + t.Fatalf("bad: %#v", out) + } + wanResp, lanResp := 0, 0 + for _, resp := range out.Responses { + if resp.WAN { + wanResp++ + } else { + lanResp++ + } + } + if lanResp != 1 || wanResp != 1 { + t.Fatalf("should have one lan and one wan response") + } + + // Start a second agent to test cross-dc queries + dir2, s2 := testServerWithConfig(t, func(c *Config) { + c.SerfLANConfig.MemberlistConfig.SecretKey = keyBytes1 + c.SerfWANConfig.MemberlistConfig.SecretKey = keyBytes1 + c.Datacenter = "dc2" + }) + defer os.RemoveAll(dir2) + defer s2.Shutdown() + + // Try to join + addr := fmt.Sprintf("127.0.0.1:%d", + s1.config.SerfWANConfig.MemberlistConfig.BindPort) + if _, err := s2.JoinWAN([]string{addr}); err != nil { + t.Fatalf("err: %v", err) + } + + var out2 structs.KeyringResponses + req2 := structs.KeyringRequest{ + Operation: structs.KeyringList, + } + if err := client.Call("Internal.KeyringOperation", &req2, &out2); err != nil { + t.Fatalf("err: %v", err) + } + + // 3 responses (one from each DC LAN, one from WAN) in two-node cluster + if len(out2.Responses) != 3 { + t.Fatalf("bad: %#v", out) + } + wanResp, lanResp = 0, 0 + for _, resp := range out2.Responses { + if resp.WAN { + wanResp++ + } else { + lanResp++ + } + } + if lanResp != 2 || wanResp != 1 { + t.Fatalf("should have two lan and one wan response") + } +} From 648c7cdc8ee9388da67b1a98a6108fdb3abfc5a6 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Mon, 6 Oct 2014 15:14:30 -0700 Subject: [PATCH 72/80] consul: simplify keyring operations --- command/agent/keyring.go | 15 ++++++--------- consul/internal_endpoint.go | 9 +++------ consul/rpc.go | 16 +++++----------- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/command/agent/keyring.go b/command/agent/keyring.go index 524968605d..66ca7e2239 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -117,10 +117,7 @@ func loadKeyringFile(c *serf.Config) error { // keyringProcess is used to abstract away the semantic similarities in // performing various operations on the encryption keyring. -func (a *Agent) keyringProcess( - method string, - args *structs.KeyringRequest) (*structs.KeyringResponses, error) { - +func (a *Agent) keyringProcess(args *structs.KeyringRequest) (*structs.KeyringResponses, error) { // Allow any server to handle the request, since this is // done over the gossip protocol. args.AllowStale = true @@ -129,7 +126,7 @@ func (a *Agent) keyringProcess( if a.server == nil { return nil, fmt.Errorf("keyring operations must run against a server node") } - if err := a.RPC(method, args, &reply); err != nil { + if err := a.RPC("Internal.KeyringOperation", args, &reply); err != nil { return &reply, err } @@ -140,23 +137,23 @@ func (a *Agent) keyringProcess( // includes both servers and clients in all DC's. func (a *Agent) ListKeys() (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Operation: structs.KeyringList} - return a.keyringProcess("Internal.KeyringOperation", &args) + return a.keyringProcess(&args) } // InstallKey installs a new gossip encryption key func (a *Agent) InstallKey(key string) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key, Operation: structs.KeyringInstall} - return a.keyringProcess("Internal.KeyringOperation", &args) + return a.keyringProcess(&args) } // UseKey changes the primary encryption key used to encrypt messages func (a *Agent) UseKey(key string) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key, Operation: structs.KeyringUse} - return a.keyringProcess("Internal.KeyringOperation", &args) + return a.keyringProcess(&args) } // RemoveKey will remove a gossip encryption key from the keyring func (a *Agent) RemoveKey(key string) (*structs.KeyringResponses, error) { args := structs.KeyringRequest{Key: key, Operation: structs.KeyringRemove} - return a.keyringProcess("Internal.KeyringOperation", &args) + return a.keyringProcess(&args) } diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 967d10706b..e20e315da0 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -72,10 +72,9 @@ func (m *Internal) KeyringOperation( reply *structs.KeyringResponses) error { m.executeKeyringOp(args, reply, false) - if !args.Forwarded { - m.executeKeyringOp(args, reply, true) args.Forwarded = true + m.executeKeyringOp(args, reply, true) return m.srv.globalRPC("Internal.KeyringOperation", args, reply) } @@ -92,10 +91,8 @@ func (m *Internal) executeKeyringOp( var serfResp *serf.KeyResponse var err error - - dc := m.srv.config.Datacenter - var mgr *serf.KeyManager + if wan { mgr = m.srv.KeyManagerWAN() } else { @@ -120,7 +117,7 @@ func (m *Internal) executeKeyringOp( reply.Responses = append(reply.Responses, &structs.KeyringResponse{ WAN: wan, - Datacenter: dc, + Datacenter: m.srv.config.Datacenter, Messages: serfResp.Messages, Keys: serfResp.Keys, NumNodes: serfResp.NumNodes, diff --git a/consul/rpc.go b/consul/rpc.go index fbb73ce7b3..8fca85ff45 100644 --- a/consul/rpc.go +++ b/consul/rpc.go @@ -229,11 +229,8 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{ func (s *Server) globalRPC(method string, args interface{}, reply structs.CompoundResponse) error { - if reply == nil { - return fmt.Errorf("nil reply struct") - } - rlen := len(s.remoteConsuls) - if rlen < 2 { + totalDC := len(s.remoteConsuls) + if totalDC == 1 { return nil } @@ -253,17 +250,14 @@ func (s *Server) globalRPC(method string, args interface{}, }() } - done := 0 - for { + replies := 0 + for replies < totalDC { select { case err := <-errorCh: return err case rr := <-respCh: reply.Add(rr) - done++ - } - if done == rlen { - break + replies++ } } return nil From 0cafb129ee784702756ef19729fb32c9fa3908f7 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Tue, 7 Oct 2014 11:05:31 -0700 Subject: [PATCH 73/80] consul: more tests, remove unused KeyManager() method --- command/agent/rpc_client_test.go | 24 +++++++++++--------- command/keyring_test.go | 5 +++-- command/util_test.go | 8 +++---- consul/client.go | 5 ----- consul/client_test.go | 20 +++++++++++++++++ consul/server_test.go | 38 ++++++++++++++++++++++++++++---- consul/structs/structs_test.go | 21 ++++++++++++++++++ 7 files changed, 94 insertions(+), 27 deletions(-) diff --git a/command/agent/rpc_client_test.go b/command/agent/rpc_client_test.go index ef082838ae..3bf03d6dc6 100644 --- a/command/agent/rpc_client_test.go +++ b/command/agent/rpc_client_test.go @@ -30,10 +30,10 @@ func (r *rpcParts) Close() { // testRPCClient returns an RPCClient connected to an RPC server that // serves only this connection. func testRPCClient(t *testing.T) *rpcParts { - return testRPCClientWithConfig(t, nil) + return testRPCClientWithConfig(t, func(c *Config) {}) } -func testRPCClientWithConfig(t *testing.T, c *Config) *rpcParts { +func testRPCClientWithConfig(t *testing.T, cb func(c *Config)) *rpcParts { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("err: %s", err) @@ -43,9 +43,7 @@ func testRPCClientWithConfig(t *testing.T, c *Config) *rpcParts { mult := io.MultiWriter(os.Stderr, lw) conf := nextConfig() - if c != nil { - conf = MergeConfig(conf, c) - } + cb(conf) dir, agent := makeAgentLog(t, conf, mult) rpc := NewAgentRPC(agent, l, mult, lw) @@ -284,8 +282,10 @@ OUTER2: func TestRPCClientListKeys(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" - conf := Config{EncryptKey: key1, Datacenter: "dc1"} - p1 := testRPCClientWithConfig(t, &conf) + p1 := testRPCClientWithConfig(t, func(c *Config) { + c.EncryptKey = key1 + c.Datacenter = "dc1" + }) defer p1.Close() // Key is initially installed to both wan/lan @@ -301,8 +301,9 @@ func TestRPCClientListKeys(t *testing.T) { func TestRPCClientInstallKey(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" key2 := "xAEZ3uVHRMZD9GcYMZaRQw==" - conf := Config{EncryptKey: key1} - p1 := testRPCClientWithConfig(t, &conf) + p1 := testRPCClientWithConfig(t, func(c *Config) { + c.EncryptKey = key1 + }) defer p1.Close() // key2 is not installed yet @@ -344,8 +345,9 @@ func TestRPCClientInstallKey(t *testing.T) { func TestRPCClientUseKey(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" key2 := "xAEZ3uVHRMZD9GcYMZaRQw==" - conf := Config{EncryptKey: key1} - p1 := testRPCClientWithConfig(t, &conf) + p1 := testRPCClientWithConfig(t, func(c *Config) { + c.EncryptKey = key1 + }) defer p1.Close() // add a second key to the ring diff --git a/command/keyring_test.go b/command/keyring_test.go index 25e20599cc..bb8691ebb4 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -17,8 +17,9 @@ func TestKeyringCommandRun(t *testing.T) { key2 := "kZyFABeAmc64UMTrm9XuKA==" // Begin with a single key - conf := agent.Config{EncryptKey: key1} - a1 := testAgentWithConfig(&conf, t) + a1 := testAgentWithConfig(t, func(c *agent.Config) { + c.EncryptKey = key1 + }) defer a1.Shutdown() // The LAN and WAN keyrings were initialized with key1 diff --git a/command/util_test.go b/command/util_test.go index 586489233e..a48f33cb0c 100644 --- a/command/util_test.go +++ b/command/util_test.go @@ -39,10 +39,10 @@ func (a *agentWrapper) Shutdown() { } func testAgent(t *testing.T) *agentWrapper { - return testAgentWithConfig(nil, t) + return testAgentWithConfig(t, func(c *agent.Config) {}) } -func testAgentWithConfig(c *agent.Config, t *testing.T) *agentWrapper { +func testAgentWithConfig(t *testing.T, cb func(c *agent.Config)) *agentWrapper { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("err: %s", err) @@ -52,9 +52,7 @@ func testAgentWithConfig(c *agent.Config, t *testing.T) *agentWrapper { mult := io.MultiWriter(os.Stderr, lw) conf := nextConfig() - if c != nil { - conf = agent.MergeConfig(c, conf) - } + cb(conf) dir, err := ioutil.TempDir("", "agent") if err != nil { diff --git a/consul/client.go b/consul/client.go index 2c053513a3..cf0ddca0cf 100644 --- a/consul/client.go +++ b/consul/client.go @@ -206,11 +206,6 @@ func (c *Client) UserEvent(name string, payload []byte) error { return c.serf.UserEvent(userEventName(name), payload, false) } -// KeyManager returns the LAN Serf keyring manager -func (c *Client) KeyManagerLAN() *serf.KeyManager { - return c.serf.KeyManager() -} - // Encrypted determines if gossip is encrypted func (c *Client) Encrypted() bool { return c.serf.EncryptionEnabled() diff --git a/consul/client_test.go b/consul/client_test.go index a783c3a52c..33425cdf70 100644 --- a/consul/client_test.go +++ b/consul/client_test.go @@ -269,3 +269,23 @@ func TestClientServer_UserEvent(t *testing.T) { t.Fatalf("missing events") } } + +func TestClient_Encrypted(t *testing.T) { + dir1, c1 := testClient(t) + defer os.RemoveAll(dir1) + defer c1.Shutdown() + + key := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + dir2, c2 := testClientWithConfig(t, func(c *Config) { + c.SerfLANConfig.MemberlistConfig.SecretKey = key + }) + defer os.RemoveAll(dir2) + defer c2.Shutdown() + + if c1.Encrypted() { + t.Fatalf("should not be encrypted") + } + if !c2.Encrypted() { + t.Fatalf("should be encrypted") + } +} diff --git a/consul/server_test.go b/consul/server_test.go index 50627837e6..9a6c311239 100644 --- a/consul/server_test.go +++ b/consul/server_test.go @@ -486,14 +486,23 @@ func TestServer_globalRPC(t *testing.T) { // Try to join addr := fmt.Sprintf("127.0.0.1:%d", - s1.config.SerfLANConfig.MemberlistConfig.BindPort) - if _, err := s2.JoinLAN([]string{addr}); err != nil { + s1.config.SerfWANConfig.MemberlistConfig.BindPort) + if _, err := s2.JoinWAN([]string{addr}); err != nil { t.Fatalf("err: %v", err) } + // Check the members + testutil.WaitForResult(func() (bool, error) { + members := len(s1.WANMembers()) + return members == 2, fmt.Errorf("expected 2 members, got %d", members) + }, func(err error) { + t.Fatalf(err.Error()) + }) + + // Wait for leader election testutil.WaitForLeader(t, s1.RPC, "dc1") - // Check that replies from each DC come in + // Check that replies from each gossip pool come in resp := &structs.KeyringResponses{} args := &structs.KeyringRequest{Operation: structs.KeyringList} if err := s1.globalRPC("Internal.KeyringOperation", args, resp); err != nil { @@ -503,7 +512,7 @@ func TestServer_globalRPC(t *testing.T) { t.Fatalf("bad: %#v", resp.Responses) } - // Check that error from remote DC is returned + // Check that an error from a remote DC is returned resp = &structs.KeyringResponses{} err := s1.globalRPC("Bad.Method", nil, resp) if err == nil { @@ -513,3 +522,24 @@ func TestServer_globalRPC(t *testing.T) { t.Fatalf("unexpcted error: %s", err) } } + +func TestServer_Encrypted(t *testing.T) { + dir1, s1 := testServer(t) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + + key := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + dir2, s2 := testServerWithConfig(t, func(c *Config) { + c.SerfLANConfig.MemberlistConfig.SecretKey = key + c.SerfWANConfig.MemberlistConfig.SecretKey = key + }) + defer os.RemoveAll(dir2) + defer s2.Shutdown() + + if s1.Encrypted() { + t.Fatalf("should not be encrypted") + } + if !s2.Encrypted() { + t.Fatalf("should be encrypted") + } +} diff --git a/consul/structs/structs_test.go b/consul/structs/structs_test.go index e5944cbe43..abf8ebb744 100644 --- a/consul/structs/structs_test.go +++ b/consul/structs/structs_test.go @@ -32,3 +32,24 @@ func TestEncodeDecode(t *testing.T) { t.Fatalf("bad: %#v %#v", arg, out) } } + +func TestStructs_Implements(t *testing.T) { + var ( + _ RPCInfo = &GenericRPC{} + _ RPCInfo = &RegisterRequest{} + _ RPCInfo = &DeregisterRequest{} + _ RPCInfo = &DCSpecificRequest{} + _ RPCInfo = &ServiceSpecificRequest{} + _ RPCInfo = &NodeSpecificRequest{} + _ RPCInfo = &ChecksInStateRequest{} + _ RPCInfo = &KVSRequest{} + _ RPCInfo = &KeyRequest{} + _ RPCInfo = &KeyListRequest{} + _ RPCInfo = &SessionRequest{} + _ RPCInfo = &SessionSpecificRequest{} + _ RPCInfo = &EventFireRequest{} + _ RPCInfo = &ACLPolicyRequest{} + _ RPCInfo = &KeyringRequest{} + _ CompoundResponse = &KeyringResponses{} + ) +} From a662acd79401bf7a461cf5ade1e7ac151665e63a Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 8 Oct 2014 13:28:59 -0700 Subject: [PATCH 74/80] consul: fix obscure bug when launching goroutines from for loop --- consul/internal_endpoint.go | 2 +- consul/rpc.go | 16 +++++----------- consul/structs/structs.go | 10 ---------- consul/structs/structs_test.go | 1 - 4 files changed, 6 insertions(+), 23 deletions(-) diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index e20e315da0..c27a078370 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -71,13 +71,13 @@ func (m *Internal) KeyringOperation( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { - m.executeKeyringOp(args, reply, false) if !args.Forwarded { args.Forwarded = true m.executeKeyringOp(args, reply, true) return m.srv.globalRPC("Internal.KeyringOperation", args, reply) } + m.executeKeyringOp(args, reply, false) return nil } diff --git a/consul/rpc.go b/consul/rpc.go index 8fca85ff45..8956b0d0ff 100644 --- a/consul/rpc.go +++ b/consul/rpc.go @@ -229,29 +229,23 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{ func (s *Server) globalRPC(method string, args interface{}, reply structs.CompoundResponse) error { - totalDC := len(s.remoteConsuls) - if totalDC == 1 { - return nil - } - errorCh := make(chan error) respCh := make(chan interface{}) // Make a new request into each datacenter for dc, _ := range s.remoteConsuls { - info := &structs.GenericRPC{Datacenter: dc} - go func() { + go func(dc string) { rr := reply.New() - if _, err := s.forward(method, info, args, &rr); err != nil { + if err := s.forwardDC(method, dc, args, &rr); err != nil { errorCh <- err return } respCh <- rr - }() + }(dc) } - replies := 0 - for replies < totalDC { + replies, total := 0, len(s.remoteConsuls) + for replies < total { select { case err := <-errorCh: return err diff --git a/consul/structs/structs.go b/consul/structs/structs.go index d655adf79a..b1f315271d 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -127,16 +127,6 @@ type QueryMeta struct { KnownLeader bool } -// GenericRPC is the simplest possible RPCInfo implementation -type GenericRPC struct { - Datacenter string - QueryOptions -} - -func (r *GenericRPC) RequestDatacenter() string { - return r.Datacenter -} - // RegisterRequest is used for the Catalog.Register endpoint // to register a node as providing a service. If no service // is provided, the node is registered. diff --git a/consul/structs/structs_test.go b/consul/structs/structs_test.go index abf8ebb744..cb7808731a 100644 --- a/consul/structs/structs_test.go +++ b/consul/structs/structs_test.go @@ -35,7 +35,6 @@ func TestEncodeDecode(t *testing.T) { func TestStructs_Implements(t *testing.T) { var ( - _ RPCInfo = &GenericRPC{} _ RPCInfo = &RegisterRequest{} _ RPCInfo = &DeregisterRequest{} _ RPCInfo = &DCSpecificRequest{} From 4203e7ab6d05af3d1cc38e7284473daa50eabaa5 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 9 Oct 2014 10:25:53 -0700 Subject: [PATCH 75/80] consul: clean up comments, fix globalRPC tests --- command/agent/keyring.go | 4 ---- consul/internal_endpoint.go | 6 ++--- consul/server_test.go | 48 ++++++++++--------------------------- 3 files changed, 15 insertions(+), 43 deletions(-) diff --git a/command/agent/keyring.go b/command/agent/keyring.go index 66ca7e2239..51a4666536 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -118,10 +118,6 @@ func loadKeyringFile(c *serf.Config) error { // keyringProcess is used to abstract away the semantic similarities in // performing various operations on the encryption keyring. func (a *Agent) keyringProcess(args *structs.KeyringRequest) (*structs.KeyringResponses, error) { - // Allow any server to handle the request, since this is - // done over the gossip protocol. - args.AllowStale = true - var reply structs.KeyringResponses if a.server == nil { return nil, fmt.Errorf("keyring operations must run against a server node") diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index c27a078370..3032e0b031 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -64,19 +64,19 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, return m.srv.UserEvent(args.Name, args.Payload) } -// KeyringOperation will query the WAN and LAN gossip keyrings of all nodes, -// adding results into a collective response as we go. It can describe requests -// for all keyring-related operations. +// KeyringOperation will query the WAN and LAN gossip keyrings of all nodes. func (m *Internal) KeyringOperation( args *structs.KeyringRequest, reply *structs.KeyringResponses) error { + // Only perform WAN keyring querying and RPC forwarding once if !args.Forwarded { args.Forwarded = true m.executeKeyringOp(args, reply, true) return m.srv.globalRPC("Internal.KeyringOperation", args, reply) } + // Query the LAN keyring of this node's DC m.executeKeyringOp(args, reply, false) return nil } diff --git a/consul/server_test.go b/consul/server_test.go index 9a6c311239..0d0d1d5889 100644 --- a/consul/server_test.go +++ b/consul/server_test.go @@ -10,7 +10,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/testutil" ) @@ -475,46 +474,23 @@ func TestServer_BadExpect(t *testing.T) { }) } -func TestServer_globalRPC(t *testing.T) { +type fakeGlobalResp struct{} + +func (r *fakeGlobalResp) Add(interface{}) { + return +} + +func (r *fakeGlobalResp) New() interface{} { + return struct{}{} +} + +func TestServer_globalRPCErrors(t *testing.T) { dir1, s1 := testServerDC(t, "dc1") defer os.RemoveAll(dir1) defer s1.Shutdown() - dir2, s2 := testServerDC(t, "dc2") - defer os.RemoveAll(dir2) - defer s2.Shutdown() - - // Try to join - addr := fmt.Sprintf("127.0.0.1:%d", - s1.config.SerfWANConfig.MemberlistConfig.BindPort) - if _, err := s2.JoinWAN([]string{addr}); err != nil { - t.Fatalf("err: %v", err) - } - - // Check the members - testutil.WaitForResult(func() (bool, error) { - members := len(s1.WANMembers()) - return members == 2, fmt.Errorf("expected 2 members, got %d", members) - }, func(err error) { - t.Fatalf(err.Error()) - }) - - // Wait for leader election - testutil.WaitForLeader(t, s1.RPC, "dc1") - - // Check that replies from each gossip pool come in - resp := &structs.KeyringResponses{} - args := &structs.KeyringRequest{Operation: structs.KeyringList} - if err := s1.globalRPC("Internal.KeyringOperation", args, resp); err != nil { - t.Fatalf("err: %s", err) - } - if len(resp.Responses) != 3 { - t.Fatalf("bad: %#v", resp.Responses) - } - // Check that an error from a remote DC is returned - resp = &structs.KeyringResponses{} - err := s1.globalRPC("Bad.Method", nil, resp) + err := s1.globalRPC("Bad.Method", nil, &fakeGlobalResp{}) if err == nil { t.Fatalf("should have errored") } From ab5fbe40946f37c9e8f457b361c8f0683471eb24 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 9 Oct 2014 15:28:38 -0700 Subject: [PATCH 76/80] agent: ignore -encrypt if provided when keyring exists --- command/agent/agent_test.go | 4 +- command/agent/command.go | 16 +++++- command/agent/keyring.go | 31 ++++------- command/agent/keyring_test.go | 53 ++++++------------- .../source/docs/agent/options.html.markdown | 5 +- 5 files changed, 45 insertions(+), 64 deletions(-) diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 1e4aec000c..3f398016fa 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -81,11 +81,11 @@ func makeAgentKeyring(t *testing.T, conf *Config, key string) (string, *Agent) { conf.DataDir = dir fileLAN := filepath.Join(dir, serfLANKeyring) - if err := initKeyring(fileLAN, key); err != nil { + if _, err := initKeyring(fileLAN, key); err != nil { t.Fatalf("err: %s", err) } fileWAN := filepath.Join(dir, serfWANKeyring) - if err := initKeyring(fileWAN, key); err != nil { + if _, err := initKeyring(fileWAN, key); err != nil { t.Fatalf("err: %s", err) } diff --git a/command/agent/command.go b/command/agent/command.go index 7bb9496189..8c0455c433 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -156,17 +156,29 @@ func (c *Command) readConfig() *Config { } fileLAN := filepath.Join(config.DataDir, serfLANKeyring) - if err := initKeyring(fileLAN, config.EncryptKey); err != nil { + done, err := initKeyring(fileLAN, config.EncryptKey) + if err != nil { c.Ui.Error(fmt.Sprintf("Error initializing keyring: %s", err)) return nil } + if !done { + c.Ui.Error(fmt.Sprintf( + "WARNING: keyring file %s already exists, not overwriting", + fileLAN)) + } if config.Server { fileWAN := filepath.Join(config.DataDir, serfWANKeyring) - if err := initKeyring(fileWAN, config.EncryptKey); err != nil { + done, err := initKeyring(fileWAN, config.EncryptKey) + if err != nil { c.Ui.Error(fmt.Sprintf("Error initializing keyring: %s", err)) return nil } + if !done { + c.Ui.Error(fmt.Sprintf( + "WARNING: keyring file %s already exists, not overwriting", + fileWAN)) + } } } diff --git a/command/agent/keyring.go b/command/agent/keyring.go index 51a4666536..f0644982e8 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -18,51 +18,42 @@ const ( serfWANKeyring = "serf/remote.keyring" ) -// initKeyring will create a keyring file at a given path. -func initKeyring(path, key string) error { +// initKeyring will create a keyring file at a given path. Returns whether any +// action was taken and any applicable error. +func initKeyring(path, key string) (bool, error) { var keys []string if _, err := base64.StdEncoding.DecodeString(key); err != nil { - return fmt.Errorf("Invalid key: %s", err) + return false, fmt.Errorf("Invalid key: %s", err) } + // Just exit if the file already exists. if _, err := os.Stat(path); err == nil { - content, err := ioutil.ReadFile(path) - if err != nil { - return err - } - if err := json.Unmarshal(content, &keys); err != nil { - return err - } - for _, existing := range keys { - if key == existing { - return nil - } - } + return false, nil } keys = append(keys, key) keyringBytes, err := json.Marshal(keys) if err != nil { - return err + return false, err } if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { - return err + return false, err } fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { - return err + return false, err } defer fh.Close() if _, err := fh.Write(keyringBytes); err != nil { os.Remove(path) - return err + return false, err } - return nil + return true, nil } // loadKeyringFile will load a gossip encryption keyring out of a file. The file diff --git a/command/agent/keyring_test.go b/command/agent/keyring_test.go index 734a67dc14..50fb1e1b40 100644 --- a/command/agent/keyring_test.go +++ b/command/agent/keyring_test.go @@ -1,12 +1,10 @@ package agent import ( - "bytes" - "encoding/json" + "fmt" "io/ioutil" "os" "path/filepath" - "strings" "testing" ) @@ -78,6 +76,7 @@ func TestAgent_LoadKeyrings(t *testing.T) { func TestAgent_InitKeyring(t *testing.T) { key1 := "tbLJg26ZJyJ9pK3qhc9jig==" key2 := "4leC33rgtXKIVUr9Nr0snQ==" + expected := fmt.Sprintf(`["%s"]`, key1) dir, err := ioutil.TempDir("", "consul") if err != nil { @@ -88,56 +87,36 @@ func TestAgent_InitKeyring(t *testing.T) { file := filepath.Join(dir, "keyring") // First initialize the keyring - if err := initKeyring(file, key1); err != nil { - t.Fatalf("err: %s", err) - } - - content1, err := ioutil.ReadFile(file) + done, err := initKeyring(file, key1) if err != nil { t.Fatalf("err: %s", err) } - if !strings.Contains(string(content1), key1) { - t.Fatalf("bad: %s", content1) - } - if strings.Contains(string(content1), key2) { - t.Fatalf("bad: %s", content1) + if !done { + t.Fatalf("should have modified keyring") } - // Now initialize again with the same key - if err := initKeyring(file, key1); err != nil { - t.Fatalf("err: %s", err) - } - - content2, err := ioutil.ReadFile(file) + content, err := ioutil.ReadFile(file) if err != nil { t.Fatalf("err: %s", err) } - if !bytes.Equal(content1, content2) { - t.Fatalf("bad: %s", content2) + if string(content) != expected { + t.Fatalf("bad: %s", content) } - // Initialize an existing keyring with a new key - if err := initKeyring(file, key2); err != nil { - t.Fatalf("err: %s", err) - } - - content3, err := ioutil.ReadFile(file) + // Try initializing again with a different key + done, err = initKeyring(file, key2) if err != nil { t.Fatalf("err: %s", err) } - if !strings.Contains(string(content3), key1) { - t.Fatalf("bad: %s", content3) - } - if !strings.Contains(string(content3), key2) { - t.Fatalf("bad: %s", content3) + if done { + t.Fatalf("should not have modified keyring") } - // Unmarshal and make sure that key1 is still primary - var keys []string - if err := json.Unmarshal(content3, &keys); err != nil { + content, err = ioutil.ReadFile(file) + if err != nil { t.Fatalf("err: %s", err) } - if keys[0] != key1 { - t.Fatalf("bad: %#v", keys) + if string(content) != expected { + t.Fatalf("bad: %s", content) } } diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown index 9046b03d1d..ece3157a3b 100644 --- a/website/source/docs/agent/options.html.markdown +++ b/website/source/docs/agent/options.html.markdown @@ -93,9 +93,8 @@ The options below are all specified on the command-line. automatically whenever the agent is restarted. This means that to encrypt Consul's gossip protocol, this option only needs to be provided once on each agent's initial startup sequence. If it is provided after Consul has been - initialized with an encryption key, then the provided key is simply added - as a secondary encryption key. More information on how keys can be changed - is available on the [keyring command](/docs/commands/keyring.html) page. + initialized with an encryption key, then the provided key is ignored and + a warning will be displayed. * `-join` - Address of another agent to join upon starting up. This can be specified multiple times to specify multiple agents to join. If Consul is From 8a652c6ffa19016c307023550856220895b344ec Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 10 Oct 2014 11:13:30 -0700 Subject: [PATCH 77/80] agent: fix loading keyring on agent start --- command/agent/agent.go | 72 ++++++++++++++++++++++------------- command/agent/agent_test.go | 4 +- command/agent/command.go | 28 +++----------- command/agent/keyring.go | 19 +++++---- command/agent/keyring_test.go | 15 ++------ 5 files changed, 66 insertions(+), 72 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index b6f3a299a7..bdd2197e6f 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -161,11 +161,6 @@ func (a *Agent) consulConfig() *consul.Config { if a.config.DataDir != "" { base.DataDir = a.config.DataDir } - if a.config.EncryptKey != "" { - key, _ := a.config.EncryptBytes() - base.SerfLANConfig.MemberlistConfig.SecretKey = key - base.SerfWANConfig.MemberlistConfig.SecretKey = key - } if a.config.NodeName != "" { base.NodeName = a.config.NodeName } @@ -263,21 +258,8 @@ func (a *Agent) consulConfig() *consul.Config { func (a *Agent) setupServer() error { config := a.consulConfig() - // Load a keyring file, if present - keyfileLAN := filepath.Join(config.DataDir, serfLANKeyring) - if _, err := os.Stat(keyfileLAN); err == nil { - config.SerfLANConfig.KeyringFile = keyfileLAN - } - if err := loadKeyringFile(config.SerfLANConfig); err != nil { - return err - } - - keyfileWAN := filepath.Join(config.DataDir, serfWANKeyring) - if _, err := os.Stat(keyfileWAN); err == nil { - config.SerfWANConfig.KeyringFile = keyfileWAN - } - if err := loadKeyringFile(config.SerfWANConfig); err != nil { - return err + if err := a.setupKeyrings(config); err != nil { + return fmt.Errorf("Failed to configure keyring: %v", err) } server, err := consul.NewServer(config) @@ -292,13 +274,8 @@ func (a *Agent) setupServer() error { func (a *Agent) setupClient() error { config := a.consulConfig() - // Load a keyring file, if present - keyfileLAN := filepath.Join(config.DataDir, serfLANKeyring) - if _, err := os.Stat(keyfileLAN); err == nil { - config.SerfLANConfig.KeyringFile = keyfileLAN - } - if err := loadKeyringFile(config.SerfLANConfig); err != nil { - return err + if err := a.setupKeyrings(config); err != nil { + return fmt.Errorf("Failed to configure keyring: %v", err) } client, err := consul.NewClient(config) @@ -309,6 +286,47 @@ func (a *Agent) setupClient() error { return nil } +// setupKeyrings is used to initialize and load keyrings during agent startup +func (a *Agent) setupKeyrings(config *consul.Config) error { + fileLAN := filepath.Join(a.config.DataDir, serfLANKeyring) + fileWAN := filepath.Join(a.config.DataDir, serfWANKeyring) + + if a.config.EncryptKey == "" { + goto LOAD + } + if _, err := os.Stat(fileLAN); err != nil { + if err := initKeyring(fileLAN, a.config.EncryptKey); err != nil { + return err + } + } + if a.config.Server { + if _, err := os.Stat(fileWAN); err != nil { + if err := initKeyring(fileWAN, a.config.EncryptKey); err != nil { + return err + } + } + } + +LOAD: + if _, err := os.Stat(fileLAN); err == nil { + config.SerfLANConfig.KeyringFile = fileLAN + } + if err := loadKeyringFile(config.SerfLANConfig); err != nil { + return err + } + if a.config.Server { + if _, err := os.Stat(fileWAN); err == nil { + config.SerfWANConfig.KeyringFile = fileWAN + } + if err := loadKeyringFile(config.SerfWANConfig); err != nil { + return err + } + } + + // Success! + return nil +} + // RPC is used to make an RPC call to the Consul servers // This allows the agent to implement the Consul.Interface func (a *Agent) RPC(method string, args interface{}, reply interface{}) error { diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 3f398016fa..1e4aec000c 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -81,11 +81,11 @@ func makeAgentKeyring(t *testing.T, conf *Config, key string) (string, *Agent) { conf.DataDir = dir fileLAN := filepath.Join(dir, serfLANKeyring) - if _, err := initKeyring(fileLAN, key); err != nil { + if err := initKeyring(fileLAN, key); err != nil { t.Fatalf("err: %s", err) } fileWAN := filepath.Join(dir, serfWANKeyring) - if _, err := initKeyring(fileWAN, key); err != nil { + if err := initKeyring(fileWAN, key); err != nil { t.Fatalf("err: %s", err) } diff --git a/command/agent/command.go b/command/agent/command.go index 8c0455c433..6d9da74da1 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -154,30 +154,14 @@ func (c *Command) readConfig() *Config { c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err)) return nil } - - fileLAN := filepath.Join(config.DataDir, serfLANKeyring) - done, err := initKeyring(fileLAN, config.EncryptKey) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error initializing keyring: %s", err)) - return nil + keyfileLAN := filepath.Join(config.DataDir, serfLANKeyring) + if _, err := os.Stat(keyfileLAN); err == nil { + c.Ui.Error("WARNING: LAN keyring exists but -encrypt given, ignoring") } - if !done { - c.Ui.Error(fmt.Sprintf( - "WARNING: keyring file %s already exists, not overwriting", - fileLAN)) - } - if config.Server { - fileWAN := filepath.Join(config.DataDir, serfWANKeyring) - done, err := initKeyring(fileWAN, config.EncryptKey) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error initializing keyring: %s", err)) - return nil - } - if !done { - c.Ui.Error(fmt.Sprintf( - "WARNING: keyring file %s already exists, not overwriting", - fileWAN)) + keyfileWAN := filepath.Join(config.DataDir, serfWANKeyring) + if _, err := os.Stat(keyfileWAN); err == nil { + c.Ui.Error("WARNING: WAN keyring exists but -encrypt given, ignoring") } } } diff --git a/command/agent/keyring.go b/command/agent/keyring.go index f0644982e8..07bd19b0c8 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -18,42 +18,41 @@ const ( serfWANKeyring = "serf/remote.keyring" ) -// initKeyring will create a keyring file at a given path. Returns whether any -// action was taken and any applicable error. -func initKeyring(path, key string) (bool, error) { +// initKeyring will create a keyring file at a given path. +func initKeyring(path, key string) error { var keys []string if _, err := base64.StdEncoding.DecodeString(key); err != nil { - return false, fmt.Errorf("Invalid key: %s", err) + return fmt.Errorf("Invalid key: %s", err) } // Just exit if the file already exists. if _, err := os.Stat(path); err == nil { - return false, nil + return nil } keys = append(keys, key) keyringBytes, err := json.Marshal(keys) if err != nil { - return false, err + return err } if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { - return false, err + return err } fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { - return false, err + return err } defer fh.Close() if _, err := fh.Write(keyringBytes); err != nil { os.Remove(path) - return false, err + return err } - return true, nil + return nil } // loadKeyringFile will load a gossip encryption keyring out of a file. The file diff --git a/command/agent/keyring_test.go b/command/agent/keyring_test.go index 50fb1e1b40..558c71f5dc 100644 --- a/command/agent/keyring_test.go +++ b/command/agent/keyring_test.go @@ -66,7 +66,7 @@ func TestAgent_LoadKeyrings(t *testing.T) { t.Fatalf("keyring should be loaded") } if c.SerfWANConfig.KeyringFile != "" { - t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) + t.Fatalf("bad: %#v", c.SerfWANConfig.KeyringFile) } if c.SerfWANConfig.MemberlistConfig.Keyring != nil { t.Fatalf("keyring should not be loaded") @@ -87,13 +87,9 @@ func TestAgent_InitKeyring(t *testing.T) { file := filepath.Join(dir, "keyring") // First initialize the keyring - done, err := initKeyring(file, key1) - if err != nil { + if err := initKeyring(file, key1); err != nil { t.Fatalf("err: %s", err) } - if !done { - t.Fatalf("should have modified keyring") - } content, err := ioutil.ReadFile(file) if err != nil { @@ -104,14 +100,11 @@ func TestAgent_InitKeyring(t *testing.T) { } // Try initializing again with a different key - done, err = initKeyring(file, key2) - if err != nil { + if err := initKeyring(file, key2); err != nil { t.Fatalf("err: %s", err) } - if done { - t.Fatalf("should not have modified keyring") - } + // Content should still be the same content, err = ioutil.ReadFile(file) if err != nil { t.Fatalf("err: %s", err) From bc0eb4c16d026ddb979b5e966b8b98d41a8c0de0 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sat, 11 Oct 2014 17:29:24 -0700 Subject: [PATCH 78/80] agent: fix gossip encryption detection --- command/agent/command.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/command/agent/command.go b/command/agent/command.go index 6d9da74da1..a62dbdd672 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -612,9 +612,12 @@ func (c *Command) Run(args []string) int { } // Figure out if gossip is encrypted - gossipEncrypted := (config.Server && - c.agent.server.Encrypted() || - c.agent.client.Encrypted()) + var gossipEncrypted bool + if config.Server { + gossipEncrypted = c.agent.server.Encrypted() + } else { + gossipEncrypted = c.agent.client.Encrypted() + } // Let the agent know we've finished registration c.agent.StartSync() From c283754381fa59054ae08f74e575d62a2385721b Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 19 Nov 2014 16:45:49 -0800 Subject: [PATCH 79/80] Rebase against upstream --- command/agent/agent_test.go | 65 ----------------------------------- command/agent/command_test.go | 35 ------------------- consul/client.go | 5 +++ 3 files changed, 5 insertions(+), 100 deletions(-) diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 1e4aec000c..547232431f 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -380,68 +380,3 @@ func TestAgent_ConsulService(t *testing.T) { t.Fatalf("%s service should be in sync", consul.ConsulServiceID) } } - -func TestAgent_LoadKeyrings(t *testing.T) { - key := "tbLJg26ZJyJ9pK3qhc9jig==" - - // Should be no configured keyring file by default - conf1 := nextConfig() - dir1, agent1 := makeAgent(t, conf1) - defer os.RemoveAll(dir1) - defer agent1.Shutdown() - - c := agent1.config.ConsulConfig - if c.SerfLANConfig.KeyringFile != "" { - t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) - } - if c.SerfLANConfig.MemberlistConfig.Keyring != nil { - t.Fatalf("keyring should not be loaded") - } - if c.SerfWANConfig.KeyringFile != "" { - t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) - } - if c.SerfWANConfig.MemberlistConfig.Keyring != nil { - t.Fatalf("keyring should not be loaded") - } - - // Server should auto-load LAN and WAN keyring files - conf2 := nextConfig() - dir2, agent2 := makeAgentKeyring(t, conf2, key) - defer os.RemoveAll(dir2) - defer agent2.Shutdown() - - c = agent2.config.ConsulConfig - if c.SerfLANConfig.KeyringFile == "" { - t.Fatalf("should have keyring file") - } - if c.SerfLANConfig.MemberlistConfig.Keyring == nil { - t.Fatalf("keyring should be loaded") - } - if c.SerfWANConfig.KeyringFile == "" { - t.Fatalf("should have keyring file") - } - if c.SerfWANConfig.MemberlistConfig.Keyring == nil { - t.Fatalf("keyring should be loaded") - } - - // Client should auto-load only the LAN keyring file - conf3 := nextConfig() - conf3.Server = false - dir3, agent3 := makeAgentKeyring(t, conf3, key) - defer os.RemoveAll(dir3) - defer agent3.Shutdown() - - c = agent3.config.ConsulConfig - if c.SerfLANConfig.KeyringFile == "" { - t.Fatalf("should have keyring file") - } - if c.SerfLANConfig.MemberlistConfig.Keyring == nil { - t.Fatalf("keyring should be loaded") - } - if c.SerfWANConfig.KeyringFile != "" { - t.Fatalf("bad: %#v", c.SerfLANConfig.KeyringFile) - } - if c.SerfWANConfig.MemberlistConfig.Keyring != nil { - t.Fatalf("keyring should not be loaded") - } -} diff --git a/command/agent/command_test.go b/command/agent/command_test.go index 703d476ac9..10557daa78 100644 --- a/command/agent/command_test.go +++ b/command/agent/command_test.go @@ -1,16 +1,10 @@ package agent import ( -<<<<<<< HEAD "fmt" "io/ioutil" "log" "os" - "path/filepath" - "strings" -======= - "github.com/mitchellh/cli" ->>>>>>> agent: -encrypt appends to keyring if one exists "testing" "github.com/hashicorp/consul/testutil" @@ -169,32 +163,3 @@ func TestRetryJoinWanFail(t *testing.T) { t.Fatalf("bad: %d", code) } } - -func TestArgConflict(t *testing.T) { - ui := new(cli.MockUi) - c := &Command{Ui: ui} - - dir, err := ioutil.TempDir("", "agent") - if err != nil { - t.Fatalf("err: %s", err) - } - - key := "HS5lJ+XuTlYKWaeGYyG+/A==" - - fileLAN := filepath.Join(dir, SerfLANKeyring) - if err := testutil.InitKeyring(fileLAN, key); err != nil { - t.Fatalf("err: %s", err) - } - - args := []string{ - "-encrypt=" + key, - "-data-dir=" + dir, - } - code := c.Run(args) - if code != 1 { - t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) - } - if !strings.Contains(ui.ErrorWriter.String(), "keyring files exist") { - t.Fatalf("bad: %#v", ui.ErrorWriter.String()) - } -} diff --git a/consul/client.go b/consul/client.go index cf0ddca0cf..d10db8550d 100644 --- a/consul/client.go +++ b/consul/client.go @@ -206,6 +206,11 @@ func (c *Client) UserEvent(name string, payload []byte) error { return c.serf.UserEvent(userEventName(name), payload, false) } +// KeyManagerLAN returns the LAN Serf keyring manager +func (c *Client) KeyManagerLAN() *serf.KeyManager { + return c.serf.KeyManager() +} + // Encrypted determines if gossip is encrypted func (c *Client) Encrypted() bool { return c.serf.EncryptionEnabled() From 28bd9810a709527ec66aec5f7b49d38b2bcc40bf Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 19 Nov 2014 23:18:12 -0800 Subject: [PATCH 80/80] agent: remove unused config variable --- command/agent/config.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/command/agent/config.go b/command/agent/config.go index dce299eace..36683ad16a 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -108,9 +108,6 @@ type Config struct { // resolve non-consul domains DNSRecursors []string `mapstructure:"recursors"` - // Disable use of an encryption keyring. - DisableKeyring bool `mapstructure:"disable_keyring"` - // DNS configuration DNSConfig DNSConfig `mapstructure:"dns_config"`