diff --git a/helper/raftutil/msgtypes.go b/helper/raftutil/msgtypes.go index 9371964d3..efd85afc5 100644 --- a/helper/raftutil/msgtypes.go +++ b/helper/raftutil/msgtypes.go @@ -54,6 +54,10 @@ var msgTypeNames = map[structs.MessageType]string{ structs.ServiceRegistrationUpsertRequestType: "ServiceRegistrationUpsertRequestType", structs.ServiceRegistrationDeleteByIDRequestType: "ServiceRegistrationDeleteByIDRequestType", structs.ServiceRegistrationDeleteByNodeIDRequestType: "ServiceRegistrationDeleteByNodeIDRequestType", + structs.SecureVariableUpsertRequestType: "SecureVariableUpsertRequestType", + structs.SecureVariableDeleteRequestType: "SecureVariableDeleteRequestType", + structs.RootKeyMetaUpsertRequestType: "RootKeyMetaUpsertRequestType", + structs.RootKeyMetaDeleteRequestType: "RootKeyMetaDeleteRequestType", structs.NamespaceUpsertRequestType: "NamespaceUpsertRequestType", structs.NamespaceDeleteRequestType: "NamespaceDeleteRequestType", } diff --git a/nomad/encrypter.go b/nomad/encrypter.go new file mode 100644 index 000000000..1949fbc96 --- /dev/null +++ b/nomad/encrypter.go @@ -0,0 +1,29 @@ +package nomad + +import "crypto/cipher" + +type Encrypter struct { + ciphers map[string]cipher.AEAD // map of key IDs to ciphers +} + +func NewEncrypter() *Encrypter { + return &Encrypter{ + ciphers: make(map[string]cipher.AEAD), + } +} + +// Encrypt takes the serialized map[string][]byte from +// SecureVariable.UnencryptedData, generates an appropriately-sized nonce +// for the algorithm, and encrypts the data with the ciper for the +// CurrentRootKeyID. The buffer returned includes the nonce. +func (e *Encrypter) Encrypt(unencryptedData []byte, keyID string) []byte { + // TODO: actually encrypt! + return unencryptedData +} + +// Decrypt takes an encrypted buffer and then root key ID. It extracts +// the nonce, decrypts the content, and returns the cleartext data. +func (e *Encrypter) Decrypt(encryptedData []byte, keyID string) ([]byte, error) { + // TODO: actually decrypt! + return encryptedData, nil +} diff --git a/nomad/fsm.go b/nomad/fsm.go index 7476d0fe9..c2a53ccec 100644 --- a/nomad/fsm.go +++ b/nomad/fsm.go @@ -55,6 +55,9 @@ const ( ScalingEventsSnapshot SnapshotType = 19 EventSinkSnapshot SnapshotType = 20 ServiceRegistrationSnapshot SnapshotType = 21 + SecureVariablesSnapshot SnapshotType = 22 + SecureVariablesQuotaSnapshot SnapshotType = 23 + RootKeyMetaSnapshot SnapshotType = 24 // Namespace appliers were moved from enterprise and therefore start at 64 NamespaceSnapshot SnapshotType = 64 @@ -314,6 +317,14 @@ func (n *nomadFSM) Apply(log *raft.Log) interface{} { return n.applyDeleteServiceRegistrationByID(msgType, buf[1:], log.Index) case structs.ServiceRegistrationDeleteByNodeIDRequestType: return n.applyDeleteServiceRegistrationByNodeID(msgType, buf[1:], log.Index) + case structs.SecureVariableUpsertRequestType: + return n.applySecureVariableUpsert(msgType, buf[1:], log.Index) + case structs.SecureVariableDeleteRequestType: + return n.applySecureVariableDelete(msgType, buf[1:], log.Index) + case structs.RootKeyMetaUpsertRequestType: + return n.applyRootKeyMetaUpsert(msgType, buf[1:], log.Index) + case structs.RootKeyMetaDeleteRequestType: + return n.applyRootKeyMetaDelete(msgType, buf[1:], log.Index) } // Check enterprise only message types. @@ -1710,6 +1721,36 @@ func (n *nomadFSM) restoreImpl(old io.ReadCloser, filter *FSMFilter) error { } } + case SecureVariablesSnapshot: + variable := new(structs.SecureVariable) + if err := dec.Decode(variable); err != nil { + return err + } + + if err := restore.SecureVariablesRestore(variable); err != nil { + return err + } + + case SecureVariablesQuotaSnapshot: + quota := new(structs.SecureVariablesQuota) + if err := dec.Decode(quota); err != nil { + return err + } + + if err := restore.SecureVariablesQuotaRestore(quota); err != nil { + return err + } + + case RootKeyMetaSnapshot: + keyMeta := new(structs.RootKeyMeta) + if err := dec.Decode(keyMeta); err != nil { + return err + } + + if err := restore.RootKeyMetaRestore(keyMeta); err != nil { + return err + } + default: // Check if this is an enterprise only object being restored restorer, ok := n.enterpriseRestorers[snapType] @@ -1995,6 +2036,68 @@ func (f *FSMFilter) Include(item interface{}) bool { return true } +func (n *nomadFSM) applySecureVariableUpsert(msgType structs.MessageType, buf []byte, index uint64) interface{} { + defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_secure_variable_upsert"}, time.Now()) + var req structs.SecureVariablesUpsertRequest + if err := structs.Decode(buf, &req); err != nil { + panic(fmt.Errorf("failed to decode request: %v", err)) + } + + if err := n.state.UpsertSecureVariables(msgType, index, []*structs.SecureVariable{req.Data}); err != nil { + n.logger.Error("UpsertSecureVariables failed", "error", err) + return err + } + + return nil +} + +func (n *nomadFSM) applySecureVariableDelete(msgType structs.MessageType, buf []byte, index uint64) interface{} { + defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_secure_variable_delete"}, time.Now()) + var req structs.SecureVariablesDeleteRequest + if err := structs.Decode(buf, &req); err != nil { + panic(fmt.Errorf("failed to decode request: %v", err)) + } + + if err := n.state.DeleteSecureVariables(msgType, index, []string{req.Path}); err != nil { + n.logger.Error("DeleteSecureVariables failed", "error", err) + return err + } + + return nil +} + +func (n *nomadFSM) applyRootKeyMetaUpsert(msgType structs.MessageType, buf []byte, index uint64) interface{} { + defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_root_key_meta_upsert"}, time.Now()) + + var req structs.KeyringUpdateRootKeyMetaRequest + if err := structs.Decode(buf, &req); err != nil { + panic(fmt.Errorf("failed to decode request: %v", err)) + } + + if err := n.state.UpsertRootKeyMeta(index, req.RootKeyMeta); err != nil { + n.logger.Error("UpsertRootKeyMeta failed", "error", err) + return err + } + + return nil +} + +func (n *nomadFSM) applyRootKeyMetaDelete(msgType structs.MessageType, buf []byte, index uint64) interface{} { + defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_root_key_meta_delete"}, time.Now()) + + var req structs.KeyringDeleteRootKeyRequest + if err := structs.Decode(buf, &req); err != nil { + panic(fmt.Errorf("failed to decode request: %v", err)) + } + + if err := n.state.DeleteRootKeyMeta(index, req.KeyID); err != nil { + n.logger.Error("DeleteRootKeyMeta failed", "error", err) + return err + } + + return nil +} + func (s *nomadSnapshot) Persist(sink raft.SnapshotSink) error { defer metrics.MeasureSince([]string{"nomad", "fsm", "persist"}, time.Now()) // Register the nodes @@ -2103,6 +2206,18 @@ func (s *nomadSnapshot) Persist(sink raft.SnapshotSink) error { sink.Cancel() return err } + if err := s.persistSecureVariables(sink, encoder); err != nil { + sink.Cancel() + return err + } + if err := s.persistSecureVariablesQuotas(sink, encoder); err != nil { + sink.Cancel() + return err + } + if err := s.persistRootKeyMeta(sink, encoder); err != nil { + sink.Cancel() + return err + } return nil } @@ -2661,6 +2776,75 @@ func (s *nomadSnapshot) persistServiceRegistrations(sink raft.SnapshotSink, } } +func (s *nomadSnapshot) persistSecureVariables(sink raft.SnapshotSink, + encoder *codec.Encoder) error { + + ws := memdb.NewWatchSet() + variables, err := s.snap.SecureVariables(ws) + if err != nil { + return err + } + + for { + raw := variables.Next() + if raw == nil { + break + } + variable := raw.(*structs.SecureVariable) + sink.Write([]byte{byte(SecureVariablesSnapshot)}) + if err := encoder.Encode(variable); err != nil { + return err + } + } + return nil +} + +func (s *nomadSnapshot) persistSecureVariablesQuotas(sink raft.SnapshotSink, + encoder *codec.Encoder) error { + + ws := memdb.NewWatchSet() + quotas, err := s.snap.SecureVariablesQuotas(ws) + if err != nil { + return err + } + + for { + raw := quotas.Next() + if raw == nil { + break + } + dirEntry := raw.(*structs.SecureVariablesQuota) + sink.Write([]byte{byte(SecureVariablesQuotaSnapshot)}) + if err := encoder.Encode(dirEntry); err != nil { + return err + } + } + return nil +} + +func (s *nomadSnapshot) persistRootKeyMeta(sink raft.SnapshotSink, + encoder *codec.Encoder) error { + + ws := memdb.NewWatchSet() + keys, err := s.snap.RootKeyMetas(ws) + if err != nil { + return err + } + + for { + raw := keys.Next() + if raw == nil { + break + } + key := raw.(*structs.RootKeyMeta) + sink.Write([]byte{byte(RootKeyMetaSnapshot)}) + if err := encoder.Encode(key); err != nil { + return err + } + } + return nil +} + // Release is a no-op, as we just need to GC the pointer // to the state store snapshot. There is nothing to explicitly // cleanup. diff --git a/nomad/keyring_endpoint.go b/nomad/keyring_endpoint.go new file mode 100644 index 000000000..d5d2742b1 --- /dev/null +++ b/nomad/keyring_endpoint.go @@ -0,0 +1,95 @@ +package nomad + +import ( + "time" + + metrics "github.com/armon/go-metrics" + "github.com/hashicorp/go-hclog" + + "github.com/hashicorp/nomad/nomad/structs" +) + +// KeyRing endpoint serves RPCs for secure variables key management +type KeyRing struct { + srv *Server + logger hclog.Logger + encrypter *Encrypter +} + +func (k *KeyRing) Rotate(args *structs.KeyringRotateRootKeyRequest, reply *structs.KeyringRotateRootKeyResponse) error { + if done, err := k.srv.forward("KeyRing.Rotate", args, args, reply); done { + return err + } + + defer metrics.MeasureSince([]string{"nomad", "keyring", "rotate"}, time.Now()) + + // TODO: allow for servers to force rotation as well + if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil { + return err + } else if aclObj != nil && !aclObj.IsManagement() { + return structs.ErrPermissionDenied + } + + // TODO: implementation; this just silences the structcheck lint + for keyID := range k.encrypter.ciphers { + k.logger.Trace("TODO", "key", keyID) + } + return nil +} + +func (k *KeyRing) List(args *structs.KeyringListRootKeyMetaRequest, reply *structs.KeyringListRootKeyMetaResponse) error { + if done, err := k.srv.forward("KeyRing.List", args, args, reply); done { + return err + } + + defer metrics.MeasureSince([]string{"nomad", "keyring", "list"}, time.Now()) + + // TODO: probably need to allow for servers to list keys as well, to support replication? + if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil { + return err + } else if aclObj != nil && !aclObj.IsManagement() { + return structs.ErrPermissionDenied + } + + // TODO: implementation + + return nil +} + +func (k *KeyRing) Update(args *structs.KeyringUpdateRootKeyRequest, reply *structs.KeyringUpdateRootKeyResponse) error { + if done, err := k.srv.forward("KeyRing.Update", args, args, reply); done { + return err + } + + defer metrics.MeasureSince([]string{"nomad", "keyring", "update"}, time.Now()) + + // TODO: need to allow for servers to update keys as well, to support replication + if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil { + return err + } else if aclObj != nil && !aclObj.IsManagement() { + return structs.ErrPermissionDenied + } + + // TODO: implementation + + return nil +} + +func (k *KeyRing) Delete(args *structs.KeyringDeleteRootKeyRequest, reply *structs.KeyringDeleteRootKeyResponse) error { + if done, err := k.srv.forward("KeyRing.Delete", args, args, reply); done { + return err + } + + defer metrics.MeasureSince([]string{"nomad", "keyring", "delete"}, time.Now()) + + // TODO: need to allow for servers to delete keys as well, to support replication + if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil { + return err + } else if aclObj != nil && !aclObj.IsManagement() { + return structs.ErrPermissionDenied + } + + // TODO: implementation + + return nil +} diff --git a/nomad/secure_variables_endpoint.go b/nomad/secure_variables_endpoint.go new file mode 100644 index 000000000..0d98cfac2 --- /dev/null +++ b/nomad/secure_variables_endpoint.go @@ -0,0 +1,104 @@ +package nomad + +import ( + "bytes" + "encoding/gob" + "time" + + metrics "github.com/armon/go-metrics" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/nomad/nomad/structs" +) + +// SecureVariables endpoint serves RPCs for storing and retrieving +// encrypted variables +type SecureVariables struct { + srv *Server + logger hclog.Logger + encrypter *Encrypter +} + +func (sv *SecureVariables) Create(args *structs.SecureVariablesUpsertRequest, reply *structs.SecureVariablesUpsertResponse) error { + if done, err := sv.srv.forward("SecureVariables.Create", args, args, reply); done { + return err + } + + defer metrics.MeasureSince([]string{"nomad", "secure_variables", "create"}, time.Now()) + + // TODO: implement real ACL checks + if aclObj, err := sv.srv.ResolveToken(args.AuthToken); err != nil { + return err + } else if aclObj != nil && !aclObj.IsManagement() { + return structs.ErrPermissionDenied + } + + sv.logger.Trace("TODO") // silences structcheck lint + + // TODO: placeholder for serialization and encryption + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(args.Data.UnencryptedData) + if err != nil { + return err + } + args.Data.EncryptedData.Data = sv.encrypter.Encrypt(buf.Bytes(), "TODO") + + return nil +} + +func (sv *SecureVariables) List(args *structs.SecureVariablesListRequest, reply *structs.SecureVariablesListResponse) error { + if done, err := sv.srv.forward("SecureVariables.List", args, args, reply); done { + return err + } + + defer metrics.MeasureSince([]string{"nomad", "secure_variables", "list"}, time.Now()) + + // TODO: implement real ACL checks + if aclObj, err := sv.srv.ResolveToken(args.AuthToken); err != nil { + return err + } else if aclObj != nil && !aclObj.IsManagement() { + return structs.ErrPermissionDenied + } + + // TODO: implementation + + return nil +} + +func (sv *SecureVariables) Read(args *structs.SecureVariablesReadRequest, reply *structs.SecureVariablesReadResponse) error { + if done, err := sv.srv.forward("SecureVariables.Read", args, args, reply); done { + return err + } + + defer metrics.MeasureSince([]string{"nomad", "secure_variables", "read"}, time.Now()) + + // TODO: implement real ACL checks + if aclObj, err := sv.srv.ResolveToken(args.AuthToken); err != nil { + return err + } else if aclObj != nil && !aclObj.IsManagement() { + return structs.ErrPermissionDenied + } + + // TODO: implementation + + return nil +} + +func (sv *SecureVariables) Update(args *structs.SecureVariablesUpsertRequest, reply *structs.SecureVariablesUpsertResponse) error { + if done, err := sv.srv.forward("SecureVariables.Update", args, args, reply); done { + return err + } + + defer metrics.MeasureSince([]string{"nomad", "secure_variables", "update"}, time.Now()) + + // TODO: implement real ACL checks + if aclObj, err := sv.srv.ResolveToken(args.AuthToken); err != nil { + return err + } else if aclObj != nil && !aclObj.IsManagement() { + return structs.ErrPermissionDenied + } + + // TODO: implementation + + return nil +} diff --git a/nomad/state/schema.go b/nomad/state/schema.go index 50c9fb20f..2dde4fc65 100644 --- a/nomad/state/schema.go +++ b/nomad/state/schema.go @@ -12,8 +12,11 @@ import ( const ( tableIndex = "index" - TableNamespaces = "namespaces" - TableServiceRegistrations = "service_registrations" + TableNamespaces = "namespaces" + TableServiceRegistrations = "service_registrations" + TableSecureVariables = "secure_variables" + TableSecureVariablesQuotas = "secure_variables_quota" + TableRootKeyMeta = "secure_variables_root_key_meta" ) const ( @@ -70,6 +73,9 @@ func init() { scalingEventTableSchema, namespaceTableSchema, serviceRegistrationsTableSchema, + secureVariablesTableSchema, + secureVariablesQuotasTableSchema, + secureVariablesRootKeyMetaSchema, }...) } @@ -1202,3 +1208,66 @@ func serviceRegistrationsTableSchema() *memdb.TableSchema { }, } } + +// secureVariablesTableSchema returns the MemDB schema for Nomad +// secure variables. +func secureVariablesTableSchema() *memdb.TableSchema { + return &memdb.TableSchema{ + Name: TableSecureVariables, + Indexes: map[string]*memdb.IndexSchema{ + indexID: { + Name: indexID, + AllowMissing: false, + Unique: true, + Indexer: &memdb.CompoundIndex{ + Indexes: []memdb.Indexer{ + &memdb.StringFieldIndex{ + Field: "Namespace", + }, + &memdb.StringFieldIndex{ + Field: "Path", + }, + }, + }, + }, + }, + } +} + +// secureVariablesQuotasTableSchema returns the MemDB schema for Nomad +// secure variables quotas tracking +func secureVariablesQuotasTableSchema() *memdb.TableSchema { + return &memdb.TableSchema{ + Name: TableSecureVariablesQuotas, + Indexes: map[string]*memdb.IndexSchema{ + indexID: { + Name: indexID, + AllowMissing: false, + Unique: true, + Indexer: &memdb.StringFieldIndex{ + Field: "Namespace", + Lowercase: true, + }, + }, + }, + } +} + +// secureVariablesRootKeyMetaSchema returns the MemDB schema for Nomad +// secure variables root keys +func secureVariablesRootKeyMetaSchema() *memdb.TableSchema { + return &memdb.TableSchema{ + Name: TableRootKeyMeta, + Indexes: map[string]*memdb.IndexSchema{ + indexID: { + Name: indexID, + AllowMissing: false, + Unique: true, + Indexer: &memdb.StringFieldIndex{ + Field: "KeyID", + Lowercase: true, + }, + }, + }, + } +} diff --git a/nomad/state/state_store.go b/nomad/state/state_store.go index 6c11a260a..3543e7cdf 100644 --- a/nomad/state/state_store.go +++ b/nomad/state/state_store.go @@ -6627,3 +6627,59 @@ func (s *StateSnapshot) DenormalizeAllocationDiffSlice(allocDiffs []*structs.All func getPreemptedAllocDesiredDescription(preemptedByAllocID string) string { return fmt.Sprintf("Preempted by alloc ID %v", preemptedByAllocID) } + +// SecureVariables queries all the variables and is used only for +// snapshot/restore and key rotation +func (s *StateStore) SecureVariables(ws memdb.WatchSet) (memdb.ResultIterator, error) { + txn := s.db.ReadTxn() + + iter, err := txn.Get(TableSecureVariables, indexID) + if err != nil { + return nil, err + } + + ws.Add(iter.WatchCh()) + return iter, nil +} + +func (s *StateStore) UpsertSecureVariables(msgType structs.MessageType, index uint64, dirEntries []*structs.SecureVariable) error { + return nil +} + +func (s *StateStore) DeleteSecureVariables(msgType structs.MessageType, index uint64, paths []string) error { + return nil +} + +// SecureVariablesQuotas queries all the quotas and is used only for +// snapshot/restore and key rotation +func (s *StateStore) SecureVariablesQuotas(ws memdb.WatchSet) (memdb.ResultIterator, error) { + txn := s.db.ReadTxn() + + iter, err := txn.Get(TableSecureVariablesQuotas, indexID) + if err != nil { + return nil, err + } + + ws.Add(iter.WatchCh()) + return iter, nil +} + +func (s *StateStore) UpsertRootKeyMeta(index uint64, rootKeyMeta *structs.RootKeyMeta) error { + return nil +} + +func (s *StateStore) DeleteRootKeyMeta(index uint64, keyID string) error { + return nil +} + +func (s *StateStore) RootKeyMetas(ws memdb.WatchSet) (memdb.ResultIterator, error) { + txn := s.db.ReadTxn() + + iter, err := txn.Get(TableRootKeyMeta, indexID) + if err != nil { + return nil, err + } + + ws.Add(iter.WatchCh()) + return iter, nil +} diff --git a/nomad/state/state_store_restore.go b/nomad/state/state_store_restore.go index 3935fb1ef..23fc99ff5 100644 --- a/nomad/state/state_store_restore.go +++ b/nomad/state/state_store_restore.go @@ -197,3 +197,30 @@ func (r *StateRestore) ServiceRegistrationRestore(service *structs.ServiceRegist } return nil } + +// SecureVariablesRestore is used to restore a single secure variable +// into the secure_variables table. +func (r *StateRestore) SecureVariablesRestore(variable *structs.SecureVariable) error { + if err := r.txn.Insert(TableSecureVariables, variable); err != nil { + return fmt.Errorf("secure variable insert failed: %v", err) + } + return nil +} + +// SecureVariablesQuotaRestore is used to restore a single secure variable quota +// into the secure_variables_quota table. +func (r *StateRestore) SecureVariablesQuotaRestore(quota *structs.SecureVariablesQuota) error { + if err := r.txn.Insert(TableSecureVariablesQuotas, quota); err != nil { + return fmt.Errorf("secure variable quota insert failed: %v", err) + } + return nil +} + +// RootKeyMetaQuotaRestore is used to restore a single root key meta +// into the secure_variables_root_key_meta table. +func (r *StateRestore) RootKeyMetaRestore(quota *structs.RootKeyMeta) error { + if err := r.txn.Insert(TableRootKeyMeta, quota); err != nil { + return fmt.Errorf("root key meta insert failed: %v", err) + } + return nil +} diff --git a/nomad/structs/secure_variables.go b/nomad/structs/secure_variables.go new file mode 100644 index 000000000..09d301389 --- /dev/null +++ b/nomad/structs/secure_variables.go @@ -0,0 +1,162 @@ +package structs + +import "time" + +// SecureVariable is the metadata envelope for a Secure Variable +type SecureVariable struct { + Namespace string + Path string + CreateTime time.Time + CreateIndex uint64 + ModifyIndex uint64 + ModifyTime time.Time + + // reserved for post-1.4.0 work + // LockIndex uint64 + // Session string + // DeletedAt time.Time + // Version uint64 + // CustomMetaData map[string]string + + EncryptedData *SecureVariableData // removed during serialization + UnencryptedData map[string]string // empty until serialized +} + +// SecureVariableData is the secret data for a Secure Variable +type SecureVariableData struct { + Data []byte // includes nonce + KeyID string // ID of root key used to encrypt this entry +} + +// SecureVariablesQuota is used to track the total size of secure +// variables entries per namespace. The total length of +// SecureVariable.EncryptedData will be added to the SecureVariablesQuota +// table in the same transaction as a write, update, or delete. +type SecureVariablesQuota struct { + Namespace string + Size uint64 + CreateIndex uint64 + ModifyIndex uint64 +} + +type SecureVariablesUpsertRequest struct { + Data *SecureVariable + WriteRequest +} + +type SecureVariablesUpsertResponse struct { + WriteMeta +} + +type SecureVariablesListRequest struct { + // TODO: do we need any fields here? + QueryOptions +} + +type SecureVariablesListResponse struct { + Data []*SecureVariable + QueryMeta +} + +type SecureVariablesReadRequest struct { + Path string + QueryOptions +} + +type SecureVariablesReadResponse struct { + Data *SecureVariable + QueryMeta +} + +type SecureVariablesDeleteRequest struct { + Path string + WriteRequest +} + +type SecureVariablesDeleteResponse struct { + WriteMeta +} + +// RootKey is used to encrypt and decrypt secure variables. It is +// never stored in raft. +type RootKey struct { + Meta RootKeyMeta + Key []byte // serialized to keystore as base64 blob +} + +// RootKeyMeta is the metadata used to refer to a RootKey. It is +// stored in raft. +type RootKeyMeta struct { + Active bool + KeyID string // UUID + Algorithm EncryptionAlgorithm + EncryptionsCount uint64 + CreateTime time.Time + CreateIndex uint64 + ModifyIndex uint64 +} + +// EncryptionAlgorithm chooses which algorithm is used for +// encrypting / decrypting entries with this key +type EncryptionAlgorithm string + +const ( + EncryptionAlgorithmXChaCha20 EncryptionAlgorithm = "xchacha20" + EncryptionAlgorithmAES256GCM EncryptionAlgorithm = "aes256-gcm" +) + +type KeyringRotateRootKeyRequest struct { + Algorithm EncryptionAlgorithm + Full bool + WriteRequest +} + +// KeyringRotateRootKeyResponse returns the full key metadata +type KeyringRotateRootKeyResponse struct { + Key *RootKeyMeta + WriteMeta +} + +type KeyringListRootKeyMetaRequest struct { + // TODO: do we need any fields here? + QueryOptions +} + +type KeyringListRootKeyMetaResponse struct { + Keys []*RootKeyMeta + QueryMeta +} + +// KeyringUpdateRootKeyRequest is used internally for key replication +// only and for keyring restores. The RootKeyMeta will be extracted +// for applying to the FSM with the KeyringUpdateRootKeyMetaRequest +// (see below) +type KeyringUpdateRootKeyRequest struct { + RootKey *RootKey + WriteRequest +} + +type KeyringUpdateRootKeyResponse struct { + WriteMeta +} + +// KeyringUpdateRootKeyMetaRequest is used internally for key +// replication so that we have a request wrapper for writing the +// metadata to the FSM without including the key material +type KeyringUpdateRootKeyMetaRequest struct { + RootKeyMeta *RootKeyMeta + WriteRequest +} + +type KeyringUpdateRootKeyMetaResponse struct { + WriteMeta +} + +type KeyringDeleteRootKeyRequest struct { + KeyID string + WriteRequest +} + +type KeyringDeleteRootKeyResponse struct { + WriteMeta +} diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index e5f1e7741..c80a01b6f 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -108,6 +108,10 @@ const ( ServiceRegistrationUpsertRequestType MessageType = 47 ServiceRegistrationDeleteByIDRequestType MessageType = 48 ServiceRegistrationDeleteByNodeIDRequestType MessageType = 49 + SecureVariableUpsertRequestType MessageType = 50 + SecureVariableDeleteRequestType MessageType = 51 + RootKeyMetaUpsertRequestType MessageType = 52 + RootKeyMetaDeleteRequestType MessageType = 53 // Namespace types were moved from enterprise and therefore start at 64 NamespaceUpsertRequestType MessageType = 64