mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 18:35:44 +03:00
secure variables: initial state store (#12932)
Implement the core SecureVariable and RootKey structs in memdb, provide the minimal skeleton for FSM, and a dummy storage and keyring RPC endpoint.
This commit is contained in:
@@ -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",
|
||||
}
|
||||
|
||||
29
nomad/encrypter.go
Normal file
29
nomad/encrypter.go
Normal file
@@ -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
|
||||
}
|
||||
184
nomad/fsm.go
184
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.
|
||||
|
||||
95
nomad/keyring_endpoint.go
Normal file
95
nomad/keyring_endpoint.go
Normal file
@@ -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
|
||||
}
|
||||
104
nomad/secure_variables_endpoint.go
Normal file
104
nomad/secure_variables_endpoint.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
162
nomad/structs/secure_variables.go
Normal file
162
nomad/structs/secure_variables.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user