mirror of
https://github.com/minio/minio.git
synced 2025-01-11 23:13:23 -05:00
add UpdateKey
method to KMS interface (#7974)
This commit adds a new method `UpdateKey` to the KMS interface. The purpose of `UpdateKey` is to re-wrap an encrypted data key (the key generated & encrypted with a master key by e.g. Vault). For example, consider Vault with a master key ID: `master-key-1` and an encrypted data key `E(dk)` for a particular object. The data key `dk` has been generated randomly when the object was created. Now, the KMS operator may "rotate" the master key `master-key-1`. However, the KMS cannot forget the "old" value of that master key since there is still an object that requires `dk`, and therefore, the `D(E(dk))`. With the `UpdateKey` method call MinIO can ask the KMS to decrypt `E(dk)` with the old key (internally) and re-encrypted `dk` with the new master key value: `E'(dk)`. However, this operation only works for the same master key ID. When rotating the data key (replacing it with a new one) then we perform a `UnsealKey` operation with the 1st master key ID and then a `GenerateKey` operation with the 2nd master key ID. This commit also updates the KMS documentation and removes the `encrypt` policy entry (we don't use `encrypt`) and add a policy entry for `rewarp`.
This commit is contained in:
parent
dfa8835720
commit
a6f4cf61f2
@ -62,6 +62,8 @@ var (
|
|||||||
|
|
||||||
errInvalidInternalIV = Error{"The internal encryption IV is malformed"}
|
errInvalidInternalIV = Error{"The internal encryption IV is malformed"}
|
||||||
errInvalidInternalSealAlgorithm = Error{"The internal seal algorithm is invalid and not supported"}
|
errInvalidInternalSealAlgorithm = Error{"The internal seal algorithm is invalid and not supported"}
|
||||||
|
|
||||||
|
errMissingUpdatedKey = Error{"The key update returned no error but also no sealed key"}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -86,6 +86,19 @@ type KMS interface {
|
|||||||
// referenced by the keyID. The provided context must
|
// referenced by the keyID. The provided context must
|
||||||
// match the context used to generate the sealed key.
|
// match the context used to generate the sealed key.
|
||||||
UnsealKey(keyID string, sealedKey []byte, context Context) (key [32]byte, err error)
|
UnsealKey(keyID string, sealedKey []byte, context Context) (key [32]byte, err error)
|
||||||
|
|
||||||
|
// UpdateKey re-wraps the sealedKey if the master key, referenced by
|
||||||
|
// `keyID`, has changed in the meantime. This usually happens when the
|
||||||
|
// KMS operator performs a key-rotation operation of the master key.
|
||||||
|
// UpdateKey fails if the provided sealedKey cannot be decrypted using
|
||||||
|
// the master key referenced by keyID.
|
||||||
|
//
|
||||||
|
// UpdateKey makes no guarantees whatsoever about whether the returned
|
||||||
|
// rotatedKey is actually different from the sealedKey. If nothing has
|
||||||
|
// changed at the KMS or if the KMS does not support updating generated
|
||||||
|
// keys this method may behave like a NOP and just return the sealedKey
|
||||||
|
// itself.
|
||||||
|
UpdateKey(keyID string, sealedKey []byte, context Context) (rotatedKey []byte, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type masterKeyKMS struct {
|
type masterKeyKMS struct {
|
||||||
@ -126,6 +139,13 @@ func (kms *masterKeyKMS) UnsealKey(keyID string, sealedKey []byte, ctx Context)
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (kms *masterKeyKMS) UpdateKey(keyID string, sealedKey []byte, ctx Context) ([]byte, error) {
|
||||||
|
if _, err := kms.UnsealKey(keyID, sealedKey, ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sealedKey, nil // The master key cannot update data keys -> Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
func (kms *masterKeyKMS) deriveKey(keyID string, context Context) (key [32]byte) {
|
func (kms *masterKeyKMS) deriveKey(keyID string, context Context) (key [32]byte) {
|
||||||
if context == nil {
|
if context == nil {
|
||||||
context = Context{}
|
context = Context{}
|
||||||
|
@ -51,11 +51,20 @@ func TestMasterKeyKMS(t *testing.T) {
|
|||||||
t.Errorf("Test %d: KMS failed to unseal the generated key: %v", i, err)
|
t.Errorf("Test %d: KMS failed to unseal the generated key: %v", i, err)
|
||||||
}
|
}
|
||||||
if err == nil && test.ShouldFail {
|
if err == nil && test.ShouldFail {
|
||||||
t.Errorf("Test %d: KMS unsealed the generated successfully but should have failed", i)
|
t.Errorf("Test %d: KMS unsealed the generated key successfully but should have failed", i)
|
||||||
}
|
}
|
||||||
if !test.ShouldFail && !bytes.Equal(key[:], unsealedKey[:]) {
|
if !test.ShouldFail && !bytes.Equal(key[:], unsealedKey[:]) {
|
||||||
t.Errorf("Test %d: The generated and unsealed key differ", i)
|
t.Errorf("Test %d: The generated and unsealed key differ", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rotatedKey, err := kms.UpdateKey(test.UnsealKeyID, sealedKey, test.UnsealContext)
|
||||||
|
if err == nil && test.ShouldFail {
|
||||||
|
t.Errorf("Test %d: KMS updated the generated key successfully but should have failed", i)
|
||||||
|
}
|
||||||
|
if !test.ShouldFail && !bytes.Equal(rotatedKey, sealedKey[:]) {
|
||||||
|
t.Errorf("Test %d: The updated and sealed key differ", i)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,3 +250,30 @@ func (v *vaultService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (k
|
|||||||
copy(key[:], []byte(plainKey))
|
copy(key[:], []byte(plainKey))
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateKey re-wraps the sealedKey if the master key referenced by the keyID
|
||||||
|
// has been changed by the KMS operator - i.e. the master key has been rotated.
|
||||||
|
// If the master key hasn't changed since the sealedKey has been created / updated
|
||||||
|
// it may return the same sealedKey as rotatedKey.
|
||||||
|
//
|
||||||
|
// The context must be same context as the one provided while
|
||||||
|
// generating the plaintext key / sealedKey.
|
||||||
|
func (v *vaultService) UpdateKey(keyID string, sealedKey []byte, ctx Context) (rotatedKey []byte, err error) {
|
||||||
|
var contextStream bytes.Buffer
|
||||||
|
ctx.WriteTo(&contextStream)
|
||||||
|
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"ciphertext": string(sealedKey),
|
||||||
|
"context": base64.StdEncoding.EncodeToString(contextStream.Bytes()),
|
||||||
|
}
|
||||||
|
s, err := v.client.Logical().Write(fmt.Sprintf("/transit/rewrap/%s", keyID), payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ciphertext, ok := s.Data["ciphertext"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errMissingUpdatedKey
|
||||||
|
}
|
||||||
|
rotatedKey = ciphertext.([]byte)
|
||||||
|
return rotatedKey, nil
|
||||||
|
}
|
||||||
|
@ -141,8 +141,8 @@ path "transit/datakey/plaintext/my-minio-key" {
|
|||||||
path "transit/decrypt/my-minio-key" {
|
path "transit/decrypt/my-minio-key" {
|
||||||
capabilities = [ "read", "update"]
|
capabilities = [ "read", "update"]
|
||||||
}
|
}
|
||||||
path "transit/encrypt/my-minio-key" {
|
path "transit/rewrap/my-minio-key" {
|
||||||
capabilities = [ "read", "update"]
|
capabilities = ["update"]
|
||||||
}
|
}
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
Loading…
Reference in New Issue
Block a user