From 8a4ed8471144001ec92a8139e483a3503c31b59c 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 9ef071137..f955d4a3a 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 6474402f9..91118fc63 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 36683ad16..e099c64b1 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 9cb1944cb..10acaab2c 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 4b2656653751a0c683fd3bffd69acea5b1798100 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 f955d4a3a..97198c36c 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 208b5ae58f703cca44f0ec5bc92aabf611622d60 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 97198c36c..225c813a9 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 91118fc63..4f31249bb 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 61e3647ac1a0889e0f779b5f3ae83bf532cf4e81 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 4f31249bb..649cded48 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 e099c64b1..2fad1db39 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 f25c2c1f06b7a9e1344c8083945f5119f2ab35f8 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 000000000..da8e743d1 --- /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 3a31abd73..8fab257ac 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 96376212ffdae9a0efe0bf4d93a750e264652d1d 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 225c813a9..16c1e33e5 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 caf97cef1..e35615de5 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 da8e743d1..e63b46015 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 28838bf79..fb02cdcd0 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 2adbc7edb..8f913ed46 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 43a60f1424bb44b95ba004852482f618a9ae49a8 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 16c1e33e5..b219f18bc 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 e35615de5..e5934fc8a 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 6cd0fc19f..d5ff5894e 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 e63b46015..4f063a2dd 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 fb02cdcd0..be1854101 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 b200332ae3b1cd485c588d2f88eb583deec474e4 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 | 3 +++ command/agent/rpc.go | 9 +++------ command/keys.go | 19 ++++++++++++++++--- 3 files changed, 22 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 000000000..dbc2250e1 --- /dev/null +++ b/command/.keys.go.swp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:384f1d9597d62c56a5d575eac062dfb02c3180d34992026ab58bf082df27237f +size 12288 diff --git a/command/agent/rpc.go b/command/agent/rpc.go index e5934fc8a..09bf68fb8 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 4f063a2dd..49bac1831 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 83af160fc3434d6a4537e5fc77219e084b2504a2 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 | 3 --- command/agent/rpc.go | 47 ++++++++++++++++++++++++------------- command/agent/rpc_client.go | 16 ++++++------- command/keys.go | 24 +++++++++++++++---- 4 files changed, 59 insertions(+), 31 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 dbc2250e1..000000000 --- a/command/.keys.go.swp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:384f1d9597d62c56a5d575eac062dfb02c3180d34992026ab58bf082df27237f -size 12288 diff --git a/command/agent/rpc.go b/command/agent/rpc.go index 09bf68fb8..e1007cb1d 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 d5ff5894e..daa72e1c0 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 49bac1831..069af8566 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 cae3f0fd0b8e94aaed7031c46601633a1c598ad0 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 e1007cb1d..7030f6538 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 164f2428ff96977a7e4c061757310136b204abc0 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 069af8566..0373804cf 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 b11afa33f30b07bdf8978bbcf09e2bd852520391 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 0373804cf..9be460063 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 d03ed1a9ba6a20af2e923a456c7ca9d5b025a3f2 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 b219f18bc..f1d52075f 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 7030f6538..5124a6d65 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 daa72e1c0..c6b442f77 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 9be460063..2c0ce8f8e 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 d52163703eeabf07e48090c3cc0ba5f843b736ce 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 2c0ce8f8e..c6464146c 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 ccda799039df37b9bcd967ee769931875cc08450 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 f1d52075f..9caf81547 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 5124a6d65..6c33a13d6 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 c6b442f77..5fe988b20 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 c6464146c..e555517ea 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 5f04ae277e285982a4224abd333ae7acd135524c 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 9caf81547..456415f96 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 6c33a13d6..2382bee48 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 5fe988b20..5d82dc8fa 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 e555517ea..b99853576 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 109a3604da3e6c314aa4007d6d2041d4e83c219e 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 2382bee48..bf9b42d52 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 000000000..0d1c464b4 --- /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 cd201139b..388c1e62b 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 04f2a53735d8239afdcf65e950235f6dc0703583 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 0d1c464b4..b326e7b3d 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 b1b722dbff8c1d9e47b32a2ba8d32fa99e0c17f0 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 b326e7b3d..903d13b19 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 0ad0805234cb8bf516f6c72606941e3b1b06f55b 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 18825613c..b290a6dee 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 ee50795850cc1ae143824f797854db5c9c7e0bc0 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 b290a6dee..a042d85e8 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 c0f1b5f8c82e9d8d910146c83ef96ebbdd24d0b4 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 000000000..785025b34 --- /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 52d171e4d..6c48f1dbb 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -79,6 +79,10 @@ keygen + > + keys + + > leave From d8f513f6d6825c46e5c751d1f717922bb4044122 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 01149ef59..2a60a4309 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 785025b34..beb9f3894 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 2e92e19760f46709f9b1102b0d3658625bfbec6f 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 456415f96..5453f4ad9 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 649cded48..6f1da6cf7 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 2fad1db39..cb3596167 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 10acaab2c..9cb1944cb 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 3e64ed70df822d68a5e4104ce704532f1a10ab7f 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 5453f4ad9..781ecc90a 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 6f1da6cf7..94b6eacd3 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 cb3596167..e4c1ee73a 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 dfdd7c4ef79f7758b1fb6bc19fc1349318064f6b 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 781ecc90a..06aad6d43 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 a36ab53f25e2d04f4f97fef0e2bba07fbe3faad7 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 06aad6d43..95fcd8bec 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 94b6eacd3..bacc099d5 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 e4c1ee73a..9334553d0 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 b99853576..8ca98bdf2 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 ee20698199047fee14a2ae697dbf5f2cf56586a7 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 95fcd8bec..d0f62fc3e 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 9334553d0..60bd73032 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 8ca98bdf2..c603a3d53 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 8dc53447a31e79ceb6540ba8f7adf5c1b1eb95d8 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 c603a3d53..17d1c5c4c 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 903d13b19..d3f01ade9 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 8fab257ac..f6cf50a91 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 beb9f3894..2b65241e6 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 6c48f1dbb..d6764e792 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -79,8 +79,8 @@ keygen - > - keys + > + keyring > From 6609cb680b37138dfd9d3b8ad4c4b34f890e1e26 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 d0f62fc3e..b19645f16 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 bacc099d5..c02b8135c 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 60bd73032..69097984d 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 17d1c5c4c..aed55c039 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 c88780fe1296c2a538dcadaf63443a5759971d7d 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 d3f01ade9..b98c3c597 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 986eb0eefed21d41e4fb468993ac80c5ade353d0 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 13e8634ce..19097e2c3 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 bb06d5ccb80ee58a8e2aeece70a3c927446994b5 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 2a60a4309..01149ef59 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 2b65241e6..8481b588a 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 a9f3cbd7f054a275df29341aa5177c3f2eb8212c 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 b19645f16..169db3276 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 c02b8135c..f15cea41f 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 69097984d..1188c7be9 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 f9fd1f3f057e559c709a64d6ef070d7749ec849c 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 a00d5cc11..1e806a9ce 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 19097e2c3..52c0c58f7 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 355fc89f7fd57687766ffddc2cfa0700c3843d4a 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 1e806a9ce..8240b4854 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 10557daa7..db7d1c9a5 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 aed55c039..83293a644 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 b98c3c597..cc18ad799 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 000000000..60486bbb8 --- /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 000000000..7e5b3e63f --- /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 9a7a7c10d4a71fcddb8d1f834061aace77972e14 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 8481b588a..ff3285dc6 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 72fc1ceeadbfd6fbb8438cfec09105c98a9382b0 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 169db3276..f8b477147 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 000000000..dc63b4f59 --- /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 000000000..7807e5376 --- /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 03012e8ac652ce5d1201031d112142fef19e1482 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 db7d1c9a5..9c1bf4db5 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 83293a644..630f0ba12 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 cc18ad799..c7c0847f4 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 ff3285dc6..6a635746c 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 a551a6e4a0bf0df7a941f866b8d08d1153f8bd63 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 dc63b4f59..e816c3d78 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 bf9b42d52..983cc3481 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 5d82dc8fa..36a54057e 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 630f0ba12..eaa298dcd 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 5a38b31a2..7cc648747 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 cd5c36ebd..4526ca75b 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 c2585b132..31a6e319f 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 71e9715c549a989668acef2d036d17f8ca9d52ef 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 e816c3d78..827aa76bc 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 983cc3481..5d6c5c8a7 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 7cc648747..e7dafb319 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 31a6e319f..3f029ee81 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 f9b5b15a6b6a07c2c9fcc9b872d155b0a0ae85ab 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 e7dafb319..690707611 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 4526ca75b..e5b5be6c5 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 f6b5fc8c087257ac6e7a02c8529809a3f2df5d53 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 5d6c5c8a7..288a97cf3 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 36a54057e..454f427a8 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 eaa298dcd..8d0dc9545 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 690707611..2cd08a2c6 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 e5b5be6c5..cd5c36ebd 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 3f029ee81..6e752ea4f 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 30f5f06dfe33116ad289dceed138f7f144565c8d 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 8d0dc9545..0e0bbc19b 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 52582e7365fe20cb60181249a8880e7a4f216b31 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 a042d85e8..94a9f07d3 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 6277a76a9dd208f1cac0eabfe11ad4385d7a1c26 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 94a9f07d3..3b08aa733 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 388c1e62b..586489233 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 c4a9291bb926383292526046c04efacbfdcb864d 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 288a97cf3..81beff662 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 454f427a8..7ba1907b2 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 3b08aa733..e5aed3898 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 0e0bbc19b..e06248d42 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 c7c0847f4..98f8ba3dd 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 1ec111bbfc12d4aed9c473a11d9addd5196f8d4a 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 81beff662..e64084d17 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 2cd08a2c6..a1a170f77 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 6e752ea4f..2557bfc39 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 6a635746c..03b1d6c25 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 c1ea2911127bbff2e72c4a688a62719ca8fa68a1 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 e64084d17..c4fe71f70 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 e06248d42..cc6d211a8 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 bea19b51358c043902e56796f1aa3e922400f320 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 cc6d211a8..cde37f3e6 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 98f8ba3dd..7e975b6cc 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 d7edc1c51ca3894fd28660039d8e7073c4be515d 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 a1a170f77..9d8d000e9 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 000000000..373523118 --- /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 a1943afddcadb80b5bf63a327832898b5ec45f8d 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 9d8d000e9..ee2270700 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 373523118..000000000 --- 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 ae1dacc33..bfdf5668e 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 cb795199d12a6165c19ba33ecc5df93a3d7e96a8 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 bfdf5668e..02d068374 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 07225a729..b30b4fe82 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 001a579d479b8d702d10a335cff1194da42f5942 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 cde37f3e6..78c56fd2b 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 2557bfc39..dfaadbaa7 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 03b1d6c25..518ccefc9 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 08f1605159bf6b009347dd63635bf924ccc3f4fd 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 518ccefc9..6caa172dd 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 33dea16567490e51d9d4b3c90a5fd60b7751de39 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 e5aed3898..518423a69 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 057c22db100f3977c743b7d526e8a164fcac8642 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 ee2270700..223b8eeff 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 cd5c36ebd..6fcb07d82 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 02d068374..ae1dacc33 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 dfaadbaa7..1b3ead960 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 db0084ccd018466ed12e6f55b5c5f06d0c2ee467 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 827aa76bc..f6d80a9dc 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 223b8eeff..f9bed288c 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 1b3ead960..d655adf79 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 daebf399469c892bcb0fee7a4ff76fda4e2cb483 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 f6d80a9dc..6df94d3c9 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 4e8f53fa5d55d6e986f4f101673fbcd7e72f1d53 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 f9bed288c..50109f88d 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 7f85c708dc102bd570ae006d4748fdf34eeb3c5e 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 6df94d3c9..2f1835d63 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 50109f88d..055e450c7 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 c59107f08efeaf9d34f25501d1f87573e191eae4 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 f8b477147..b6f3a299a 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 f15cea41f..089f0a88a 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 1188c7be9..dce299eac 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 2f1835d63..d4253b5e1 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 78c56fd2b..41c0b6ff7 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 6caa172dd..3ecbaebc9 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 295f876923eeecd26f1794a1a7ce87ea0658faba 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 089f0a88a..620389767 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 be1854101..2c053513a 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 8f913ed46..cba7f11ba 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 d02afd42fbd2685e0267ab0424966769adaceb35 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 8240b4854..10ee331b8 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 620389767..7bb949618 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 9c1bf4db5..703d476ac 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 52c0c58f7..13e8634ce 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 d4253b5e1..524968605 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 7807e5376..734a67dc1 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 518423a69..2eed8f8a8 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 7e975b6cc..cc75d9797 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 6fcb07d82..f400b2be9 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 b30b4fe82..07225a729 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 76b7d4ed4..50627837e 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 9217e371bcab783860ac3d1b3ad096c0148010e8 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 01149ef59..9046b03d1 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 bb9617642adc561610c1d46ed6eba10ae8026d45 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 2eed8f8a8..ef082838a 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 f24ac482e387245f2c58db59d6c5f866655be715 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 10ee331b8..1e4aec000 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 cc75d9797..7f2d4c074 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 60486bbb8..000000000 --- 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 7e5b3e63f..000000000 --- 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 b3f251de9c184a42dcb199ec163a091830f12008 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 7f2d4c074..25e20599c 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 055e450c7..fe3c68162 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 f400b2be9..fbb73ce7b 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 00fc7fa1ddf07e40613fc47974332d07277db9dd 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 41c0b6ff7..ee072b879 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 344b63b9db2588838e41ae52d99c771dfcd955ea 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 fe3c68162..967d10706 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 66ad81ef13176aff82bda25c4ac9b7664d5ee56b 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 e3c33fe92..45b57f21c 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 fcacee723b8157430a526a81e3c37a3e77198d14 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 524968605..66ca7e223 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 967d10706..e20e315da 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 fbb73ce7b..8fca85ff4 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 2661bbfa270e0af398e2a6fbbc81161e2791d259 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 ef082838a..3bf03d6dc 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 25e20599c..bb8691ebb 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 586489233..a48f33cb0 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 2c053513a..cf0ddca0c 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 a783c3a52..33425cdf7 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 50627837e..9a6c31123 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 e5944cbe4..abf8ebb74 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 4a8249db00fabca02c0ea1ca92ffcffd44eec2f7 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 e20e315da..c27a07837 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 8fca85ff4..8956b0d0f 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 d655adf79..b1f315271 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 abf8ebb74..cb7808731 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 3b2ab70c4d4eb5b79e6f71d2b2a84cb8c9db1919 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 66ca7e223..51a466653 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 c27a07837..3032e0b03 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 9a6c31123..0d0d1d588 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 196cbd27b2e8511edb10ca253b23a2c92e6c3342 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 1e4aec000..3f398016f 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 7bb949618..8c0455c43 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 51a466653..f0644982e 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 734a67dc1..50fb1e1b4 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 9046b03d1..ece3157a3 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 c9118b53bf5c892143098630bbf0279dc968d04e 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 b6f3a299a..bdd2197e6 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 3f398016f..1e4aec000 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 8c0455c43..6d9da74da 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 f0644982e..07bd19b0c 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 50fb1e1b4..558c71f5d 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 a675b5faae92cee1779f68b8f04d74c2e5cec9ae 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 6d9da74da..a62dbdd67 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 4cd89a9113480ad7c3cf4f57c29316cfdc90bca9 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 1e4aec000..547232431 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 703d476ac..10557daa7 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 cf0ddca0c..d10db8550 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 accf2bbb59bbbf85bcec371147749ef54c6325e2 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 dce299eac..36683ad16 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"`