admin: new API for creating KMS master keys (#9982)

This commit adds a new admin API for creating master keys.
An admin client can send a POST request to:
```
/minio/admin/v3/kms/key/create?key-id=<keyID>
```

The name / ID of the new key is specified as request
query parameter `key-id=<ID>`.

Creating new master keys requires KES - it does not work with
the native Vault KMS (deprecated) nor with a static master key
(deprecated).

Further, this commit removes the `UpdateKey` method from the `KMS`
interface. This method is not needed and not used anymore.
This commit is contained in:
Andreas Auernhammer
2020-07-09 03:50:43 +02:00
committed by GitHub
parent ee20ebe07a
commit a317a2531c
11 changed files with 118 additions and 83 deletions

View File

@@ -34,9 +34,9 @@ import (
xnet "github.com/minio/minio/pkg/net"
)
// ErrKESKeyNotFound is the error returned a KES server
// when a master key does not exist.
var ErrKESKeyNotFound = NewKESError(http.StatusNotFound, "key does not exist")
// ErrKESKeyExists is the error returned a KES server
// when a master key does exist.
var ErrKESKeyExists = NewKESError(http.StatusBadRequest, "key does already exist")
// KesConfig contains the configuration required
// to initialize and connect to a kes server.
@@ -134,20 +134,27 @@ func NewKes(cfg KesConfig) (KMS, error) {
}, nil
}
// KeyID returns the default key ID.
func (kes *kesService) KeyID() string {
// DefaultKeyID returns the default key ID that should be
// used for SSE-S3 or SSE-KMS when the S3 client does not
// provide an explicit key ID.
func (kes *kesService) DefaultKeyID() string {
return kes.defaultKeyID
}
// Info returns some status information about the KMS.
// Info returns some information about the KES,
// configuration - like the endpoint or authentication
// method.
func (kes *kesService) Info() KMSInfo {
return KMSInfo{
Endpoint: kes.endpoint,
Name: kes.KeyID(),
Name: kes.DefaultKeyID(),
AuthType: "TLS",
}
}
// CreateKey tries to create a new master key with the given keyID.
func (kes *kesService) CreateKey(keyID string) error { return kes.client.CreateKey(keyID) }
// GenerateKey returns a new plaintext key, generated by the KMS,
// and a sealed version of this plaintext key encrypted using the
// named key referenced by keyID. It also binds the generated key
@@ -158,12 +165,6 @@ func (kes *kesService) GenerateKey(keyID string, ctx Context) (key [32]byte, sea
var plainKey []byte
plainKey, sealedKey, err = kes.client.GenerateDataKey(keyID, context.Bytes())
if err == ErrKESKeyNotFound { // Try to create the key if it does not exist.
if err = kes.client.CreateKey(keyID); err != nil {
return key, nil, err
}
plainKey, sealedKey, err = kes.client.GenerateDataKey(keyID, context.Bytes())
}
if err != nil {
return key, nil, err
}
@@ -197,28 +198,11 @@ func (kes *kesService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (k
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 (kes *kesService) UpdateKey(keyID string, sealedKey []byte, ctx Context) ([]byte, error) {
_, err := kes.UnsealKey(keyID, sealedKey, ctx)
if err != nil {
return nil, err
}
// Currently a kes server does not support key rotation (of the same key)
// Therefore, we simply return the same sealedKey.
return sealedKey, nil
}
// kesClient implements the bare minimum functionality needed for
// MinIO to talk to a KES server. In particular, it implements
// GenerateDataKey (API: /v1/key/generate/) and
// DecryptDataKey (API: /v1/key/decrypt/).
// • CreateKey (API: /v1/key/create/)
// • GenerateDataKey (API: /v1/key/generate/)
// • DecryptDataKey (API: /v1/key/decrypt/)
type kesClient struct {
addr string
httpClient http.Client

View File

@@ -72,8 +72,14 @@ func (c Context) WriteTo(w io.Writer) (n int64, err error) {
// data key generation and unsealing of KMS-generated
// data keys.
type KMS interface {
// KeyID - returns configured KMS key id.
KeyID() string
// DefaultKeyID returns the default master key ID. It should be
// used for SSE-S3 and whenever a S3 client requests SSE-KMS but
// does not specify an explicit SSE-KMS key ID.
DefaultKeyID() string
// CreateKey creates a new master key with the given key ID
// at the KMS.
CreateKey(keyID string) error
// GenerateKey generates a new random data key using
// the master key referenced by the keyID. It returns
@@ -90,21 +96,9 @@ type KMS interface {
// 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)
// Returns KMSInfo
Info() (kmsInfo KMSInfo)
// Info returns descriptive information about the KMS,
// like the default key ID and authentication method.
Info() KMSInfo
}
type masterKeyKMS struct {
@@ -112,7 +106,8 @@ type masterKeyKMS struct {
masterKey [32]byte
}
// KMSInfo stores the details of KMS
// KMSInfo contains some describing information about
// the KMS.
type KMSInfo struct {
Endpoint string
Name string
@@ -125,10 +120,14 @@ type KMSInfo struct {
// to the generated keys.
func NewMasterKey(keyID string, key [32]byte) KMS { return &masterKeyKMS{keyID: keyID, masterKey: key} }
func (kms *masterKeyKMS) KeyID() string {
func (kms *masterKeyKMS) DefaultKeyID() string {
return kms.keyID
}
func (kms *masterKeyKMS) CreateKey(keyID string) error {
return errors.New("crypto: creating keys is not supported by a static master key")
}
func (kms *masterKeyKMS) GenerateKey(keyID string, ctx Context) (key [32]byte, sealedKey []byte, err error) {
if _, err = io.ReadFull(rand.Reader, key[:]); err != nil {
logger.CriticalIf(context.Background(), errOutOfEntropy)
@@ -166,13 +165,6 @@ 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{}

View File

@@ -57,15 +57,6 @@ func TestMasterKeyKMS(t *testing.T) {
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)
}
}
}

View File

@@ -51,8 +51,8 @@ func TestParseMasterKey(t *testing.T) {
if !tt.success && err == nil {
t.Error("Unexpected failure")
}
if err == nil && kms.KeyID() != tt.expectedKeyID {
t.Errorf("Expected keyID %s, got %s", tt.expectedKeyID, kms.KeyID())
if err == nil && kms.DefaultKeyID() != tt.expectedKeyID {
t.Errorf("Expected keyID %s, got %s", tt.expectedKeyID, kms.DefaultKeyID())
}
})
}

View File

@@ -17,6 +17,7 @@ package crypto
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"strings"
"time"
@@ -190,20 +191,34 @@ func (v *vaultService) authenticate() (err error) {
return
}
// KeyID - vault configured keyID
func (v *vaultService) KeyID() string {
// DefaultKeyID returns the default key ID that should be
// used for SSE-S3 or SSE-KMS when the S3 client does not
// provide an explicit key ID.
func (v *vaultService) DefaultKeyID() string {
return v.config.Key.Name
}
// Returns - vault info
func (v *vaultService) Info() (kmsInfo KMSInfo) {
// Info returns some information about the Vault,
// configuration - like the endpoint or authentication
// method.
func (v *vaultService) Info() KMSInfo {
return KMSInfo{
Endpoint: v.config.Endpoint,
Name: v.config.Key.Name,
Name: v.DefaultKeyID(),
AuthType: v.config.Auth.Type,
}
}
// CreateKey is a stub that exists such that the Vault
// client implements the KMS interface. It always returns
// a not-implemented error.
//
// Creating keys requires a KES instance between MinIO and Vault.
func (v *vaultService) CreateKey(keyID string) error {
// Creating new keys requires KES.
return errors.New("crypto: creating keys is not supported by Vault")
}
// GenerateKey returns a new plaintext key, generated by the KMS,
// and a sealed version of this plaintext key encrypted using the
// named key referenced by keyID. It also binds the generated key