diff --git a/command/agent/agent.go b/command/agent/agent.go index f8b477147c..b6f3a299a7 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -264,17 +264,18 @@ func (a *Agent) setupServer() error { config := a.consulConfig() // Load a keyring file, if present - keyfileLAN := filepath.Join(config.DataDir, SerfLANKeyring) + keyfileLAN := filepath.Join(config.DataDir, serfLANKeyring) if _, err := os.Stat(keyfileLAN); err == nil { config.SerfLANConfig.KeyringFile = keyfileLAN } - keyfileWAN := filepath.Join(config.DataDir, SerfWANKeyring) - if _, err := os.Stat(keyfileWAN); err == nil { - config.SerfWANConfig.KeyringFile = keyfileWAN - } if err := loadKeyringFile(config.SerfLANConfig); err != nil { return err } + + keyfileWAN := filepath.Join(config.DataDir, serfWANKeyring) + if _, err := os.Stat(keyfileWAN); err == nil { + config.SerfWANConfig.KeyringFile = keyfileWAN + } if err := loadKeyringFile(config.SerfWANConfig); err != nil { return err } @@ -292,7 +293,7 @@ func (a *Agent) setupClient() error { config := a.consulConfig() // Load a keyring file, if present - keyfileLAN := filepath.Join(config.DataDir, SerfLANKeyring) + keyfileLAN := filepath.Join(config.DataDir, serfLANKeyring) if _, err := os.Stat(keyfileLAN); err == nil { config.SerfLANConfig.KeyringFile = keyfileLAN } diff --git a/command/agent/command.go b/command/agent/command.go index f15cea41fa..089f0a88ab 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -143,17 +143,35 @@ func (c *Command) readConfig() *Config { config.NodeName = hostname } + // Ensure we have a data directory + if config.DataDir == "" { + c.Ui.Error("Must specify data directory using -data-dir") + return nil + } + if config.EncryptKey != "" { if _, err := config.EncryptBytes(); err != nil { c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err)) return nil } - } - // Ensure we have a data directory - if config.DataDir == "" { - c.Ui.Error("Must specify data directory using -data-dir") - return nil + fileLAN := filepath.Join(config.DataDir, serfLANKeyring) + if _, err := os.Stat(fileLAN); err != nil { + initKeyring(fileLAN, config.EncryptKey) + } else { + c.Ui.Error(fmt.Sprintf("WARNING: %s exists, not using key: %s", + fileLAN, config.EncryptKey)) + } + + if config.Server { + fileWAN := filepath.Join(config.DataDir, serfWANKeyring) + if _, err := os.Stat(fileWAN); err != nil { + initKeyring(fileWAN, config.EncryptKey) + } else { + c.Ui.Error(fmt.Sprintf("WARNING: %s exists, not using key: %s", + fileWAN, config.EncryptKey)) + } + } } // Verify data center is valid @@ -218,12 +236,6 @@ func (c *Command) readConfig() *Config { c.Ui.Error("WARNING: Windows is not recommended as a Consul server. Do not use in production.") } - // Error if an encryption key is passed while a keyring already exists - if config.EncryptKey != "" && config.keyringFileExists() { - c.Ui.Error(fmt.Sprintf("Error: -encrypt specified but keyring files exist")) - return nil - } - // Set the version info config.Revision = c.Revision config.Version = c.Version @@ -465,6 +477,22 @@ func (c *Command) retryJoinWan(config *Config, errCh chan<- struct{}) { } } +// gossipEncrypted determines if the consul instance is using symmetric +// encryption keys to protect gossip protocol messages. +func (c *Command) gossipEncrypted() bool { + if c.agent.config.EncryptKey != "" { + return true + } + + server := c.agent.server + if server != nil { + return server.KeyManagerLAN() != nil || server.KeyManagerWAN() != nil + } + + client := c.agent.client + return client != nil && client.KeyManagerLAN() != nil +} + func (c *Command) Run(args []string) int { c.Ui = &cli.PrefixedUi{ OutputPrefix: "==> ", @@ -591,9 +619,6 @@ func (c *Command) Run(args []string) int { }(wp) } - // Determine if gossip is encrypted - gossipEncrypted := config.EncryptKey != "" || config.keyringFileExists() - // Let the agent know we've finished registration c.agent.StartSync() @@ -606,7 +631,7 @@ func (c *Command) Run(args []string) int { c.Ui.Info(fmt.Sprintf(" Cluster Addr: %v (LAN: %d, WAN: %d)", config.AdvertiseAddr, config.Ports.SerfLan, config.Ports.SerfWan)) c.Ui.Info(fmt.Sprintf("Gossip encrypt: %v, RPC-TLS: %v, TLS-Incoming: %v", - gossipEncrypted, config.VerifyOutgoing, config.VerifyIncoming)) + c.gossipEncrypted(), config.VerifyOutgoing, config.VerifyIncoming)) // Enable log streaming c.Ui.Info("") diff --git a/command/agent/config.go b/command/agent/config.go index 1188c7be9a..dce299eace 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -411,25 +411,6 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { return addr.String(), nil } -// keyringFileExists determines if there are encryption key files present -// in the data directory. On client nodes, this returns true if a LAN keyring -// is present. On server nodes, it returns true if either keyring file exists. -func (c *Config) keyringFileExists() bool { - fileLAN := filepath.Join(c.DataDir, SerfLANKeyring) - fileWAN := filepath.Join(c.DataDir, SerfWANKeyring) - - if _, err := os.Stat(fileLAN); err == nil { - return true - } - if !c.Server { - return false - } - if _, err := os.Stat(fileWAN); err == nil { - return true - } - return false -} - // DecodeConfig reads the configuration from the given reader in JSON // format and decodes it into a proper Config structure. func DecodeConfig(r io.Reader) (*Config, error) { diff --git a/command/agent/keyring.go b/command/agent/keyring.go index 2f1835d634..d4253b5e1c 100644 --- a/command/agent/keyring.go +++ b/command/agent/keyring.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/memberlist" @@ -13,10 +14,44 @@ import ( ) const ( - SerfLANKeyring = "serf/local.keyring" - SerfWANKeyring = "serf/remote.keyring" + serfLANKeyring = "serf/local.keyring" + serfWANKeyring = "serf/remote.keyring" ) +// initKeyring will create a keyring file at a given path. +func initKeyring(path, key string) error { + if _, err := base64.StdEncoding.DecodeString(key); err != nil { + return fmt.Errorf("Invalid key: %s", err) + } + + keys := []string{key} + keyringBytes, err := json.Marshal(keys) + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return err + } + + if _, err := os.Stat(path); err == nil { + return fmt.Errorf("File already exists: %s", path) + } + + fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer fh.Close() + + if _, err := fh.Write(keyringBytes); err != nil { + os.Remove(path) + return err + } + + return nil +} + // loadKeyringFile will load a gossip encryption keyring out of a file. The file // must be in JSON format and contain a list of encryption key strings. func loadKeyringFile(c *serf.Config) error { diff --git a/command/keyring.go b/command/keyring.go index 78c56fd2b9..41c0b6ff76 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -1,12 +1,8 @@ package command import ( - "encoding/base64" - "encoding/json" "flag" "fmt" - "os" - "path/filepath" "strings" "github.com/hashicorp/consul/command/agent" @@ -20,7 +16,7 @@ type KeyringCommand struct { } func (c *KeyringCommand) Run(args []string) int { - var installKey, useKey, removeKey, init, dataDir string + var installKey, useKey, removeKey string var listKeys bool cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) @@ -30,8 +26,6 @@ func (c *KeyringCommand) Run(args []string) int { cmdFlags.StringVar(&useKey, "use", "", "use key") cmdFlags.StringVar(&removeKey, "remove", "", "remove key") cmdFlags.BoolVar(&listKeys, "list", false, "list keys") - cmdFlags.StringVar(&init, "init", "", "initialize keyring") - cmdFlags.StringVar(&dataDir, "data-dir", "", "data directory") rpcAddr := RPCAddrFlag(cmdFlags) if err := cmdFlags.Parse(args); err != nil { @@ -47,7 +41,7 @@ func (c *KeyringCommand) Run(args []string) int { // Only accept a single argument found := listKeys - for _, arg := range []string{installKey, useKey, removeKey, init} { + for _, arg := range []string{installKey, useKey, removeKey} { if found && len(arg) > 0 { c.Ui.Error("Only a single action is allowed") return 1 @@ -61,26 +55,6 @@ func (c *KeyringCommand) Run(args []string) int { return 1 } - if init != "" { - if dataDir == "" { - c.Ui.Error("Must provide -data-dir") - return 1 - } - - fileLAN := filepath.Join(dataDir, agent.SerfLANKeyring) - if err := initKeyring(fileLAN, init); err != nil { - c.Ui.Error(fmt.Sprintf("Error: %s", err)) - return 1 - } - fileWAN := filepath.Join(dataDir, agent.SerfWANKeyring) - if err := initKeyring(fileWAN, init); err != nil { - c.Ui.Error(fmt.Sprintf("Error: %s", err)) - return 1 - } - - return 0 - } - // All other operations will require a client connection client, err := RPCClient(*rpcAddr) if err != nil { @@ -204,40 +178,6 @@ func (c *KeyringCommand) handleList( } } -// initKeyring will create a keyring file at a given path. -func initKeyring(path, key string) error { - if _, err := base64.StdEncoding.DecodeString(key); err != nil { - return fmt.Errorf("Invalid key: %s", err) - } - - keys := []string{key} - keyringBytes, err := json.Marshal(keys) - if err != nil { - return err - } - - if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { - return err - } - - if _, err := os.Stat(path); err == nil { - return fmt.Errorf("File already exists: %s", path) - } - - fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer fh.Close() - - if _, err := fh.Write(keyringBytes); err != nil { - os.Remove(path) - return err - } - - return nil -} - func (c *KeyringCommand) Help() string { helpText := ` Usage: consul keyring [options] @@ -263,11 +203,6 @@ Options: operation may only be performed on keys which are not currently the primary key. -list List all keys currently in use within the cluster. - -init= Create the initial keyring files for Consul to use - containing the provided key. The -data-dir argument - is required with this option. - -data-dir= The path to the Consul agent's data directory. This - argument is only needed for keyring initialization. -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. ` return strings.TrimSpace(helpText) diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown index 6caa172dd7..3ecbaebc90 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown @@ -19,34 +19,23 @@ responsibility of the operator to ensure that only the required encryption keys are installed on the cluster. You can review the installed keys using the `-list` argument, and remove unneeded keys with `-remove`. -With the exception of the `-init` argument, all operations performed by this -command can only be run against server nodes, and affect both the LAN and -WAN keyrings in lock-step. +All operations performed by this command can only be run against server nodes, +and affect both the LAN and WAN keyrings in lock-step. -All variations of the `keyring` command, unless otherwise specified below, will -return 0 if all nodes reply and there are no errors. If any node fails to reply -or reports failure, the exit code will be 1. +All variations of the `keyring` command return 0 if all nodes reply and there +are no errors. If any node fails to reply or reports failure, the exit code +will be 1. ## Usage Usage: `consul keyring [options]` -Only one actionable argument may be specified per run, including `-init`, -`-list`, `-install`, `-remove`, and `-use`. +Only one actionable argument may be specified per run, including `-list`, +`-install`, `-remove`, and `-use`. The list of available flags are: -* `-init` - Creates the keyring file(s). This is useful to configure initial - encryption keyrings, which can later be mutated using the other arguments in - this command. This argument accepts an ASCII key, which can be generated using - the [keygen command](/docs/commands/keygen.html). Requires the `-data-dir` - argument. - - This operation can be run on both client and server nodes and requires no - network connectivity. - - Returns 0 if the key is successfully configured, or 1 if there were any - problems. +* `-list` - List all keys currently in use within the cluster. * `-install` - Install a new encryption key. This will broadcast the new key to all members in the cluster. @@ -57,10 +46,6 @@ The list of available flags are: * `-remove` - Remove the given key from the cluster. This operation may only be performed on keys which are not currently the primary key. -* `-list` - List all keys currently in use within the cluster. - -* `-data-dir` - The path to Consul's data directory. Used with `-init` only. - * `-rpc-addr` - RPC address of the Consul agent. ## Output