Initial transit key archiving work
This commit is contained in:
parent
1769984368
commit
beafe25508
|
@ -57,6 +57,10 @@ func pathConfigWrite(
|
||||||
minDecryptionVersion := d.Get("min_decryption_version").(int)
|
minDecryptionVersion := d.Get("min_decryption_version").(int)
|
||||||
if minDecryptionVersion != 0 &&
|
if minDecryptionVersion != 0 &&
|
||||||
minDecryptionVersion != policy.MinDecryptionVersion {
|
minDecryptionVersion != policy.MinDecryptionVersion {
|
||||||
|
if minDecryptionVersion > policy.LatestVersion {
|
||||||
|
return logical.ErrorResponse(
|
||||||
|
fmt.Sprintf("cannot set min decryption version of %d, latest key version is %d", minDecryptionVersion, policy.LatestVersion)), nil
|
||||||
|
}
|
||||||
policy.MinDecryptionVersion = minDecryptionVersion
|
policy.MinDecryptionVersion = minDecryptionVersion
|
||||||
persistNeeded = true
|
persistNeeded = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -72,11 +73,174 @@ type Policy struct {
|
||||||
// for decryption
|
// for decryption
|
||||||
MinDecryptionVersion int `json:"min_decryption_version"`
|
MinDecryptionVersion int `json:"min_decryption_version"`
|
||||||
|
|
||||||
|
// The latest key version in this policy
|
||||||
|
LatestVersion int `json:"latest_version"`
|
||||||
|
|
||||||
|
// The latest key version in the archive. We never delete these, so this is a max.
|
||||||
|
ArchiveVersion int `json:"archive_version"`
|
||||||
|
|
||||||
// Whether the key is allowed to be deleted
|
// Whether the key is allowed to be deleted
|
||||||
DeletionAllowed bool `json:"deletion_allowed"`
|
DeletionAllowed bool `json:"deletion_allowed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ArchivedKeys stores old keys. This is used to keep the key loading time sane when
|
||||||
|
// there are huge numbers of rotations.
|
||||||
|
type ArchivedKeys struct {
|
||||||
|
Keys []KeyEntry `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) loadArchive(storage logical.Storage, name string) (*ArchivedKeys, error) {
|
||||||
|
archive := &ArchivedKeys{}
|
||||||
|
|
||||||
|
raw, err := storage.Get("policy/" + name + "/archive")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if raw == nil {
|
||||||
|
archive.Keys = make([]KeyEntry, 0)
|
||||||
|
return archive, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(raw.Value, archive); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return archive, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) storeArchive(archive *ArchivedKeys, storage logical.Storage, name string) error {
|
||||||
|
// Encode the policy
|
||||||
|
buf, err := json.Marshal(archive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the policy into storage
|
||||||
|
err = storage.Put(&logical.StorageEntry{
|
||||||
|
Key: "policy/" + name + "/archive",
|
||||||
|
Value: buf,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleArchiving manages the movement of keys to and from the policy archive.
|
||||||
|
// This should *ONLY* be called from Persist() since it assumes that the policy
|
||||||
|
// will be persisted afterwards.
|
||||||
|
func (p *Policy) handleArchiving(storage logical.Storage, name string) error {
|
||||||
|
// We need to move keys that are no longer accessible to ArchivedKeys, and keys
|
||||||
|
// that now need to be accessible back here.
|
||||||
|
//
|
||||||
|
// For safety, because there isn't really a good reason to, we never delete
|
||||||
|
// keys from the archive even when we move them back.
|
||||||
|
|
||||||
|
// 0/1 are aliases, so don't deal with this code path unless we're past that
|
||||||
|
if p.MinDecryptionVersion < 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have the latest minimum version in the current set of keys
|
||||||
|
_, keysContainsMinimum := p.Keys[p.MinDecryptionVersion]
|
||||||
|
|
||||||
|
// If keys contains the minimum value, we are moving keys *to* the archive,
|
||||||
|
// but we only need to do this if the archive doesn't contain those key
|
||||||
|
// versions, since we don't remove key versions from the archive.
|
||||||
|
if keysContainsMinimum &&
|
||||||
|
p.ArchiveVersion >= p.MinDecryptionVersion-1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
archive, err := p.loadArchive(storage, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if keysContainsMinimum {
|
||||||
|
// Need to move keys *to* archive
|
||||||
|
|
||||||
|
if len(archive.Keys) < p.MinDecryptionVersion-1 {
|
||||||
|
// Increase the size of the archive slice. We need a size that is
|
||||||
|
// equivalent to the minimum decryption version minus 1, but adding
|
||||||
|
// one since slice numbering starts at 0 and we're indexing by key
|
||||||
|
// version
|
||||||
|
newKeys := make([]KeyEntry, p.MinDecryptionVersion)
|
||||||
|
copy(newKeys, archive.Keys)
|
||||||
|
archive.Keys = newKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// As we are archiving progressively, we should only have to archive
|
||||||
|
// from the min version down to the latest version minus however many
|
||||||
|
// keys are in the policy's map. For example, if we have never
|
||||||
|
// archived, the latest version is 10, and we move the min decryption
|
||||||
|
// version to 5, we will archive from 4 down to (10-10) non-inclusive.
|
||||||
|
// If the latest version now becomes 8, we will archive from 7 down to
|
||||||
|
// (10-6) non-inclusive, e.g. keys 5, 6, and 7.
|
||||||
|
for i := p.LatestVersion - len(p.Keys) + 1; i < p.MinDecryptionVersion; i-- {
|
||||||
|
archive.Keys[i] = p.Keys[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.storeArchive(archive, storage, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform deletion afterwards so that if there is an error saving we
|
||||||
|
// haven't messed with the current policy
|
||||||
|
for i := p.LatestVersion - len(p.Keys) + 1; i < p.MinDecryptionVersion; i-- {
|
||||||
|
delete(p.Keys, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the archive max key version. This also corresponds to the
|
||||||
|
// maximum safe index into the slice. Continuing our example from
|
||||||
|
// before, p.ArchiveVersion will now be 7.
|
||||||
|
p.ArchiveVersion = p.MinDecryptionVersion - 1
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Need to move keys *from* archive
|
||||||
|
|
||||||
|
// If we've been archiving, keys should have been archived
|
||||||
|
// sequentially. So we can perform a sanity check. First test the
|
||||||
|
// actual latest version in the policy, so continuing the previous
|
||||||
|
// example, if the key version is 10 and the minimum was 8, p.Keys
|
||||||
|
// should hold 8, 9, and 10. Now if we move the minimum back, e.g. to
|
||||||
|
// 5, we need to load keys 5, 6, and 7, so should load everything up to
|
||||||
|
// (10-3), inclusive. If p.ArchiveVersion is less than this (which it
|
||||||
|
// shouldn't be, as set earlier in the example), we have a problem.
|
||||||
|
// Also, we should never have a situation where the Archive version is
|
||||||
|
// less than the minimum decryption version but we also do not have the
|
||||||
|
// minimum version in p.Keys (which is the only way we'd be in this
|
||||||
|
// code path to begin with). That's also a problem.
|
||||||
|
//
|
||||||
|
// Note that we *should never have these problems*. If we do it's
|
||||||
|
// serious.
|
||||||
|
if p.ArchiveVersion < p.LatestVersion-len(p.Keys) ||
|
||||||
|
p.ArchiveVersion < p.MinDecryptionVersion {
|
||||||
|
return fmt.Errorf("latest archived key version not high enough to satisfy request")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := p.MinDecryptionVersion; i <= p.LatestVersion-len(p.Keys); i++ {
|
||||||
|
_, ok := p.Keys[i]
|
||||||
|
if ok {
|
||||||
|
// We hit the beginning of the values currently in the keyset,
|
||||||
|
// so break
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.Keys[i] = archive.Keys[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Policy) Persist(storage logical.Storage, name string) error {
|
func (p *Policy) Persist(storage logical.Storage, name string) error {
|
||||||
|
err := p.handleArchiving(storage, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Encode the policy
|
// Encode the policy
|
||||||
buf, err := p.Serialize()
|
buf, err := p.Serialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -104,18 +268,18 @@ func (p *Policy) Serialize() ([]byte, error) {
|
||||||
// raw key is used and no context is required, otherwise the KDF
|
// raw key is used and no context is required, otherwise the KDF
|
||||||
// mode is used with the context to derive the proper key.
|
// mode is used with the context to derive the proper key.
|
||||||
func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
||||||
if p.Keys == nil || len(p.Keys) == 0 {
|
if p.Keys == nil || p.LatestVersion == 0 {
|
||||||
if p.Key == nil || len(p.Key) == 0 {
|
if p.Key == nil || len(p.Key) == 0 {
|
||||||
return nil, certutil.InternalError{Err: "unable to access the key; no key versions found"}
|
return nil, certutil.InternalError{Err: "unable to access the key; no key versions found"}
|
||||||
}
|
}
|
||||||
p.migrateKeyToKeysMap()
|
p.migrateKeyToKeysMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.Keys) == 0 {
|
if p.LatestVersion == 0 {
|
||||||
return nil, certutil.InternalError{Err: "unable to access the key; no key versions found"}
|
return nil, certutil.InternalError{Err: "unable to access the key; no key versions found"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ver <= 0 || ver > len(p.Keys) {
|
if ver <= 0 || ver > p.LatestVersion {
|
||||||
return nil, certutil.UserError{Err: "invalid key version"}
|
return nil, certutil.UserError{Err: "invalid key version"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +311,7 @@ func (p *Policy) Encrypt(context []byte, value string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive the key that should be used
|
// Derive the key that should be used
|
||||||
key, err := p.DeriveKey(context, len(p.Keys))
|
key, err := p.DeriveKey(context, p.LatestVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", certutil.InternalError{Err: err.Error()}
|
return "", certutil.InternalError{Err: err.Error()}
|
||||||
}
|
}
|
||||||
|
@ -188,7 +352,7 @@ func (p *Policy) Encrypt(context []byte, value string) (string, error) {
|
||||||
encoded := base64.StdEncoding.EncodeToString(full)
|
encoded := base64.StdEncoding.EncodeToString(full)
|
||||||
|
|
||||||
// Prepend some information
|
// Prepend some information
|
||||||
encoded = "vault:v" + strconv.Itoa(len(p.Keys)) + ":" + encoded
|
encoded = "vault:v" + strconv.Itoa(p.LatestVersion) + ":" + encoded
|
||||||
|
|
||||||
return encoded, nil
|
return encoded, nil
|
||||||
}
|
}
|
||||||
|
@ -273,7 +437,10 @@ func (p *Policy) rotate(storage logical.Storage) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.Keys[len(p.Keys)+1] = KeyEntry{
|
|
||||||
|
p.LatestVersion += 1
|
||||||
|
|
||||||
|
p.Keys[p.LatestVersion] = KeyEntry{
|
||||||
Key: newKey,
|
Key: newKey,
|
||||||
CreationTime: time.Now().Unix(),
|
CreationTime: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
@ -324,10 +491,20 @@ func getPolicy(req *logical.Request, name string) (*Policy, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
persistNeeded := false
|
||||||
// Ensure we've moved from Key -> Keys
|
// Ensure we've moved from Key -> Keys
|
||||||
if p.Key != nil && len(p.Key) > 0 {
|
if p.Key != nil && len(p.Key) > 0 {
|
||||||
p.migrateKeyToKeysMap()
|
p.migrateKeyToKeysMap()
|
||||||
|
persistNeeded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// With archiving, past assumptions about the length of the keys map are no longer valid
|
||||||
|
if p.LatestVersion == 0 && len(p.Keys) != 0 {
|
||||||
|
p.LatestVersion = len(p.Keys)
|
||||||
|
persistNeeded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if persistNeeded {
|
||||||
err = p.Persist(req.Storage, name)
|
err = p.Persist(req.Storage, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
Loading…
Reference in a new issue