agent: move keyring initialization out of agent, add -init option to keys command

pull/336/head
Ryan Uber 2014-09-12 17:18:10 -07:00
parent 0952535e33
commit 8a40f3888c
4 changed files with 94 additions and 83 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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=<key> 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)