diff --git a/cmd/common-main.go b/cmd/common-main.go index bd3091018..3322a9bca 100644 --- a/cmd/common-main.go +++ b/cmd/common-main.go @@ -341,28 +341,37 @@ func handleCommonEnvVars() { case env.IsSet(config.EnvKMSMasterKey) && env.IsSet(config.EnvKESEndpoint): logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSMasterKey, config.EnvKESEndpoint)) } - if env.IsSet(config.EnvKMSSecretKey) { - KMS, err := kms.Parse(env.Get(config.EnvKMSSecretKey, "")) - if err != nil { - logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") - } - GlobalKMS = KMS - } else if env.IsSet(config.EnvKMSMasterKey) { - logger.LogIf(GlobalContext, errors.New("legacy KMS configuration"), fmt.Sprintf("The environment variable %q is deprecated and will be removed in the future", config.EnvKMSMasterKey)) - v := strings.SplitN(env.Get(config.EnvKMSMasterKey, ""), ":", 2) - if len(v) != 2 { - logger.Fatal(errors.New("invalid "+config.EnvKMSMasterKey), "Unable to parse the KMS secret key inherited from the shell environment") - } - secretKey, err := hex.DecodeString(v[1]) + parseMasterKey := func(key string) error { + KMS, err := kms.Parse(env.Get(key, "")) if err != nil { - logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") - } - KMS, err := kms.New(v[0], secretKey) - if err != nil { - logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") + v := strings.SplitN(env.Get(key, ""), ":", 2) + if len(v) != 2 { + return errors.New("invalid " + key) + } + secretKey, err := hex.DecodeString(v[1]) + if err != nil { + return err + } + KMS, err = kms.New(v[0], secretKey) + if err != nil { + return err + } } GlobalKMS = KMS + return nil + } + + if env.IsSet(config.EnvKMSSecretKey) { + if err = parseMasterKey(config.EnvKMSSecretKey); err != nil { + logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") + } + } else if env.IsSet(config.EnvKMSMasterKey) { + logger.LogIf(GlobalContext, errors.New("legacy KMS configuration"), + fmt.Sprintf("The environment variable %q is deprecated and will be removed in the future", config.EnvKMSMasterKey)) + if err = parseMasterKey(config.EnvKMSMasterKey); err != nil { + logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment") + } } if env.IsSet(config.EnvKESEndpoint) { kesEndpoints, err := crypto.ParseKESEndpoints(env.Get(config.EnvKESEndpoint, "")) diff --git a/docs/kms/IAM.md b/docs/kms/IAM.md new file mode 100644 index 000000000..b805b5285 --- /dev/null +++ b/docs/kms/IAM.md @@ -0,0 +1,61 @@ +# KMS IAM/Config Encryption + +MinIO will soon release a change that re-works the encryption of IAM and +configuration data. Currently, MinIO encrypts IAM data (user/temp. credentials, +policies and other configuration data) with the cluster root credentials before +storing it on the backend disks. After release `RELEASE.2021-04-22T15-44-28Z` +onwards, MinIO will use the KMS provided keys to encrypt the IAM data instead +of the cluster root credentials. If the KMS is not enabled, MinIO will store +the IAM data as plain text in its backend. + +### FAQ + +> Why is this change needed? Was the previous encryption not secure (enough)? + +The previous mechanism of encryption using the cluster root credentials itself +is secure. The purpose of this change is to improve the encryption key +management by delegating it to the KMS. Once the cluster root credentials are no +longer needed to decrypt persistent data at the backend, they can be rotated +much more easily. By using a KMS to protect the IAM data a potential security or +compliance team has much more control over how/when a MinIO cluster can be +started. + +> Does this mean I need an enterprise KMS setup to run MinIO (securely)? + +No, MinIO does not depend on any third-party KMS provider. You have three options here: + +- Run MinIO without a KMS. In this case all IAM data will be stored in plain-text. + +- Run MinIO with a single secret key. MinIO supports a static cryptographic key + that can act as minimal KMS. With this method all IAM data will be stored + encrypted. The encryption key has to be passed as environment variable. + +- Run MinIO with KES (minio/kes) in combination with any supported KMS as + secure key store. For example, you can run MinIO + KES + Hashicorp Vault. + +> What about an exiting MinIO deployment? Can I just upgrade my cluster? + +Yes, MinIO will try to transparently migrate any existing IAM data and either stores +it in plaintext (no KMS) or re-encrypts using the KMS. + +> Is this change backward compatible? Will it break my setup? + +This change is not backward compatible for all setups. In particular, the native +Hashicorp Vault integration - which has been deprecated already - won't be +supported anymore. KES is now mandatory if a third-party KMS should be used. + +Further, since the configuration data is encrypted with the KMS, the KMS +configuration itself can no longer be stored in the MinIO config file and +instead must be provided via environment variables. If you have set your KMS +configuration using e.g. the `mc admin config` commands you will need to adjust +your deployment. + +Even though this change is backward compatible we do not expect that it affects +the vast majority of deployments in any negative way. + +> Will an upgrade of an existing MinIO cluster impact the SLA of the cluster or will it even cause downtime? + +No, an upgrade should not cause any downtime. However, on the first startup - +since MinIO will attempt to migrate any existing IAM data - the boot process may +take slightly longer, but may not be visibly noticeable. Once the migration has +completed, any subsequent restart should be as fast as before or even faster. diff --git a/pkg/kms/single-key.go b/pkg/kms/single-key.go index 3009dc1e3..6354f1947 100644 --- a/pkg/kms/single-key.go +++ b/pkg/kms/single-key.go @@ -28,7 +28,9 @@ import ( "fmt" "strconv" "strings" + "unicode/utf8" + "github.com/minio/sio" "github.com/secure-io/sio-go/sioutil" "golang.org/x/crypto/chacha20" "golang.org/x/crypto/chacha20poly1305" @@ -55,7 +57,7 @@ func Parse(s string) (KMS, error) { } // New returns a single-key KMS that derives new DEKs from the -// given key. The given key must always be 32 bytes. +// given key. func New(keyID string, key []byte) (KMS, error) { if len(key) != 32 { return nil, errors.New("kms: invalid key length " + strconv.Itoa(len(key))) @@ -168,11 +170,39 @@ func (kms secretKey) GenerateKey(keyID string, context Context) (DEK, error) { }, nil } +func (kms secretKey) legacyDecryptKey(keyID string, sealedKey []byte, ctx Context) ([]byte, error) { + var derivedKey = kms.deriveKey(keyID, ctx) + + var key [32]byte + out, err := sio.DecryptBuffer(key[:0], sealedKey, sio.Config{Key: derivedKey[:]}) + if err != nil || len(out) != 32 { + return nil, err // TODO(aead): upgrade sio to use sio.Error + } + return key[:], nil +} + +func (kms secretKey) deriveKey(keyID string, context Context) (key [32]byte) { + if context == nil { + context = Context{} + } + ctxBytes, _ := context.MarshalText() + + mac := hmac.New(sha256.New, kms.key[:]) + mac.Write([]byte(keyID)) + mac.Write(ctxBytes) + mac.Sum(key[:0]) + return key +} + func (kms secretKey) DecryptKey(keyID string, ciphertext []byte, context Context) ([]byte, error) { if keyID != kms.keyID { return nil, fmt.Errorf("kms: key %q does not exist", keyID) } + if !utf8.Valid(ciphertext) { + return kms.legacyDecryptKey(keyID, ciphertext, context) + } + var encryptedKey encryptedKey if err := json.Unmarshal(ciphertext, &encryptedKey); err != nil { return nil, err