command: remove -init argument from keyring, auto-persist keyrings when using agent -encrypt

pull/336/head
Ryan Uber 2014-10-02 22:05:00 -07:00
parent 1f9d13dc73
commit 7a74f559b9
6 changed files with 94 additions and 132 deletions

View File

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

View File

@ -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("")

View File

@ -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) {

View File

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

View File

@ -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=<key> Create the initial keyring files for Consul to use
containing the provided key. The -data-dir argument
is required with this option.
-data-dir=<path> 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)

View File

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