2bba591906
Fixes #7231. Before an agent would always emit a warning when there is an encrypt key in the configuration and an existing keyring stored, which is happening on restart. Now it only emits that warning when the encrypt key from the configuration is not part of the keyring.
195 lines
5.2 KiB
Go
195 lines
5.2 KiB
Go
package agent
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/hashicorp/consul/agent/consul"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/memberlist"
|
|
"github.com/hashicorp/serf/serf"
|
|
)
|
|
|
|
const (
|
|
SerfLANKeyring = "serf/local.keyring"
|
|
SerfWANKeyring = "serf/remote.keyring"
|
|
)
|
|
|
|
// initKeyring will create a keyring file at a given path.
|
|
func initKeyring(path, key string) error {
|
|
var keys []string
|
|
|
|
if keyBytes, err := decodeStringKey(key); err != nil {
|
|
return fmt.Errorf("Invalid key: %s", err)
|
|
} else if err := memberlist.ValidateKey(keyBytes); err != nil {
|
|
return fmt.Errorf("Invalid key: %s", err)
|
|
}
|
|
|
|
// Just exit if the file already exists.
|
|
if _, err := os.Stat(path); err == nil {
|
|
return nil
|
|
}
|
|
|
|
keys = append(keys, key)
|
|
keyringBytes, err := json.Marshal(keys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
|
return err
|
|
}
|
|
|
|
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 {
|
|
if c.KeyringFile == "" {
|
|
return nil
|
|
}
|
|
|
|
if _, err := os.Stat(c.KeyringFile); err != nil {
|
|
return err
|
|
}
|
|
|
|
keyringData, err := ioutil.ReadFile(c.KeyringFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
keys := make([]string, 0)
|
|
if err := json.Unmarshal(keyringData, &keys); err != nil {
|
|
return err
|
|
}
|
|
|
|
return loadKeyring(c, keys)
|
|
}
|
|
|
|
// loadKeyring takes a list of base64-encoded strings and installs them in the
|
|
// given Serf's keyring.
|
|
func loadKeyring(c *serf.Config, keys []string) error {
|
|
keysDecoded := make([][]byte, len(keys))
|
|
for i, key := range keys {
|
|
keyBytes, err := decodeStringKey(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keysDecoded[i] = keyBytes
|
|
}
|
|
|
|
if len(keysDecoded) == 0 {
|
|
return fmt.Errorf("no keys present in keyring: %s", c.KeyringFile)
|
|
}
|
|
|
|
keyring, err := memberlist.NewKeyring(keysDecoded, keysDecoded[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.MemberlistConfig.Keyring = keyring
|
|
return nil
|
|
}
|
|
|
|
func decodeStringKey(key string) ([]byte, error) {
|
|
return base64.StdEncoding.DecodeString(key)
|
|
}
|
|
|
|
// keyringProcess is used to abstract away the semantic similarities in
|
|
// performing various operations on the encryption keyring.
|
|
func (a *Agent) keyringProcess(args *structs.KeyringRequest) (*structs.KeyringResponses, error) {
|
|
var reply structs.KeyringResponses
|
|
|
|
if _, ok := a.delegate.(*consul.Server); !ok {
|
|
return nil, fmt.Errorf("keyring operations must run against a server node")
|
|
}
|
|
if err := a.RPC("Internal.KeyringOperation", args, &reply); err != nil {
|
|
return &reply, err
|
|
}
|
|
|
|
return &reply, nil
|
|
}
|
|
|
|
// ParseRelayFactor validates and converts the given relay factor to uint8
|
|
func ParseRelayFactor(n int) (uint8, error) {
|
|
if n < 0 || n > 5 {
|
|
return 0, fmt.Errorf("Relay factor must be in range: [0, 5]")
|
|
}
|
|
return uint8(n), nil
|
|
}
|
|
|
|
// ValidateLocalOnly validates the local-only flag, requiring that it only be
|
|
// set for list requests.
|
|
func ValidateLocalOnly(local bool, list bool) error {
|
|
if local && !list {
|
|
return fmt.Errorf("local-only can only be set for list requests")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListKeys lists out all keys installed on the collective Consul cluster. This
|
|
// includes both servers and clients in all DC's.
|
|
func (a *Agent) ListKeys(token string, relayFactor uint8) (*structs.KeyringResponses, error) {
|
|
args := structs.KeyringRequest{Operation: structs.KeyringList}
|
|
parseKeyringRequest(&args, token, relayFactor)
|
|
return a.keyringProcess(&args)
|
|
}
|
|
|
|
// InstallKey installs a new gossip encryption key
|
|
func (a *Agent) InstallKey(key, token string, relayFactor uint8) (*structs.KeyringResponses, error) {
|
|
args := structs.KeyringRequest{Key: key, Operation: structs.KeyringInstall}
|
|
parseKeyringRequest(&args, token, relayFactor)
|
|
return a.keyringProcess(&args)
|
|
}
|
|
|
|
// UseKey changes the primary encryption key used to encrypt messages
|
|
func (a *Agent) UseKey(key, token string, relayFactor uint8) (*structs.KeyringResponses, error) {
|
|
args := structs.KeyringRequest{Key: key, Operation: structs.KeyringUse}
|
|
parseKeyringRequest(&args, token, relayFactor)
|
|
return a.keyringProcess(&args)
|
|
}
|
|
|
|
// RemoveKey will remove a gossip encryption key from the keyring
|
|
func (a *Agent) RemoveKey(key, token string, relayFactor uint8) (*structs.KeyringResponses, error) {
|
|
args := structs.KeyringRequest{Key: key, Operation: structs.KeyringRemove}
|
|
parseKeyringRequest(&args, token, relayFactor)
|
|
return a.keyringProcess(&args)
|
|
}
|
|
|
|
func parseKeyringRequest(req *structs.KeyringRequest, token string, relayFactor uint8) {
|
|
req.Token = token
|
|
req.RelayFactor = relayFactor
|
|
}
|
|
|
|
// keyringIsMissingKey checks whether a key is part of a keyring. Returns true
|
|
// if it is not included.
|
|
func keyringIsMissingKey(keyring *memberlist.Keyring, key string) bool {
|
|
k1, err := decodeStringKey(key)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
for _, k2 := range keyring.GetKeys() {
|
|
if bytes.Equal(k1, k2) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|