From e20a72499902c58b2f874e0dcd29062b05fda700 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Fri, 5 Sep 2014 17:22:33 -0700 Subject: [PATCH] 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 9ef0711377..f955d4a3ab 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 6474402f99..91118fc630 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 36683ad16a..e099c64b14 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 9cb1944cbc..10acaab2c6 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