Initial transit key archiving work

This commit is contained in:
Jeff Mitchell 2016-01-15 14:02:51 -05:00
parent 1769984368
commit beafe25508
2 changed files with 187 additions and 6 deletions

View File

@ -57,6 +57,10 @@ func pathConfigWrite(
minDecryptionVersion := d.Get("min_decryption_version").(int)
if minDecryptionVersion != 0 &&
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
persistNeeded = true
}

View File

@ -6,6 +6,7 @@ import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
@ -72,11 +73,174 @@ type Policy struct {
// for decryption
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
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 {
err := p.handleArchiving(storage, name)
if err != nil {
return err
}
// Encode the policy
buf, err := p.Serialize()
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
// mode is used with the context to derive the proper key.
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 {
return nil, certutil.InternalError{Err: "unable to access the key; no key versions found"}
}
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"}
}
if ver <= 0 || ver > len(p.Keys) {
if ver <= 0 || ver > p.LatestVersion {
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
key, err := p.DeriveKey(context, len(p.Keys))
key, err := p.DeriveKey(context, p.LatestVersion)
if err != nil {
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)
// Prepend some information
encoded = "vault:v" + strconv.Itoa(len(p.Keys)) + ":" + encoded
encoded = "vault:v" + strconv.Itoa(p.LatestVersion) + ":" + encoded
return encoded, nil
}
@ -273,7 +437,10 @@ func (p *Policy) rotate(storage logical.Storage) error {
if err != nil {
return err
}
p.Keys[len(p.Keys)+1] = KeyEntry{
p.LatestVersion += 1
p.Keys[p.LatestVersion] = KeyEntry{
Key: newKey,
CreationTime: time.Now().Unix(),
}
@ -324,10 +491,20 @@ func getPolicy(req *logical.Request, name string) (*Policy, error) {
return nil, err
}
persistNeeded := false
// Ensure we've moved from Key -> Keys
if p.Key != nil && len(p.Key) > 0 {
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)
if err != nil {
return nil, err