From 8a40f3888c63f23dfb36c4d37e554d20966a80f5 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 12 Sep 2014 17:18:10 -0700 Subject: [PATCH] agent: move keyring initialization out of agent, add -init option to keys command --- command/agent/agent.go | 64 ++++-------------------------- command/agent/command.go | 13 +++---- command/agent/config.go | 16 ++------ command/keys.go | 84 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 94 insertions(+), 83 deletions(-) diff --git a/command/agent/agent.go b/command/agent/agent.go index 06aad6d433..95fcd8bec5 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -20,8 +20,8 @@ import ( ) const ( - serfLANKeyring = "serf/local.keyring" - serfWANKeyring = "serf/remote.keyring" + SerfLANKeyring = "serf/local.keyring" + SerfWANKeyring = "serf/remote.keyring" ) /* @@ -119,18 +119,6 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { // Initialize the local state agent.state.Init(config, agent.logger) - // Setup encryption keyring files - if config.PersistKeyring && config.EncryptKey != "" { - if config.Server { - if err := agent.initKeyringFile(serfWANKeyring); err != nil { - return nil, err - } - } - if err := agent.initKeyringFile(serfLANKeyring); err != nil { - return nil, err - } - } - // Setup either the client or the server var err error if config.Server { @@ -182,16 +170,16 @@ func (a *Agent) consulConfig() *consul.Config { if a.config.DataDir != "" { base.DataDir = a.config.DataDir } - if a.config.EncryptKey != "" && !a.config.PersistKeyring { + if a.config.EncryptKey != "" { key, _ := a.config.EncryptBytes() base.SerfLANConfig.MemberlistConfig.SecretKey = key base.SerfWANConfig.MemberlistConfig.SecretKey = key } - if a.config.PersistKeyring { - lanKeyring := filepath.Join(base.DataDir, serfLANKeyring) - wanKeyring := filepath.Join(base.DataDir, serfWANKeyring) - base.SerfLANConfig.KeyringFile = lanKeyring - base.SerfWANConfig.KeyringFile = wanKeyring + if a.config.Server && a.config.keyringFilesExist() { + pathWAN := filepath.Join(base.DataDir, SerfWANKeyring) + pathLAN := filepath.Join(base.DataDir, SerfLANKeyring) + base.SerfWANConfig.KeyringFile = pathWAN + base.SerfLANConfig.KeyringFile = pathLAN } if a.config.NodeName != "" { base.NodeName = a.config.NodeName @@ -813,39 +801,3 @@ func (a *Agent) RemoveKeyLAN(key string) (*serf.KeyResponse, error) { km := a.client.KeyManagerLAN() return km.RemoveKey(key) } - -// initKeyringFile is used to create and initialize a persistent keyring file -// for gossip encryption keys. It is used at agent startup to dump the initial -// encryption key into a keyfile if persistence is enabled. -func (a *Agent) initKeyringFile(path string) error { - serfDir := filepath.Join(a.config.DataDir, "serf") - if err := os.MkdirAll(serfDir, 0700); err != nil { - return err - } - - keys := []string{a.config.EncryptKey} - keyringBytes, err := json.MarshalIndent(keys, "", " ") - if err != nil { - return err - } - - keyringFile := filepath.Join(a.config.DataDir, path) - - // If the keyring file already exists, don't re-initialize - if _, err := os.Stat(keyringFile); err == nil { - return nil - } - - fh, err := os.OpenFile(keyringFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer fh.Close() - - if _, err := fh.Write(keyringBytes); err != nil { - os.Remove(keyringFile) - return err - } - - return nil -} diff --git a/command/agent/command.go b/command/agent/command.go index 94b6eacd30..bacc099d53 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -67,8 +67,6 @@ func (c *Command) readConfig() *Config { cmdFlags.StringVar(&cmdConfig.UiDir, "ui-dir", "", "path to the web UI directory") cmdFlags.StringVar(&cmdConfig.PidFile, "pid-file", "", "path to file to store PID") cmdFlags.StringVar(&cmdConfig.EncryptKey, "encrypt", "", "gossip encryption key") - cmdFlags.BoolVar(&cmdConfig.PersistKeyring, "persist-keyring", false, "persist keyring changes") - cmdFlags.BoolVar(&cmdConfig.Server, "server", false, "run agent as server") cmdFlags.BoolVar(&cmdConfig.Bootstrap, "bootstrap", false, "enable server bootstrap mode") cmdFlags.IntVar(&cmdConfig.BootstrapExpect, "bootstrap-expect", 0, "enable automatic bootstrap via expect mode") @@ -219,11 +217,10 @@ func (c *Command) readConfig() *Config { c.Ui.Error("WARNING: Windows is not recommended as a Consul server. Do not use in production.") } - // Warn if an encryption key is passed while a keyring already exists - if config.EncryptKey != "" && (config.PersistKeyring && config.CheckKeyringFiles()) { - c.Ui.Error(fmt.Sprintf( - "WARNING: Keyring already exists, ignoring new key %s", - config.EncryptKey)) + // Error if an encryption key is passed while a keyring already exists + if config.EncryptKey != "" && config.keyringFilesExist() { + c.Ui.Error(fmt.Sprintf("Error: -encrypt specified but keyring files exist")) + return nil } // Set the version info @@ -595,7 +592,7 @@ func (c *Command) Run(args []string) int { // Determine if gossip is encrypted gossipEncrypted := false - if config.EncryptKey != "" || (config.PersistKeyring && config.CheckKeyringFiles()) { + if config.EncryptKey != "" || config.keyringFilesExist() { gossipEncrypted = true } diff --git a/command/agent/config.go b/command/agent/config.go index e4c1ee73aa..9334553d00 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -150,11 +150,6 @@ type Config struct { // the TERM signal. Defaults false. This can be changed on reload. LeaveOnTerm bool `mapstructure:"leave_on_terminate"` - // Enable keyring persistence. There are currently two keyrings; one for - // the LAN serf cluster and the other for the WAN. Each will maintain its - // own keyring file in the agent's data directory. - PersistKeyring bool `mapstructure:"persist_keyring"` - // SkipLeaveOnInt controls if Serf skips a graceful leave when receiving // the INT signal. Defaults false. This can be changed on reload. SkipLeaveOnInt bool `mapstructure:"skip_leave_on_interrupt"` @@ -416,13 +411,13 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { return addr.String(), nil } -// CheckKeyringFiles checks for existence of the keyring files for Serf -func (c *Config) CheckKeyringFiles() bool { - if _, err := os.Stat(filepath.Join(c.DataDir, serfLANKeyring)); err != nil { +// keyringFilesExist checks for existence of the keyring files for Serf +func (c *Config) keyringFilesExist() bool { + if _, err := os.Stat(filepath.Join(c.DataDir, SerfLANKeyring)); err != nil { return false } if c.Server { - if _, err := os.Stat(filepath.Join(c.DataDir, serfWANKeyring)); err != nil { + if _, err := os.Stat(filepath.Join(c.DataDir, SerfWANKeyring)); err != nil { return false } } @@ -697,9 +692,6 @@ func MergeConfig(a, b *Config) *Config { if b.EncryptKey != "" { result.EncryptKey = b.EncryptKey } - if b.PersistKeyring { - result.PersistKeyring = true - } if b.LogLevel != "" { result.LogLevel = b.LogLevel } diff --git a/command/keys.go b/command/keys.go index b998535763..8ca98bdf2f 100644 --- a/command/keys.go +++ b/command/keys.go @@ -1,10 +1,15 @@ package command import ( + "encoding/base64" + "encoding/json" "flag" "fmt" + "os" + "path/filepath" "strings" + "github.com/hashicorp/consul/command/agent" "github.com/mitchellh/cli" "github.com/ryanuber/columnize" ) @@ -16,7 +21,7 @@ type KeysCommand struct { } func (c *KeysCommand) Run(args []string) int { - var installKey, useKey, removeKey string + var installKey, useKey, removeKey, init, dataDir string var listKeys, wan bool cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) @@ -27,6 +32,8 @@ func (c *KeysCommand) Run(args []string) int { cmdFlags.StringVar(&removeKey, "remove", "", "remove key") cmdFlags.BoolVar(&listKeys, "list", false, "list keys") cmdFlags.BoolVar(&wan, "wan", false, "operate on wan keys") + cmdFlags.StringVar(&init, "init", "", "initialize keyring") + cmdFlags.StringVar(&dataDir, "data-dir", "", "data directory") rpcAddr := RPCAddrFlag(cmdFlags) if err := cmdFlags.Parse(args); err != nil { @@ -40,15 +47,11 @@ func (c *KeysCommand) Run(args []string) int { Ui: c.Ui, } - var out []string - var failures map[string]string - var err error - // Only accept a single argument found := listKeys - for _, arg := range []string{installKey, useKey, removeKey} { + for _, arg := range []string{installKey, useKey, removeKey, init} { if found && len(arg) > 0 { - c.Ui.Error("Only one of -list, -install, -use, or -remove allowed") + c.Ui.Error("Only a single action is allowed") return 1 } found = found || len(arg) > 0 @@ -60,6 +63,43 @@ func (c *KeysCommand) Run(args []string) int { return 1 } + var out, paths []string + var failures map[string]string + var err error + + if init != "" { + if dataDir == "" { + c.Ui.Error("Must provide -data-dir") + return 1 + } + if _, err := base64.StdEncoding.DecodeString(init); err != nil { + c.Ui.Error(fmt.Sprintf("Invalid key: %s", err)) + return 1 + } + + paths = append(paths, filepath.Join(dataDir, agent.SerfLANKeyring)) + if wan { + paths = append(paths, filepath.Join(dataDir, agent.SerfWANKeyring)) + } + + keys := []string{init} + keyringBytes, err := json.MarshalIndent(keys, "", " ") + if err != nil { + c.Ui.Error(fmt.Sprintf("Error: %s", err)) + return 1 + } + + for _, path := range paths { + if err := initializeKeyring(path, keyringBytes); err != nil { + c.Ui.Error("Error: %s", err) + return 1 + } + } + + return 0 + } + + // All other operations will require a client connection client, err := RPCClient(*rpcAddr) if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) @@ -181,6 +221,30 @@ func (c *KeysCommand) Run(args []string) int { return 0 } +// initializeKeyring will create a keyring file at a given path. +func initializeKeyring(path string, key []byte) error { + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return err + } + + if _, err := os.Stat(path); err == nil { + return fmt.Errorf("File already exists: %s", path) + } + + fh, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer fh.Close() + + if _, err := fh.Write(keyringBytes); err != nil { + os.Remove(path) + return err + } + + return nil +} + func (c *KeysCommand) Help() string { helpText := ` Usage: consul keys [options] @@ -204,6 +268,12 @@ Options: -list List all keys currently in use within the cluster. -wan If talking with a server node, this flag can be used to operate on the WAN gossip layer. + -init= Create an initial keyring file for Consul to use + containing the provided key. By default, this option + will only initialize the LAN keyring. If the -wan + option is also passed, then the wan keyring will be + created as well. The -data-dir argument is required + with this option. -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. ` return strings.TrimSpace(helpText)