From a6f4cf61f2a6e597425eb453000f561a0e1aa89b Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Fri, 2 Aug 2019 00:47:47 +0200 Subject: [PATCH] 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`. --- cmd/crypto/error.go | 2 ++ cmd/crypto/kms.go | 20 ++++++++++++++++++++ cmd/crypto/kms_test.go | 11 ++++++++++- cmd/crypto/vault.go | 27 +++++++++++++++++++++++++++ docs/kms/README.md | 4 ++-- 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/cmd/crypto/error.go b/cmd/crypto/error.go index 5e35148c8..d3a3a2c6a 100644 --- a/cmd/crypto/error.go +++ b/cmd/crypto/error.go @@ -62,6 +62,8 @@ var ( errInvalidInternalIV = Error{"The internal encryption IV is malformed"} 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 ( diff --git a/cmd/crypto/kms.go b/cmd/crypto/kms.go index 3eb9bf8ef..53a27252b 100644 --- a/cmd/crypto/kms.go +++ b/cmd/crypto/kms.go @@ -86,6 +86,19 @@ type KMS interface { // referenced by the keyID. The provided context must // match the context used to generate the sealed key. 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 { @@ -126,6 +139,13 @@ func (kms *masterKeyKMS) UnsealKey(keyID string, sealedKey []byte, ctx Context) 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) { if context == nil { context = Context{} diff --git a/cmd/crypto/kms_test.go b/cmd/crypto/kms_test.go index 8884ee9d2..8e985d04a 100644 --- a/cmd/crypto/kms_test.go +++ b/cmd/crypto/kms_test.go @@ -51,11 +51,20 @@ func TestMasterKeyKMS(t *testing.T) { t.Errorf("Test %d: KMS failed to unseal the generated key: %v", i, err) } 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[:]) { 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) + } + } } diff --git a/cmd/crypto/vault.go b/cmd/crypto/vault.go index fd651294a..1ccd39747 100644 --- a/cmd/crypto/vault.go +++ b/cmd/crypto/vault.go @@ -250,3 +250,30 @@ func (v *vaultService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (k copy(key[:], []byte(plainKey)) 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 +} diff --git a/docs/kms/README.md b/docs/kms/README.md index 6bf4235b4..a19bc916f 100644 --- a/docs/kms/README.md +++ b/docs/kms/README.md @@ -141,8 +141,8 @@ path "transit/datakey/plaintext/my-minio-key" { path "transit/decrypt/my-minio-key" { capabilities = [ "read", "update"] } -path "transit/encrypt/my-minio-key" { - capabilities = [ "read", "update"] +path "transit/rewrap/my-minio-key" { + capabilities = ["update"] } EOF