Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
useKeyCommand = "use-key"
removeKeyCommand = "remove-key"
listKeysCommand = "list-keys"
getPrimaryKeyCommand = "get-primary-key"
tagsCommand = "tags"
queryCommand = "query"
respondCommand = "respond"
Expand Down
13 changes: 13 additions & 0 deletions client/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,19 @@ func (c *RPCClient) ListKeys() (map[string]int, int, map[string]string, error) {
return resp.Keys, resp.NumNodes, resp.Messages, err
}

// GetPrimaryKey returns the current encyption key used in cluster
func (c *RPCClient) GetPrimaryKey() (map[string]int, int, map[string]string, error) {
header := requestHeader{
Command: getPrimaryKeyCommand,
Seq: c.getSeq(),
}

resp := keyResponse{}
err := c.genericRPC(&header, nil, &resp)

return resp.Keys, resp.NumNodes, resp.Messages, err
}

// Stats is used to get debugging state information
func (c *RPCClient) Stats() (map[string]map[string]string, error) {
header := requestHeader{
Expand Down
8 changes: 8 additions & 0 deletions cmd/serf/command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,14 @@ func (a *Agent) ListKeys() (*serf.KeyResponse, error) {
return manager.ListKeys()
}

// GetPrimaryKey sends a query to all members to return a list containing
// the primary encryption key
func (a *Agent) GetPrimaryKey() (*serf.KeyResponse, error) {
a.logger.Print("[INFO] agent: Initiating primary key retrieval")
manager := a.serf.KeyManager()
return manager.GetPrimaryKey()
}

// SetTags is used to update the tags. The agent will make sure to
// persist tags if necessary before gossiping to the cluster.
func (a *Agent) SetTags(tags map[string]string) error {
Expand Down
22 changes: 22 additions & 0 deletions cmd/serf/command/agent/ipc.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const (
useKeyCommand = "use-key"
removeKeyCommand = "remove-key"
listKeysCommand = "list-keys"
getPrimaryKeyCommand = "get-primary-key"
tagsCommand = "tags"
queryCommand = "query"
respondCommand = "respond"
Expand Down Expand Up @@ -518,6 +519,9 @@ func (i *AgentIPC) handleRequest(client *IPCClient, reqHeader *requestHeader) er
case listKeysCommand:
return i.handleListKeys(client, seq)

case getPrimaryKeyCommand:
return i.handleGetPrimaryKey(client, seq)

case tagsCommand:
return i.handleTags(client, seq)

Expand Down Expand Up @@ -820,6 +824,24 @@ func (i *AgentIPC) handleListKeys(client *IPCClient, seq uint64) error {
return client.Send(&header, &resp)
}

func (i *AgentIPC) handleGetPrimaryKey(client *IPCClient, seq uint64) error {
queryResp, err := i.agent.GetPrimaryKey()

header := responseHeader{
Seq: seq,
Error: errToString(err),
}
resp := keyResponse{
Messages: queryResp.Messages,
Keys: queryResp.Keys,
NumNodes: queryResp.NumNodes,
NumErr: queryResp.NumErr,
NumResp: queryResp.NumResp,
}

return client.Send(&header, &resp)
}

func (i *AgentIPC) handleStream(client *IPCClient, seq uint64) error {
var es *eventStream
var req streamRequest
Expand Down
40 changes: 37 additions & 3 deletions cmd/serf/command/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Options:
will ask all nodes in the cluster for a list of keys
and dump a summary containing each key and the
number of members it is installed on to the console.
-getPrimaryKey Obtain the primary key used for encrypting messages.
-rpc-addr=127.0.0.1:7373 RPC address of the Serf agent.
-rpc-auth="" RPC auth token of the Serf agent.
`
Expand All @@ -58,14 +59,15 @@ Options:
func (c *KeysCommand) Run(args []string) int {
var installKey, useKey, removeKey string
var lines []string
var listKeys bool
var listKeys, getPrimaryKey bool

cmdFlags := flag.NewFlagSet("key", flag.ContinueOnError)
cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
cmdFlags.StringVar(&installKey, "install", "", "install a new key")
cmdFlags.StringVar(&useKey, "use", "", "change primary encryption key")
cmdFlags.StringVar(&removeKey, "remove", "", "remove a key")
cmdFlags.BoolVar(&listKeys, "list", false, "list cluster keys")
cmdFlags.BoolVar(&getPrimaryKey, "getPrimaryKey", false, "get primary encryption key")
rpcAddr := RPCAddrFlag(cmdFlags)
rpcAuth := RPCAuthFlag(cmdFlags)
if err := cmdFlags.Parse(args); err != nil {
Expand All @@ -80,10 +82,14 @@ func (c *KeysCommand) Run(args []string) int {
}

// Make sure that we only have one actionable argument to avoid ambiguity
found := listKeys
found := listKeys || getPrimaryKey
if listKeys && getPrimaryKey {
c.Ui.Error("Only one of -getPrimaryKey or -list allowed")
return 1
}
for _, arg := range []string{installKey, useKey, removeKey} {
if found && len(arg) > 0 {
c.Ui.Error("Only one of -install, -use, -remove, or -list allowed")
c.Ui.Error("Only one of -install, -use, -remove, -getPrimaryKey, or -list allowed")
return 1
}
found = found || len(arg) > 0
Expand Down Expand Up @@ -132,6 +138,34 @@ func (c *KeysCommand) Run(args []string) int {
return 0
}

if getPrimaryKey {
c.Ui.Info("Getting the primary encryption key")
keys, _, failures, err := client.GetPrimaryKey()

if err != nil {
if len(failures) > 0 {
for node, message := range failures {
lines = append(lines, fmt.Sprintf("failed: | %s | %s", node, message))
}
out := columnize.SimpleFormat(lines)
c.Ui.Error(out)
}

c.Ui.Error("")
c.Ui.Error(fmt.Sprintf("Failed to gather member keys: %s", err))
return 1
}

c.Ui.Info("Key obtained")
c.Ui.Output("")

for key := range keys {
c.Ui.Output(key)
}

return 0
}

if installKey != "" {
c.Ui.Info("Installing key on all members...")
if failures, err := client.InstallKey(installKey); err != nil {
Expand Down
27 changes: 27 additions & 0 deletions serf/internal_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const (
// listKeysQuery is used to list all known keys in the cluster
listKeysQuery = "list-keys"

// getPrimaryKeyQuery is used to obtain the current primary key
getPrimaryKeyQuery = "get-primary-key"

// minEncodedKeyLength is used to compute the max number of keys in a list key
// response. eg 1024/25 = 40. a message with max size of 1024 bytes cannot
// contain more than 40 keys. There is a test
Expand Down Expand Up @@ -118,6 +121,8 @@ func (s *serfQueries) handleQuery(q *Query) {
s.handleRemoveKey(q)
case listKeysQuery:
s.handleListKeys(q)
case getPrimaryKeyQuery:
s.handleGetPrimaryKey(q)
default:
s.logger.Printf("[WARN] serf: Unhandled internal query '%s'", queryName)
}
Expand Down Expand Up @@ -365,3 +370,25 @@ func (s *serfQueries) handleListKeys(q *Query) {
SEND:
s.sendKeyResponse(q, &response)
}

// handleGetPrimaryKey is invoked when a query is received to return the current
// primary key used by the Serf instance in the same fasion as handleListKeys method
func (s *serfQueries) handleGetPrimaryKey(q *Query) {
response := nodeKeyResponse{Result: false}
keyring := s.serf.config.MemberlistConfig.Keyring

if !s.serf.EncryptionEnabled() {
response.Message = "Keyring is empty (encryption not enabled)"
s.logger.Printf("[ERR] serf: Keyring is empty (encryption not enabled)")
goto SEND
}

s.logger.Printf("[INFO] serf: Received get-primary-key query")

response.Keys = append(response.Keys,
base64.StdEncoding.EncodeToString(keyring.GetPrimaryKey()))
response.Result = true

SEND:
s.sendKeyResponse(q, &response)
}
14 changes: 14 additions & 0 deletions serf/keymanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,17 @@ func (k *KeyManager) ListKeysWithOptions(opts *KeyRequestOptions) (*KeyResponse,

return k.handleKeyRequest("", listKeysQuery, opts)
}

// GetPrimaryKey is used to obtain the currently used primary key from members in a Serf cluster
// and returns the response formatted as usual, but containing the single key in the list
// for WAN and LAN keyrings
func (k *KeyManager) GetPrimaryKey() (*KeyResponse, error) {
return k.GetPrimaryKeyWithOptions(nil)
}

func (k *KeyManager) GetPrimaryKeyWithOptions(opts *KeyRequestOptions) (*KeyResponse, error) {
k.l.RLock()
defer k.l.RUnlock()

return k.handleKeyRequest("", getPrimaryKeyQuery, opts)
}
58 changes: 58 additions & 0 deletions serf/keymanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,61 @@ func TestSerf_ListKeys(t *testing.T) {
}
}
}

func TestSerf_GetPrimaryKey(t *testing.T) {
ip1, returnFn1 := testutil.TakeIP()
defer returnFn1()

s1, err := testKeyringSerf(t, ip1)
if err != nil {
t.Fatalf("err: %v", err)
}
defer s1.Shutdown()

manager := s1.KeyManager()

initialPrimaryKeyBytes := s1.config.MemberlistConfig.Keyring.GetKeys()[0]
initialPrimaryKey := base64.StdEncoding.EncodeToString(initialPrimaryKeyBytes)

response, err := manager.GetPrimaryKey()
if err != nil {
t.Fatalf("err: %v", err)
}

if len(response.Keys) != 1 {
t.Fatalf("Expected single key in keyring: %s, but got %v", initialPrimaryKey, response.Keys)
}

// should be single key there
for k := range response.Keys {
if k != string(initialPrimaryKey) {
t.Fatalf("Expected to find single primary key equals %s, but got %s", initialPrimaryKey, k)
}
}

extraKey := "5K9OtfP7efFrNKe5WCQvXvnaXJ5cWP0SvXiwe0kkjM4="
extraKeyBytes, err := base64.StdEncoding.DecodeString(extraKey)
if err != nil {
t.Fatalf("err: %v", err)
}

s1.config.MemberlistConfig.Keyring.AddKey(extraKeyBytes)
s1.config.MemberlistConfig.Keyring.UseKey(extraKeyBytes)

response, err = manager.GetPrimaryKey()
if err != nil {
t.Fatalf("err: %v", err)
}

if len(response.Keys) != 1 {
t.Fatalf("Expected single key in keyring even after adding the key: %s, but got %v", initialPrimaryKey, response.Keys)
}

// should be single key there
for k := range response.Keys {
if k != extraKey {
t.Fatalf("Expected to find single primary key equals %s, but got %s", extraKey, k)
}
}

}