fix: allow parsing keys in both new and old format (#12144)

Bonus fix fallback to decrypt previously
encrypted content as well using older master
key ciphertext format.

Signed-off-by: Harshavardhana <harsha@minio.io>
This commit is contained in:
Harshavardhana 2021-04-24 19:05:25 -07:00 committed by GitHub
parent 5d954ea228
commit f420996dfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 19 deletions

View File

@ -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, ""))

61
docs/kms/IAM.md Normal file
View File

@ -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.

View File

@ -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