diff --git a/api/keyring_test.go b/api/keyring_test.go index f20e7fdfdd482123b89172c6e60ca13f7a09bb65..119a40aec9017fd0163086adb67e685ad2611b4c 100644 --- a/api/keyring_test.go +++ b/api/keyring_test.go @@ -27,8 +27,7 @@ func TestKeyring_CRUD(t *testing.T) { keys, qm, err := kr.List(&QueryOptions{WaitIndex: key.CreateIndex}) require.NoError(t, err) assertQueryMeta(t, qm) - // TODO: there'll be 2 keys here once we get bootstrapping done - require.Len(t, keys, 1) + require.Len(t, keys, 2) // Write a new active key, forcing a rotation id := "fd77c376-9785-4c80-8e62-4ec3ab5f8b9a" @@ -57,8 +56,12 @@ func TestKeyring_CRUD(t *testing.T) { keys, qm, err = kr.List(&QueryOptions{WaitIndex: key.CreateIndex}) require.NoError(t, err) assertQueryMeta(t, qm) - // TODO: there'll be 2 keys here once we get bootstrapping done - require.Len(t, keys, 1) - require.Equal(t, id, keys[0].KeyID) - + require.Len(t, keys, 2) + for _, key := range keys { + if key.KeyID == id { + require.True(t, key.Active, "new key should be active") + } else { + require.False(t, key.Active, "initial key should be inactive") + } + } } diff --git a/command/agent/keyring_endpoint_test.go b/command/agent/keyring_endpoint_test.go index 46d408837934944f9e68543a3ad58e74e94159bc..a79a6bc0b5e49d7ed9d9db4318fcf28e34989026 100644 --- a/command/agent/keyring_endpoint_test.go +++ b/command/agent/keyring_endpoint_test.go @@ -32,6 +32,7 @@ func TestHTTP_Keyring_CRUD(t *testing.T) { rotateResp := obj.(structs.KeyringRotateRootKeyResponse) require.NotNil(t, rotateResp.Key) require.True(t, rotateResp.Key.Active) + newID1 := rotateResp.Key.KeyID // List @@ -40,8 +41,14 @@ func TestHTTP_Keyring_CRUD(t *testing.T) { obj, err = s.Server.KeyringRequest(respW, req) require.NoError(t, err) listResp := obj.([]*structs.RootKeyMeta) - require.Len(t, listResp, 1) - require.True(t, listResp[0].Active) + require.Len(t, listResp, 2) + for _, key := range listResp { + if key.KeyID == newID1 { + require.True(t, key.Active, "new key should be active") + } else { + require.False(t, key.Active, "initial key should be inactive") + } + } // Update @@ -51,12 +58,12 @@ func TestHTTP_Keyring_CRUD(t *testing.T) { encodedKey := make([]byte, base64.StdEncoding.EncodedLen(32)) base64.StdEncoding.Encode(encodedKey, keyBuf) - newID := uuid.Generate() + newID2 := uuid.Generate() key := &api.RootKey{ Meta: &api.RootKeyMeta{ Active: true, - KeyID: newID, + KeyID: newID2, Algorithm: api.EncryptionAlgorithm(keyMeta.Algorithm), EncryptionsCount: 500, }, @@ -84,9 +91,15 @@ func TestHTTP_Keyring_CRUD(t *testing.T) { obj, err = s.Server.KeyringRequest(respW, req) require.NoError(t, err) listResp = obj.([]*structs.RootKeyMeta) - require.Len(t, listResp, 1) - require.True(t, listResp[0].Active) - require.Equal(t, newID, listResp[0].KeyID) - + require.Len(t, listResp, 2) + + for _, key := range listResp { + require.NotEqual(t, newID1, key.KeyID) + if key.KeyID == newID2 { + require.True(t, key.Active, "new key should be active") + } else { + require.False(t, key.Active, "initial key should be inactive") + } + } }) } diff --git a/nomad/encrypter_test.go b/nomad/encrypter_test.go index 759746c9c82fc951e0f2a2c25a9e607c48aaea6e..f9f62517a176f295e764ed937dfd35871a7dcb53 100644 --- a/nomad/encrypter_test.go +++ b/nomad/encrypter_test.go @@ -57,9 +57,19 @@ func TestEncrypter_Restore(t *testing.T) { defer shutdown() testutil.WaitForLeader(t, srv.RPC) codec := rpcClient(t, srv) - nodeID := srv.GetConfig().NodeID + // Verify we have a bootstrap key + + listReq := &structs.KeyringListRootKeyMetaRequest{ + QueryOptions: structs.QueryOptions{ + Region: "global", + }, + } + var listResp structs.KeyringListRootKeyMetaResponse + msgpackrpc.CallWithCodec(codec, "Keyring.List", listReq, &listResp) + require.Len(t, listResp.Keys, 1) + // Send a few key rotations to add keys rotateReq := &structs.KeyringRotateRootKeyRequest{ @@ -89,15 +99,9 @@ func TestEncrypter_Restore(t *testing.T) { // Verify we've restored all the keys from the old keystore - listReq := &structs.KeyringListRootKeyMetaRequest{ - QueryOptions: structs.QueryOptions{ - Region: "global", - }, - } - var listResp structs.KeyringListRootKeyMetaResponse err := msgpackrpc.CallWithCodec(codec, "Keyring.List", listReq, &listResp) require.NoError(t, err) - require.Len(t, listResp.Keys, 4) + require.Len(t, listResp.Keys, 5) // 4 new + the bootstrap key for _, keyMeta := range listResp.Keys { diff --git a/nomad/keyring_endpoint_test.go b/nomad/keyring_endpoint_test.go index c2f505f4a403a60bfa77c0bbf1a8284feddfd074..c6bed763d6c8eb1b17cd742e04855954e23ca86c 100644 --- a/nomad/keyring_endpoint_test.go +++ b/nomad/keyring_endpoint_test.go @@ -85,7 +85,7 @@ func TestKeyringEndpoint_CRUD(t *testing.T) { // wait for the blocking query to complete and check the response wg.Wait() require.Greater(t, listResp.Index, getResp.Index) - require.Len(t, listResp.Keys, 1) + require.Len(t, listResp.Keys, 2) // bootstrap + new one // Delete the key and verify that it's gone @@ -117,7 +117,7 @@ func TestKeyringEndpoint_CRUD(t *testing.T) { err = msgpackrpc.CallWithCodec(codec, "Keyring.List", listReq, &listResp) require.NoError(t, err) require.Greater(t, listResp.Index, getResp.Index) - require.Len(t, listResp.Keys, 0) + require.Len(t, listResp.Keys, 1) // just the bootstrap key } // TestKeyringEndpoint_validateUpdate exercises all the various @@ -215,7 +215,6 @@ func TestKeyringEndpoint_Rotate(t *testing.T) { // Setup an existing key key, err := structs.NewRootKey(structs.EncryptionAlgorithmXChaCha20) require.NoError(t, err) - id := key.Meta.KeyID key.Meta.Active = true updateReq := &structs.KeyringUpdateRootKeyRequest{ @@ -245,6 +244,8 @@ func TestKeyringEndpoint_Rotate(t *testing.T) { require.NoError(t, err) require.NotEqual(t, updateResp.Index, rotateResp.Index) + newID := rotateResp.Key.KeyID + // Verify we have a new key and the old one is inactive listReq := &structs.KeyringListRootKeyMetaRequest{ @@ -257,15 +258,13 @@ func TestKeyringEndpoint_Rotate(t *testing.T) { require.NoError(t, err) require.Greater(t, listResp.Index, updateResp.Index) - require.Len(t, listResp.Keys, 2) + require.Len(t, listResp.Keys, 3) // bootstrap + old + new - var newID string for _, keyMeta := range listResp.Keys { - if keyMeta.KeyID == id { - require.False(t, keyMeta.Active, "expected old key to be inactive") + if keyMeta.KeyID != newID { + require.False(t, keyMeta.Active, "expected old keys to be inactive") } else { require.True(t, keyMeta.Active, "expected new key to be inactive") - newID = keyMeta.KeyID } } diff --git a/nomad/leader.go b/nomad/leader.go index d4b40f943807c323973dd034b7550a2232cb1a58..2267a4d9956a324780ad1297e7a546b919be738e 100644 --- a/nomad/leader.go +++ b/nomad/leader.go @@ -292,6 +292,12 @@ func (s *Server) establishLeadership(stopCh chan struct{}) error { // Initialize scheduler configuration s.getOrCreateSchedulerConfig() + // Create the first root key if it doesn't already exist + err := s.initializeKeyring() + if err != nil { + return err + } + // Initialize the ClusterID _, _ = s.ClusterID() // todo: use cluster ID for stuff, later! @@ -1678,6 +1684,46 @@ func (s *Server) getOrCreateSchedulerConfig() *structs.SchedulerConfiguration { return config } +// initializeKeyring creates the first root key if the leader doesn't +// already have one. The metadata will be replicated via raft and then +// the followers will get the key material from their own key +// replication. +func (s *Server) initializeKeyring() error { + + store := s.fsm.State() + keyMeta, err := store.GetActiveRootKeyMeta(nil) + if err != nil { + return err + } + if keyMeta != nil { + return nil + } + + s.logger.Named("core").Trace("initializing keyring") + + // TODO: algorithm should be set from config + rootKey, err := structs.NewRootKey(structs.EncryptionAlgorithmXChaCha20) + rootKey.Meta.Active = true + if err != nil { + return fmt.Errorf("could not initialize keyring: %v", err) + } + + err = s.staticEndpoints.Keyring.encrypter.AddKey(rootKey) + if err != nil { + return fmt.Errorf("could not add initial key to keyring: %v", err) + } + + if _, _, err = s.raftApply(structs.RootKeyMetaUpsertRequestType, + structs.KeyringUpdateRootKeyMetaRequest{ + RootKeyMeta: rootKey.Meta, + }); err != nil { + return fmt.Errorf("could not initialize keyring: %v", err) + } + + s.logger.Named("core").Info("initialized keyring", "id", rootKey.Meta.KeyID) + return nil +} + func (s *Server) generateClusterID() (string, error) { if !ServersMeetMinimumVersion(s.Members(), minClusterIDVersion, false) { s.logger.Named("core").Warn("cannot initialize cluster ID until all servers are above minimum version", "min_version", minClusterIDVersion) diff --git a/nomad/server.go b/nomad/server.go index 4e104c23c74a40c863ec43946fcbc2e023e956af..797ea7f8d5bcec766d8277234e5eb3cc04b3efbc 100644 --- a/nomad/server.go +++ b/nomad/server.go @@ -1176,6 +1176,7 @@ func (s *Server) setupRpcServer(server *rpc.Server, ctx *RPCContext) error { s.staticEndpoints.Search = &Search{srv: s, logger: s.logger.Named("search")} s.staticEndpoints.Namespace = &Namespace{srv: s} s.staticEndpoints.SecureVariables = &SecureVariables{srv: s, logger: s.logger.Named("secure_variables"), encrypter: encrypter} + s.staticEndpoints.Keyring = &Keyring{srv: s, logger: s.logger.Named("keyring"), encrypter: encrypter} s.staticEndpoints.Enterprise = NewEnterpriseEndpoints(s) @@ -1233,7 +1234,7 @@ func (s *Server) setupRpcServer(server *rpc.Server, ctx *RPCContext) error { node := &Node{srv: s, ctx: ctx, logger: s.logger.Named("client")} plan := &Plan{srv: s, ctx: ctx, logger: s.logger.Named("plan")} serviceReg := &ServiceRegistration{srv: s, ctx: ctx} - keyringReg := &Keyring{srv: s, logger: s.logger.Named("keyring"), encrypter: encrypter} + keyringReg := &Keyring{srv: s, ctx: ctx, logger: s.logger.Named("keyring"), encrypter: encrypter} // Register the dynamic endpoints server.Register(alloc)