mirror of
https://github.com/minio/minio.git
synced 2025-11-10 22:10:12 -05:00
kms: encrypt IAM/config data with the KMS (#12041)
This commit changes the config/IAM encryption process. Instead of encrypting config data (users, policies etc.) with the root credentials MinIO now encrypts this data with a KMS - if configured. Therefore, this PR moves the MinIO-KMS configuration (via env. variables) to a "top-level" configuration. The KMS configuration cannot be stored in the config file since it is used to decrypt the config file in the first place. As a consequence, this commit also removes support for Hashicorp Vault - which has been deprecated anyway. Signed-off-by: Andreas Auernhammer <aead@mail.de>
This commit is contained in:
committed by
Harshavardhana
parent
e05e14309c
commit
3455f786fa
@@ -33,7 +33,6 @@ import (
|
||||
"github.com/minio/minio/cmd/config/identity/openid"
|
||||
"github.com/minio/minio/cmd/config/policy/opa"
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
@@ -154,11 +153,6 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure to write backend is encrypted
|
||||
if globalConfigEncrypted {
|
||||
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||
}
|
||||
|
||||
if dynamic {
|
||||
// Apply dynamic values.
|
||||
if err := applyDynamicConfig(GlobalContext, objectAPI, cfg); err != nil {
|
||||
@@ -411,11 +405,6 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure to write backend is encrypted
|
||||
if globalConfigEncrypted {
|
||||
saveConfig(GlobalContext, objectAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||
}
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
@@ -454,10 +443,6 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
||||
off = !cache.Enabled(kv)
|
||||
case config.StorageClassSubSys:
|
||||
off = !storageclass.Enabled(kv)
|
||||
case config.KmsVaultSubSys:
|
||||
off = !crypto.EnabledVault(kv)
|
||||
case config.KmsKesSubSys:
|
||||
off = !crypto.EnabledKes(kv)
|
||||
case config.PolicyOPASubSys:
|
||||
off = !opa.Enabled(kv)
|
||||
case config.IdentityOpenIDSubSys:
|
||||
|
||||
@@ -1756,32 +1756,32 @@ func fetchKMSStatus() madmin.KMS {
|
||||
}
|
||||
if len(stat.Endpoints) == 0 {
|
||||
kmsStat.Status = stat.Name
|
||||
return kmsStat
|
||||
}
|
||||
if err := checkConnection(stat.Endpoints[0], 15*time.Second); err != nil {
|
||||
kmsStat.Status = string(madmin.ItemOffline)
|
||||
return kmsStat
|
||||
}
|
||||
kmsStat.Status = string(madmin.ItemOnline)
|
||||
|
||||
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
||||
// 1. Generate a new key using the KMS.
|
||||
key, err := GlobalKMS.GenerateKey("", kmsContext)
|
||||
if err != nil {
|
||||
kmsStat.Encrypt = fmt.Sprintf("Encryption failed: %v", err)
|
||||
} else {
|
||||
if err := checkConnection(stat.Endpoints[0], 15*time.Second); err != nil {
|
||||
kmsStat.Status = string(madmin.ItemOffline)
|
||||
} else {
|
||||
kmsStat.Status = string(madmin.ItemOnline)
|
||||
kmsStat.Encrypt = "success"
|
||||
}
|
||||
|
||||
kmsContext := kms.Context{"MinIO admin API": "ServerInfoHandler"} // Context for a test key operation
|
||||
// 1. Generate a new key using the KMS.
|
||||
key, err := GlobalKMS.GenerateKey("", kmsContext)
|
||||
if err != nil {
|
||||
kmsStat.Encrypt = fmt.Sprintf("Encryption failed: %v", err)
|
||||
} else {
|
||||
kmsStat.Encrypt = "success"
|
||||
}
|
||||
|
||||
// 2. Verify that we can indeed decrypt the (encrypted) key
|
||||
decryptedKey, err := GlobalKMS.DecryptKey(key.KeyID, key.Ciphertext, kmsContext)
|
||||
switch {
|
||||
case err != nil:
|
||||
kmsStat.Decrypt = fmt.Sprintf("Decryption failed: %v", err)
|
||||
case subtle.ConstantTimeCompare(key.Plaintext, decryptedKey) != 1:
|
||||
kmsStat.Decrypt = "Decryption failed: decrypted key does not match generated key"
|
||||
default:
|
||||
kmsStat.Decrypt = "success"
|
||||
}
|
||||
}
|
||||
// 2. Verify that we can indeed decrypt the (encrypted) key
|
||||
decryptedKey, err := GlobalKMS.DecryptKey(key.KeyID, key.Ciphertext, kmsContext)
|
||||
switch {
|
||||
case err != nil:
|
||||
kmsStat.Decrypt = fmt.Sprintf("Decryption failed: %v", err)
|
||||
case subtle.ConstantTimeCompare(key.Plaintext, decryptedKey) != 1:
|
||||
kmsStat.Decrypt = "Decryption failed: decrypted key does not match generated key"
|
||||
default:
|
||||
kmsStat.Decrypt = "success"
|
||||
}
|
||||
return kmsStat
|
||||
}
|
||||
|
||||
@@ -204,7 +204,6 @@ const (
|
||||
ErrInvalidSSECustomerParameters
|
||||
ErrIncompatibleEncryptionMethod
|
||||
ErrKMSNotConfigured
|
||||
ErrKMSAuthFailure
|
||||
|
||||
ErrNoAccessKey
|
||||
ErrInvalidToken
|
||||
@@ -1079,11 +1078,6 @@ var errorCodes = errorCodeMap{
|
||||
Description: "Server side encryption specified but KMS is not configured",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrKMSAuthFailure: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Server side encryption specified but KMS authorization failed",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNoAccessKey: {
|
||||
Code: "AccessDenied",
|
||||
Description: "No AWSAccessKey was presented",
|
||||
@@ -1837,8 +1831,6 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
apiErr = ErrIncompatibleEncryptionMethod
|
||||
case errKMSNotConfigured:
|
||||
apiErr = ErrKMSNotConfigured
|
||||
case crypto.ErrKMSAuthLogin:
|
||||
apiErr = ErrKMSAuthFailure
|
||||
case context.Canceled, context.DeadlineExceeded:
|
||||
apiErr = ErrOperationTimedOut
|
||||
case errDiskNotFound:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -18,8 +18,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
@@ -37,6 +39,7 @@ import (
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
@@ -44,6 +47,7 @@ import (
|
||||
"github.com/minio/minio/pkg/console"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
"github.com/minio/minio/pkg/handlers"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
)
|
||||
|
||||
// serverDebugLog will enable debug printing
|
||||
@@ -316,7 +320,6 @@ func handleCommonEnvVars() {
|
||||
"Unable to validate credentials inherited from the shell environment")
|
||||
}
|
||||
globalActiveCred = cred
|
||||
globalConfigEncrypted = true
|
||||
}
|
||||
|
||||
if env.IsSet(config.EnvRootUser) || env.IsSet(config.EnvRootPassword) {
|
||||
@@ -326,29 +329,58 @@ func handleCommonEnvVars() {
|
||||
"Unable to validate credentials inherited from the shell environment")
|
||||
}
|
||||
globalActiveCred = cred
|
||||
globalConfigEncrypted = true
|
||||
}
|
||||
|
||||
if env.IsSet(config.EnvAccessKeyOld) && env.IsSet(config.EnvSecretKeyOld) {
|
||||
oldCred, err := auth.CreateCredentials(env.Get(config.EnvAccessKeyOld, ""), env.Get(config.EnvSecretKeyOld, ""))
|
||||
if err != nil {
|
||||
logger.Fatal(config.ErrInvalidCredentials(err),
|
||||
"Unable to validate the old credentials inherited from the shell environment")
|
||||
}
|
||||
globalOldCred = oldCred
|
||||
os.Unsetenv(config.EnvAccessKeyOld)
|
||||
os.Unsetenv(config.EnvSecretKeyOld)
|
||||
if env.IsSet(config.EnvKMSSecretKey) && env.IsSet(config.EnvKESEndpoint) {
|
||||
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSSecretKey, config.EnvKESEndpoint))
|
||||
}
|
||||
|
||||
if env.IsSet(config.EnvRootUserOld) && env.IsSet(config.EnvRootPasswordOld) {
|
||||
oldCred, err := auth.CreateCredentials(env.Get(config.EnvRootUserOld, ""), env.Get(config.EnvRootPasswordOld, ""))
|
||||
switch {
|
||||
case env.IsSet(config.EnvKMSSecretKey) && env.IsSet(config.EnvKESEndpoint):
|
||||
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", config.EnvKMSSecretKey, config.EnvKESEndpoint))
|
||||
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(config.ErrInvalidCredentials(err),
|
||||
"Unable to validate the old credentials inherited from the shell environment")
|
||||
logger.Fatal(err, "Unable to parse the KMS secret key inherited from the shell environment")
|
||||
}
|
||||
globalOldCred = oldCred
|
||||
os.Unsetenv(config.EnvRootUserOld)
|
||||
os.Unsetenv(config.EnvRootPasswordOld)
|
||||
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])
|
||||
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")
|
||||
}
|
||||
GlobalKMS = KMS
|
||||
}
|
||||
if env.IsSet(config.EnvKESEndpoint) {
|
||||
kesEndpoints, err := crypto.ParseKESEndpoints(env.Get(config.EnvKESEndpoint, ""))
|
||||
if err != nil {
|
||||
logger.Fatal(err, "Unable to parse the KES endpoints inherited from the shell environment")
|
||||
}
|
||||
KMS, err := crypto.NewKes(crypto.KesConfig{
|
||||
Enabled: true,
|
||||
Endpoint: kesEndpoints,
|
||||
DefaultKeyID: env.Get(config.EnvKESKeyName, ""),
|
||||
CertFile: env.Get(config.EnvKESClientCert, ""),
|
||||
KeyFile: env.Get(config.EnvKESClientKey, ""),
|
||||
CAPath: env.Get(config.EnvKESServerCA, globalCertsCADir.Get()),
|
||||
Transport: newCustomHTTPTransportWithHTTP2(&tls.Config{RootCAs: globalRootCAs}, defaultDialTimeout)(),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
|
||||
}
|
||||
GlobalKMS = KMS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/cmd/logger/target/http"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
@@ -55,8 +56,6 @@ func initHelp() {
|
||||
config.RegionSubSys: config.DefaultRegionKVS,
|
||||
config.APISubSys: api.DefaultKVS,
|
||||
config.CredentialsSubSys: config.DefaultCredentialKVS,
|
||||
config.KmsVaultSubSys: crypto.DefaultVaultKVS,
|
||||
config.KmsKesSubSys: crypto.DefaultKesKVS,
|
||||
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
||||
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
|
||||
config.HealSubSys: heal.DefaultKVS,
|
||||
@@ -204,8 +203,6 @@ func initHelp() {
|
||||
config.IdentityOpenIDSubSys: openid.Help,
|
||||
config.IdentityLDAPSubSys: xldap.Help,
|
||||
config.PolicyOPASubSys: opa.Help,
|
||||
config.KmsVaultSubSys: crypto.HelpVault,
|
||||
config.KmsKesSubSys: crypto.HelpKes,
|
||||
config.LoggerWebhookSubSys: logger.Help,
|
||||
config.AuditWebhookSubSys: logger.HelpAudit,
|
||||
config.NotifyAMQPSubSys: notify.HelpAMQP,
|
||||
@@ -295,27 +292,6 @@ func validateConfig(s config.Config, setDriveCounts []int) error {
|
||||
etcdClnt.Close()
|
||||
}
|
||||
}
|
||||
{
|
||||
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), newCustomHTTPTransportWithHTTP2(
|
||||
&tls.Config{
|
||||
RootCAs: globalRootCAs,
|
||||
}, defaultDialTimeout)())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set env to enable master key validation.
|
||||
// this is needed only for KMS.
|
||||
env.SetEnvOn()
|
||||
|
||||
if _, err = crypto.NewKMS(kmsCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Disable merging env values for the rest.
|
||||
env.SetEnvOff()
|
||||
}
|
||||
|
||||
if _, err := openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
||||
NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil {
|
||||
return err
|
||||
@@ -467,33 +443,17 @@ func lookupConfigs(s config.Config, setDriveCounts []int) {
|
||||
}
|
||||
|
||||
if globalCacheConfig.Enabled {
|
||||
if cacheEncKey := env.Get(cache.EnvCacheEncryptionMasterKey, ""); cacheEncKey != "" {
|
||||
globalCacheKMS, err = crypto.ParseMasterKey(cacheEncKey)
|
||||
if cacheEncKey := env.Get(cache.EnvCacheEncryptionKey, ""); cacheEncKey != "" {
|
||||
globalCacheKMS, err = kms.Parse(cacheEncKey)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup encryption cache: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), newCustomHTTPTransportWithHTTP2(
|
||||
&tls.Config{
|
||||
RootCAs: globalRootCAs,
|
||||
}, defaultDialTimeout)())
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup KMS config: %w", err))
|
||||
}
|
||||
|
||||
GlobalKMS, err = crypto.NewKMS(kmsCfg)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to setup KMS with current KMS config: %w", err))
|
||||
}
|
||||
globalAutoEncryption = kmsCfg.AutoEncryption // Enable auto-encryption if enabled
|
||||
|
||||
if kmsCfg.Vault.Enabled {
|
||||
const deprecationWarning = `Native Hashicorp Vault support is deprecated and will be removed on 2021-10-01. Please migrate to KES + Hashicorp Vault: https://github.com/minio/kes/wiki/Hashicorp-Vault-Keystore
|
||||
Note that native Hashicorp Vault and KES + Hashicorp Vault are not compatible.
|
||||
If you need help to migrate smoothly visit: https://min.io/pricing`
|
||||
logger.LogIf(ctx, fmt.Errorf(deprecationWarning))
|
||||
globalAutoEncryption = crypto.LookupAutoEncryption() // Enable auto-encryption if enabled
|
||||
if globalAutoEncryption && GlobalKMS == nil {
|
||||
logger.Fatal(errors.New("no KMS configured"), "MINIO_KMS_AUTO_ENCRYPTION requires a valid KMS configuration")
|
||||
}
|
||||
|
||||
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
|
||||
|
||||
@@ -20,57 +20,32 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
etcd "go.etcd.io/etcd/clientv3"
|
||||
)
|
||||
|
||||
func handleEncryptedConfigBackend(objAPI ObjectLayer) error {
|
||||
|
||||
encrypted, err := checkBackendEncrypted(objAPI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to encrypt config %w", err)
|
||||
}
|
||||
|
||||
if encrypted {
|
||||
// backend is encrypted, but credentials are not specified
|
||||
// we shall fail right here. if not proceed forward.
|
||||
if !globalConfigEncrypted || !globalActiveCred.IsValid() {
|
||||
return config.ErrMissingCredentialsBackendEncrypted(nil)
|
||||
}
|
||||
} else {
|
||||
// backend is not yet encrypted, check if encryption of
|
||||
// backend is requested if not return nil and proceed
|
||||
// forward.
|
||||
if !globalConfigEncrypted {
|
||||
return nil
|
||||
}
|
||||
if !globalActiveCred.IsValid() {
|
||||
return config.ErrMissingCredentialsBackendEncrypted(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate IAM configuration
|
||||
if err = migrateConfigPrefixToEncrypted(objAPI, globalOldCred, encrypted); err != nil {
|
||||
if err = migrateConfigPrefixToEncrypted(objAPI, encrypted); err != nil {
|
||||
return fmt.Errorf("Unable to migrate all config at .minio.sys/config/: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
backendEncryptedFile = "backend-encrypted"
|
||||
)
|
||||
const backendEncryptedFile = "backend-encrypted"
|
||||
|
||||
var (
|
||||
backendEncryptedMigrationIncomplete = []byte("incomplete")
|
||||
backendEncryptedMigrationComplete = []byte("encrypted")
|
||||
)
|
||||
var backendEncryptedMigrationComplete = []byte("encrypted")
|
||||
|
||||
func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) {
|
||||
data, err := readKeyEtcd(ctx, client, backendEncryptedFile)
|
||||
@@ -112,38 +87,15 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
|
||||
}
|
||||
|
||||
if encrypted {
|
||||
// backend is encrypted, but credentials are not specified
|
||||
// we shall fail right here. if not proceed forward.
|
||||
if !globalConfigEncrypted || !globalActiveCred.IsValid() {
|
||||
return config.ErrMissingCredentialsBackendEncrypted(nil)
|
||||
if GlobalKMS != nil {
|
||||
stat, err := GlobalKMS.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("Attempting to re-encrypt config, IAM users and policies on MinIO with %q (%s)", stat.DefaultKey, stat.Name)
|
||||
} else {
|
||||
logger.Info("Attempting to migrate encrypted config, IAM users and policies on MinIO to a plaintext format. To encrypt all MinIO config data a KMS is needed")
|
||||
}
|
||||
} else {
|
||||
// backend is not yet encrypted, check if encryption of
|
||||
// backend is requested if not return nil and proceed
|
||||
// forward.
|
||||
if !globalConfigEncrypted {
|
||||
return nil
|
||||
}
|
||||
if !globalActiveCred.IsValid() {
|
||||
return errInvalidArgument
|
||||
}
|
||||
}
|
||||
|
||||
if encrypted {
|
||||
// No key rotation requested, and backend is
|
||||
// already encrypted. We proceed without migration.
|
||||
if !globalOldCred.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// No real reason to rotate if old and new creds are same.
|
||||
if globalOldCred.Equal(globalActiveCred) {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Info("Attempting rotation of encrypted IAM users and policies on etcd with newly supplied credentials")
|
||||
} else {
|
||||
logger.Info("Attempting encryption of all IAM users and policies on etcd")
|
||||
}
|
||||
|
||||
listCtx, cancel := context.WithTimeout(ctx, 1*time.Minute)
|
||||
@@ -154,134 +106,89 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedMigrationIncomplete); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, kv := range r.Kvs {
|
||||
var (
|
||||
cdata []byte
|
||||
cencdata []byte
|
||||
)
|
||||
cdata, err = readKeyEtcd(ctx, client, string(kv.Key))
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errConfigNotFound:
|
||||
// Perhaps not present or someone deleted it.
|
||||
continue
|
||||
}
|
||||
return err
|
||||
data, err := readKeyEtcd(ctx, client, string(kv.Key))
|
||||
if err == errConfigNotFound { // Perhaps not present or someone deleted it.
|
||||
continue
|
||||
}
|
||||
|
||||
var data []byte
|
||||
// Is rotating of creds requested?
|
||||
if globalOldCred.IsValid() {
|
||||
data, err = decryptData(cdata, globalOldCred, globalActiveCred)
|
||||
if err != nil {
|
||||
if err == madmin.ErrMaliciousData {
|
||||
return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
data = cdata
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !utf8.Valid(data) {
|
||||
_, err = decryptData(data, globalActiveCred)
|
||||
if err == nil {
|
||||
// Config is already encrypted with right keys
|
||||
continue
|
||||
data, err = decryptData(data, globalActiveCred)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err)
|
||||
}
|
||||
return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err)
|
||||
}
|
||||
|
||||
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
|
||||
if err != nil {
|
||||
return err
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: string(kv.Key),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = saveKeyEtcd(ctx, client, string(kv.Key), cencdata); err != nil {
|
||||
if err = saveKeyEtcd(ctx, client, string(kv.Key), data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if encrypted && globalActiveCred.IsValid() && globalOldCred.IsValid() {
|
||||
logger.Info("Rotation complete, please make sure to unset MINIO_ROOT_USER_OLD and MINIO_ROOT_PASSWORD_OLD envs")
|
||||
if encrypted {
|
||||
if GlobalKMS != nil {
|
||||
logger.Info("Migration of encrypted config data completed. All config data is now encrypted with the KMS")
|
||||
} else {
|
||||
logger.Info("Migration of encrypted config data completed. All config data is now stored in plaintext")
|
||||
}
|
||||
}
|
||||
|
||||
return saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||
return deleteKeyEtcd(ctx, client, backendEncryptedFile)
|
||||
}
|
||||
|
||||
func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Credentials, encrypted bool) error {
|
||||
if encrypted {
|
||||
// No key rotation requested, and backend is
|
||||
// already encrypted. We proceed without migration.
|
||||
if !activeCredOld.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// No real reason to rotate if old and new creds are same.
|
||||
if activeCredOld.Equal(globalActiveCred) {
|
||||
return nil
|
||||
}
|
||||
logger.Info("Attempting rotation of encrypted config, IAM users and policies on MinIO with newly supplied credentials")
|
||||
} else {
|
||||
logger.Info("Attempting encryption of all config, IAM users and policies on MinIO backend")
|
||||
func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, encrypted bool) error {
|
||||
if !encrypted {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := saveConfig(GlobalContext, objAPI, backendEncryptedFile, backendEncryptedMigrationIncomplete)
|
||||
if err != nil {
|
||||
return err
|
||||
if encrypted {
|
||||
if GlobalKMS != nil {
|
||||
stat, err := GlobalKMS.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("Attempting to re-encrypt config, IAM users and policies on MinIO with %q (%s)", stat.DefaultKey, stat.Name)
|
||||
} else {
|
||||
logger.Info("Attempting to migrate encrypted config, IAM users and policies on MinIO to a plaintext format. To encrypt all MinIO config data a KMS is needed")
|
||||
}
|
||||
}
|
||||
|
||||
var marker string
|
||||
for {
|
||||
res, err := objAPI.ListObjects(GlobalContext, minioMetaBucket,
|
||||
minioConfigPrefix, marker, "", maxObjectList)
|
||||
res, err := objAPI.ListObjects(GlobalContext, minioMetaBucket, minioConfigPrefix, marker, "", maxObjectList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, obj := range res.Objects {
|
||||
var (
|
||||
cdata []byte
|
||||
cencdata []byte
|
||||
)
|
||||
|
||||
cdata, err = readConfig(GlobalContext, objAPI, obj.Name)
|
||||
data, err := readConfig(GlobalContext, objAPI, obj.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var data []byte
|
||||
// Is rotating of creds requested?
|
||||
if activeCredOld.IsValid() {
|
||||
data, err = decryptData(cdata, activeCredOld, globalActiveCred)
|
||||
if err != nil {
|
||||
if err == madmin.ErrMaliciousData {
|
||||
return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
data = cdata
|
||||
}
|
||||
|
||||
if !utf8.Valid(data) {
|
||||
_, err = decryptData(data, globalActiveCred)
|
||||
if err == nil {
|
||||
// Config is already encrypted with right keys
|
||||
continue
|
||||
data, err = decryptData(data, globalActiveCred)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err)
|
||||
}
|
||||
return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", err)
|
||||
}
|
||||
|
||||
cencdata, err = madmin.EncryptData(globalActiveCred.String(), data)
|
||||
if err != nil {
|
||||
return err
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{
|
||||
obj.Bucket: path.Join(obj.Bucket, obj.Name),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = saveConfig(GlobalContext, objAPI, obj.Name, cencdata); err != nil {
|
||||
if err = saveConfig(GlobalContext, objAPI, obj.Name, data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -289,13 +196,14 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, activeCredOld auth.Crede
|
||||
if !res.IsTruncated {
|
||||
break
|
||||
}
|
||||
|
||||
marker = res.NextMarker
|
||||
}
|
||||
|
||||
if encrypted && globalActiveCred.IsValid() && activeCredOld.IsValid() {
|
||||
logger.Info("Rotation complete, please make sure to unset MINIO_ROOT_USER_OLD and MINIO_ROOT_PASSWORD_OLD envs")
|
||||
if encrypted {
|
||||
if GlobalKMS != nil {
|
||||
logger.Info("Migration of encrypted config data completed. All config data is now encrypted with the KMS")
|
||||
} else {
|
||||
logger.Info("Migration of encrypted config data completed. All config data is now stored in plaintext")
|
||||
}
|
||||
}
|
||||
|
||||
return saveConfig(GlobalContext, objAPI, backendEncryptedFile, backendEncryptedMigrationComplete)
|
||||
return deleteConfig(GlobalContext, globalObjectAPI, backendEncryptedFile)
|
||||
}
|
||||
|
||||
@@ -34,11 +34,11 @@ import (
|
||||
"github.com/minio/minio/cmd/config/notify"
|
||||
"github.com/minio/minio/cmd/config/policy/opa"
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
"github.com/minio/minio/pkg/event/target"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
"github.com/minio/minio/pkg/quick"
|
||||
@@ -2412,7 +2412,6 @@ func migrateV27ToV28() error {
|
||||
}
|
||||
|
||||
srvConfig.Version = "28"
|
||||
srvConfig.KMS = crypto.KMSConfig{}
|
||||
if err = quick.SaveConfig(srvConfig, configFile, globalEtcdClient); err != nil {
|
||||
return fmt.Errorf("Failed to migrate config from ‘27’ to ‘28’. %w", err)
|
||||
}
|
||||
@@ -2507,13 +2506,28 @@ func checkConfigVersion(objAPI ObjectLayer, configFile string, version string) (
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if globalConfigEncrypted && !utf8.Valid(data) {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
if err == madmin.ErrMaliciousData {
|
||||
return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil)
|
||||
if !utf8.Valid(data) {
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, configFile),
|
||||
})
|
||||
if err != nil {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
if err == madmin.ErrMaliciousData {
|
||||
return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil)
|
||||
}
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
if err == madmin.ErrMaliciousData {
|
||||
return false, nil, config.ErrInvalidCredentialsBackendEncrypted(nil)
|
||||
}
|
||||
return false, nil, err
|
||||
}
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2546,8 +2560,6 @@ func migrateV27ToV28MinioSys(objAPI ObjectLayer) error {
|
||||
}
|
||||
|
||||
cfg.Version = "28"
|
||||
cfg.KMS = crypto.KMSConfig{}
|
||||
|
||||
if err = saveServerConfig(GlobalContext, objAPI, cfg); err != nil {
|
||||
return fmt.Errorf("Failed to migrate config from ‘27’ to ‘28’. %w", err)
|
||||
}
|
||||
@@ -2739,7 +2751,6 @@ func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
|
||||
logger.SetLoggerHTTPAudit(newCfg, k, auditArgs)
|
||||
}
|
||||
|
||||
crypto.SetKMSConfig(newCfg, cfg.KMS)
|
||||
xldap.SetIdentityLDAP(newCfg, cfg.LDAPServerConfig)
|
||||
openid.SetIdentityOpenID(newCfg, cfg.OpenID)
|
||||
opa.SetPolicyOPAConfig(newCfg, cfg.Policy.OPA)
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/minio/minio/cmd/config/notify"
|
||||
"github.com/minio/minio/cmd/config/policy/opa"
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/event/target"
|
||||
@@ -713,9 +712,6 @@ type serverConfigV28 struct {
|
||||
// Cache configuration
|
||||
Cache cache.Config `json:"cache"`
|
||||
|
||||
// KMS configuration
|
||||
KMS crypto.KMSConfig `json:"kms"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify notifierV3 `json:"notify"`
|
||||
|
||||
@@ -742,9 +738,6 @@ type serverConfigV30 struct {
|
||||
// Cache configuration
|
||||
Cache cache.Config `json:"cache"`
|
||||
|
||||
// KMS configuration
|
||||
KMS crypto.KMSConfig `json:"kms"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify notifierV3 `json:"notify"`
|
||||
|
||||
@@ -770,9 +763,6 @@ type serverConfigV31 struct {
|
||||
// Cache configuration
|
||||
Cache cache.Config `json:"cache"`
|
||||
|
||||
// KMS configuration
|
||||
KMS crypto.KMSConfig `json:"kms"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify notifierV3 `json:"notify"`
|
||||
|
||||
@@ -809,9 +799,6 @@ type serverConfigV32 struct {
|
||||
// Cache configuration
|
||||
Cache cache.Config `json:"cache"`
|
||||
|
||||
// KMS configuration
|
||||
KMS crypto.KMSConfig `json:"kms"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify notify.Config `json:"notify"`
|
||||
|
||||
@@ -850,9 +837,6 @@ type serverConfigV33 struct {
|
||||
// Cache configuration
|
||||
Cache cache.Config `json:"cache"`
|
||||
|
||||
// KMS configuration
|
||||
KMS crypto.KMSConfig `json:"kms"`
|
||||
|
||||
// Notification queue configuration.
|
||||
Notify notify.Config `json:"notify"`
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"path"
|
||||
@@ -27,6 +26,7 @@ import (
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
@@ -64,8 +64,10 @@ func listServerConfigHistory(ctx context.Context, objAPI ObjectLayer, withData b
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if globalConfigEncrypted && !utf8.Valid(data) {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||
obj.Bucket: path.Join(obj.Bucket, obj.Name),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,10 +105,11 @@ func readServerConfigHistory(ctx context.Context, objAPI ObjectLayer, uuidKV str
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if globalConfigEncrypted && !utf8.Valid(data) {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, historyFile),
|
||||
})
|
||||
}
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
@@ -114,39 +117,39 @@ func saveServerConfigHistory(ctx context.Context, objAPI ObjectLayer, kv []byte)
|
||||
uuidKV := mustGetUUID() + kvPrefix
|
||||
historyFile := pathJoin(minioConfigHistoryPrefix, uuidKV)
|
||||
|
||||
var err error
|
||||
if globalConfigEncrypted {
|
||||
kv, err = madmin.EncryptData(globalActiveCred.String(), kv)
|
||||
if GlobalKMS != nil {
|
||||
var err error
|
||||
kv, err = config.EncryptBytes(GlobalKMS, kv, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, historyFile),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Save the new config KV settings into the history path.
|
||||
return saveConfig(ctx, objAPI, historyFile, kv)
|
||||
}
|
||||
|
||||
func saveServerConfig(ctx context.Context, objAPI ObjectLayer, config interface{}) error {
|
||||
data, err := json.Marshal(config)
|
||||
func saveServerConfig(ctx context.Context, objAPI ObjectLayer, cfg interface{}) error {
|
||||
data, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if globalConfigEncrypted {
|
||||
data, err = madmin.EncryptData(globalActiveCred.String(), data)
|
||||
var configFile = path.Join(minioConfigPrefix, minioConfigFile)
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, configFile),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
||||
// Save the new config in the std config path
|
||||
return saveConfig(ctx, objAPI, configFile, data)
|
||||
}
|
||||
|
||||
func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, error) {
|
||||
configFile := path.Join(minioConfigPrefix, minioConfigFile)
|
||||
configData, err := readConfig(ctx, objAPI, configFile)
|
||||
data, err := readConfig(ctx, objAPI, configFile)
|
||||
if err != nil {
|
||||
// Config not found for some reason, allow things to continue
|
||||
// by initializing a new fresh config in safe mode.
|
||||
@@ -156,19 +159,18 @@ func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if globalConfigEncrypted && !utf8.Valid(configData) {
|
||||
configData, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(configData))
|
||||
if GlobalKMS != nil && !utf8.Valid(data) {
|
||||
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, configFile),
|
||||
})
|
||||
if err != nil {
|
||||
if err == madmin.ErrMaliciousData {
|
||||
return nil, config.ErrInvalidCredentialsBackendEncrypted(nil)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var srvCfg = config.New()
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
if err = json.Unmarshal(configData, &srvCfg); err != nil {
|
||||
if err = json.Unmarshal(data, &srvCfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
2
cmd/config/cache/lookup.go
vendored
2
cmd/config/cache/lookup.go
vendored
@@ -48,7 +48,7 @@ const (
|
||||
EnvCacheRange = "MINIO_CACHE_RANGE"
|
||||
EnvCacheCommit = "MINIO_CACHE_COMMIT"
|
||||
|
||||
EnvCacheEncryptionMasterKey = "MINIO_CACHE_ENCRYPTION_MASTER_KEY"
|
||||
EnvCacheEncryptionKey = "MINIO_CACHE_ENCRYPTION_SECRET_KEY"
|
||||
|
||||
DefaultExpiry = "90"
|
||||
DefaultQuota = "80"
|
||||
|
||||
@@ -23,24 +23,29 @@ const (
|
||||
|
||||
// Top level common ENVs
|
||||
const (
|
||||
EnvAccessKey = "MINIO_ACCESS_KEY"
|
||||
EnvSecretKey = "MINIO_SECRET_KEY"
|
||||
EnvRootUser = "MINIO_ROOT_USER"
|
||||
EnvRootPassword = "MINIO_ROOT_PASSWORD"
|
||||
EnvAccessKeyOld = "MINIO_ACCESS_KEY_OLD"
|
||||
EnvSecretKeyOld = "MINIO_SECRET_KEY_OLD"
|
||||
EnvRootUserOld = "MINIO_ROOT_USER_OLD"
|
||||
EnvRootPasswordOld = "MINIO_ROOT_PASSWORD_OLD"
|
||||
EnvBrowser = "MINIO_BROWSER"
|
||||
EnvDomain = "MINIO_DOMAIN"
|
||||
EnvRegionName = "MINIO_REGION_NAME"
|
||||
EnvPublicIPs = "MINIO_PUBLIC_IPS"
|
||||
EnvFSOSync = "MINIO_FS_OSYNC"
|
||||
EnvArgs = "MINIO_ARGS"
|
||||
EnvDNSWebhook = "MINIO_DNS_WEBHOOK_ENDPOINT"
|
||||
EnvAccessKey = "MINIO_ACCESS_KEY"
|
||||
EnvSecretKey = "MINIO_SECRET_KEY"
|
||||
EnvRootUser = "MINIO_ROOT_USER"
|
||||
EnvRootPassword = "MINIO_ROOT_PASSWORD"
|
||||
|
||||
EnvBrowser = "MINIO_BROWSER"
|
||||
EnvDomain = "MINIO_DOMAIN"
|
||||
EnvRegionName = "MINIO_REGION_NAME"
|
||||
EnvPublicIPs = "MINIO_PUBLIC_IPS"
|
||||
EnvFSOSync = "MINIO_FS_OSYNC"
|
||||
EnvArgs = "MINIO_ARGS"
|
||||
EnvDNSWebhook = "MINIO_DNS_WEBHOOK_ENDPOINT"
|
||||
|
||||
EnvUpdate = "MINIO_UPDATE"
|
||||
|
||||
EnvKMSMasterKey = "MINIO_KMS_MASTER_KEY" // legacy
|
||||
EnvKMSSecretKey = "MINIO_KMS_SECRET_KEY"
|
||||
EnvKESEndpoint = "MINIO_KMS_KES_ENDPOINT"
|
||||
EnvKESKeyName = "MINIO_KMS_KES_KEY_NAME"
|
||||
EnvKESClientKey = "MINIO_KMS_KES_KEY_FILE"
|
||||
EnvKESClientCert = "MINIO_KMS_KES_CERT_FILE"
|
||||
EnvKESServerCA = "MINIO_KMS_KES_CAPATH"
|
||||
|
||||
EnvEndpoints = "MINIO_ENDPOINTS" // legacy
|
||||
EnvWorm = "MINIO_WORM" // legacy
|
||||
EnvRegion = "MINIO_REGION" // legacy
|
||||
|
||||
@@ -28,6 +28,30 @@ import (
|
||||
"github.com/secure-io/sio-go/sioutil"
|
||||
)
|
||||
|
||||
// EncryptBytes encrypts the plaintext with a key managed by KMS.
|
||||
// The context is bound to the returned ciphertext.
|
||||
//
|
||||
// The same context must be provided when decrypting the
|
||||
// ciphertext.
|
||||
func EncryptBytes(KMS kms.KMS, plaintext []byte, context kms.Context) ([]byte, error) {
|
||||
ciphertext, err := Encrypt(KMS, bytes.NewReader(plaintext), context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return io.ReadAll(ciphertext)
|
||||
}
|
||||
|
||||
// DecryptBytes decrypts the ciphertext using a key managed by the KMS.
|
||||
// The same context that have been used during encryption must be
|
||||
// provided.
|
||||
func DecryptBytes(KMS kms.KMS, ciphertext []byte, context kms.Context) ([]byte, error) {
|
||||
plaintext, err := Decrypt(KMS, bytes.NewReader(ciphertext), context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return io.ReadAll(plaintext)
|
||||
}
|
||||
|
||||
// Encrypt encrypts the plaintext with a key managed by KMS.
|
||||
// The context is bound to the returned ciphertext.
|
||||
//
|
||||
|
||||
@@ -15,12 +15,7 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/cmd/config"
|
||||
@@ -29,101 +24,7 @@ import (
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
)
|
||||
|
||||
// KMSConfig has the KMS config for hashicorp vault
|
||||
type KMSConfig struct {
|
||||
AutoEncryption bool `json:"-"`
|
||||
Vault VaultConfig `json:"vault"`
|
||||
Kes KesConfig `json:"kes"`
|
||||
}
|
||||
|
||||
// KMS Vault constants.
|
||||
const (
|
||||
KMSVaultEndpoint = "endpoint"
|
||||
KMSVaultCAPath = "capath"
|
||||
KMSVaultKeyName = "key_name"
|
||||
KMSVaultKeyVersion = "key_version"
|
||||
KMSVaultNamespace = "namespace"
|
||||
KMSVaultAuthType = "auth_type"
|
||||
KMSVaultAppRoleID = "auth_approle_id"
|
||||
KMSVaultAppRoleSecret = "auth_approle_secret"
|
||||
)
|
||||
|
||||
// KMS kes constants.
|
||||
const (
|
||||
KMSKesEndpoint = "endpoint"
|
||||
KMSKesKeyFile = "key_file"
|
||||
KMSKesCertFile = "cert_file"
|
||||
KMSKesCAPath = "capath"
|
||||
KMSKesKeyName = "key_name"
|
||||
)
|
||||
|
||||
// DefaultKVS - default KV crypto config
|
||||
var (
|
||||
DefaultVaultKVS = config.KVS{
|
||||
config.KV{
|
||||
Key: KMSVaultEndpoint,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultKeyName,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultAuthType,
|
||||
Value: "approle",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultAppRoleID,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultAppRoleSecret,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultCAPath,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultKeyVersion,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultNamespace,
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
|
||||
DefaultKesKVS = config.KVS{
|
||||
config.KV{
|
||||
Key: KMSKesEndpoint,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSKesKeyName,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSKesCertFile,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSKesKeyFile,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSKesCAPath,
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// EnvKMSMasterKey is the environment variable used to specify
|
||||
// a KMS master key used to protect SSE-S3 per-object keys.
|
||||
// Valid values must be of the from: "KEY_ID:32_BYTE_HEX_VALUE".
|
||||
EnvKMSMasterKey = "MINIO_KMS_MASTER_KEY"
|
||||
|
||||
// EnvKMSAutoEncryption is the environment variable used to en/disable
|
||||
// SSE-S3 auto-encryption. SSE-S3 auto-encryption, if enabled,
|
||||
// requires a valid KMS configuration and turns any non-SSE-C
|
||||
@@ -132,301 +33,49 @@ const (
|
||||
EnvKMSAutoEncryption = "MINIO_KMS_AUTO_ENCRYPTION"
|
||||
)
|
||||
|
||||
const (
|
||||
// EnvKMSVaultEndpoint is the environment variable used to specify
|
||||
// the vault HTTPS endpoint.
|
||||
EnvKMSVaultEndpoint = "MINIO_KMS_VAULT_ENDPOINT"
|
||||
|
||||
// EnvKMSVaultAuthType is the environment variable used to specify
|
||||
// the authentication type for vault.
|
||||
EnvKMSVaultAuthType = "MINIO_KMS_VAULT_AUTH_TYPE"
|
||||
|
||||
// EnvKMSVaultAppRoleID is the environment variable used to specify
|
||||
// the vault AppRole ID.
|
||||
EnvKMSVaultAppRoleID = "MINIO_KMS_VAULT_APPROLE_ID"
|
||||
|
||||
// EnvKMSVaultAppSecretID is the environment variable used to specify
|
||||
// the vault AppRole secret corresponding to the AppRole ID.
|
||||
EnvKMSVaultAppSecretID = "MINIO_KMS_VAULT_APPROLE_SECRET"
|
||||
|
||||
// EnvKMSVaultKeyVersion is the environment variable used to specify
|
||||
// the vault key version.
|
||||
EnvKMSVaultKeyVersion = "MINIO_KMS_VAULT_KEY_VERSION"
|
||||
|
||||
// EnvKMSVaultKeyName is the environment variable used to specify
|
||||
// the vault named key-ring. In the S3 context it's referred as
|
||||
// customer master key ID (CMK-ID).
|
||||
EnvKMSVaultKeyName = "MINIO_KMS_VAULT_KEY_NAME"
|
||||
|
||||
// EnvKMSVaultCAPath is the environment variable used to specify the
|
||||
// path to a directory of PEM-encoded CA cert files. These CA cert
|
||||
// files are used to authenticate MinIO to Vault over mTLS.
|
||||
EnvKMSVaultCAPath = "MINIO_KMS_VAULT_CAPATH"
|
||||
|
||||
// EnvKMSVaultNamespace is the environment variable used to specify
|
||||
// vault namespace. The vault namespace is used if the enterprise
|
||||
// version of Hashicorp Vault is used.
|
||||
EnvKMSVaultNamespace = "MINIO_KMS_VAULT_NAMESPACE"
|
||||
)
|
||||
|
||||
const (
|
||||
// EnvKMSKesEndpoint is the environment variable used to specify
|
||||
// one or multiple KES server HTTPS endpoints. The individual
|
||||
// endpoints should be separated by ','.
|
||||
EnvKMSKesEndpoint = "MINIO_KMS_KES_ENDPOINT"
|
||||
|
||||
// EnvKMSKesKeyFile is the environment variable used to specify
|
||||
// the TLS private key used by MinIO to authenticate to the kes
|
||||
// server HTTPS via mTLS.
|
||||
EnvKMSKesKeyFile = "MINIO_KMS_KES_KEY_FILE"
|
||||
|
||||
// EnvKMSKesCertFile is the environment variable used to specify
|
||||
// the TLS certificate used by MinIO to authenticate to the kes
|
||||
// server HTTPS via mTLS.
|
||||
EnvKMSKesCertFile = "MINIO_KMS_KES_CERT_FILE"
|
||||
|
||||
// EnvKMSKesCAPath is the environment variable used to specify
|
||||
// the TLS root certificates used by MinIO to verify the certificate
|
||||
// presented by to the kes server when establishing a TLS connection.
|
||||
EnvKMSKesCAPath = "MINIO_KMS_KES_CA_PATH"
|
||||
|
||||
// EnvKMSKesKeyName is the environment variable used to specify
|
||||
// the (default) key at the kes server. In the S3 context it's
|
||||
// referred as customer master key ID (CMK-ID).
|
||||
EnvKMSKesKeyName = "MINIO_KMS_KES_KEY_NAME"
|
||||
)
|
||||
|
||||
var defaultVaultCfg = VaultConfig{
|
||||
Auth: VaultAuth{
|
||||
Type: "approle",
|
||||
},
|
||||
}
|
||||
|
||||
var defaultKesCfg = KesConfig{}
|
||||
|
||||
// EnabledVault returns true if HashiCorp Vault is enabled.
|
||||
func EnabledVault(kvs config.KVS) bool {
|
||||
endpoint := kvs.Get(KMSVaultEndpoint)
|
||||
return endpoint != ""
|
||||
}
|
||||
|
||||
// EnabledKes returns true if kes as KMS is enabled.
|
||||
func EnabledKes(kvs config.KVS) bool {
|
||||
endpoint := kvs.Get(KMSKesEndpoint)
|
||||
return endpoint != ""
|
||||
}
|
||||
|
||||
// LookupKesConfig lookup kes server configuration.
|
||||
func LookupKesConfig(kvs config.KVS) (KesConfig, error) {
|
||||
kesCfg := KesConfig{}
|
||||
|
||||
endpointStr := env.Get(EnvKMSKesEndpoint, kvs.Get(KMSKesEndpoint))
|
||||
var endpoints []string
|
||||
// ParseKESEndpoints parses the given endpoint string and
|
||||
// returns a list of valid endpoint URLs. The order of the
|
||||
// returned endpoints is randomized.
|
||||
func ParseKESEndpoints(endpointStr string) ([]string, error) {
|
||||
var rawEndpoints []string
|
||||
for _, endpoint := range strings.Split(endpointStr, ",") {
|
||||
if strings.TrimSpace(endpoint) == "" {
|
||||
continue
|
||||
}
|
||||
if !ellipses.HasEllipses(endpoint) {
|
||||
endpoints = append(endpoints, endpoint)
|
||||
rawEndpoints = append(rawEndpoints, endpoint)
|
||||
continue
|
||||
}
|
||||
pattern, err := ellipses.FindEllipsesPatterns(endpoint)
|
||||
if err != nil {
|
||||
return kesCfg, err
|
||||
return nil, Errorf("Invalid KES endpoint %q: %v", endpointStr, err)
|
||||
}
|
||||
for _, p := range pattern {
|
||||
endpoints = append(endpoints, p.Expand()...)
|
||||
rawEndpoints = append(rawEndpoints, p.Expand()...)
|
||||
}
|
||||
}
|
||||
if len(endpoints) == 0 {
|
||||
return kesCfg, nil
|
||||
if len(rawEndpoints) == 0 {
|
||||
return nil, Errorf("Invalid KES endpoint %q", endpointStr)
|
||||
}
|
||||
|
||||
randNum := rand.Intn(len(endpoints) + 1) // We add 1 b/c len(endpoints) may be 0: See: rand.Intn docs
|
||||
kesCfg.Endpoint = make([]string, len(endpoints))
|
||||
for i, endpoint := range endpoints {
|
||||
var (
|
||||
randNum = rand.Intn(len(rawEndpoints))
|
||||
endpoints = make([]string, len(rawEndpoints))
|
||||
)
|
||||
for i, endpoint := range rawEndpoints {
|
||||
endpoint, err := xnet.ParseHTTPURL(endpoint)
|
||||
if err != nil {
|
||||
return kesCfg, err
|
||||
return nil, Errorf("Invalid KES endpoint %q: %v", endpointStr, err)
|
||||
}
|
||||
kesCfg.Endpoint[(randNum+i)%len(endpoints)] = endpoint.String()
|
||||
endpoints[(randNum+i)%len(rawEndpoints)] = endpoint.String()
|
||||
}
|
||||
kesCfg.KeyFile = env.Get(EnvKMSKesKeyFile, kvs.Get(KMSKesKeyFile))
|
||||
kesCfg.CertFile = env.Get(EnvKMSKesCertFile, kvs.Get(KMSKesCertFile))
|
||||
kesCfg.CAPath = env.Get(EnvKMSKesCAPath, kvs.Get(KMSKesCAPath))
|
||||
kesCfg.DefaultKeyID = env.Get(EnvKMSKesKeyName, kvs.Get(KMSKesKeyName))
|
||||
|
||||
if reflect.DeepEqual(kesCfg, defaultKesCfg) {
|
||||
return kesCfg, nil
|
||||
}
|
||||
|
||||
// Verify all the proper settings.
|
||||
if err := kesCfg.Verify(); err != nil {
|
||||
return kesCfg, err
|
||||
}
|
||||
kesCfg.Enabled = true
|
||||
return kesCfg, nil
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func lookupAutoEncryption() (bool, error) {
|
||||
autoBool, err := config.ParseBool(env.Get(EnvAutoEncryptionLegacy, config.EnableOff))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !autoBool {
|
||||
autoBool, err = config.ParseBool(env.Get(EnvKMSAutoEncryption, config.EnableOff))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return autoBool, nil
|
||||
}
|
||||
|
||||
// LookupConfig lookup vault or kes config, returns KMSConfig
|
||||
// to configure KMS object for object encryption
|
||||
func LookupConfig(c config.Config, defaultRootCAsDir string, transport *http.Transport) (KMSConfig, error) {
|
||||
vcfg, err := LookupVaultConfig(c[config.KmsVaultSubSys][config.Default])
|
||||
if err != nil {
|
||||
return KMSConfig{}, err
|
||||
}
|
||||
kesCfg, err := LookupKesConfig(c[config.KmsKesSubSys][config.Default])
|
||||
if err != nil {
|
||||
return KMSConfig{}, err
|
||||
}
|
||||
kesCfg.Transport = transport
|
||||
if kesCfg.Enabled && kesCfg.CAPath == "" {
|
||||
kesCfg.CAPath = defaultRootCAsDir
|
||||
}
|
||||
autoEncrypt, err := lookupAutoEncryption()
|
||||
if err != nil {
|
||||
return KMSConfig{}, err
|
||||
}
|
||||
kmsCfg := KMSConfig{
|
||||
AutoEncryption: autoEncrypt,
|
||||
Vault: vcfg,
|
||||
Kes: kesCfg,
|
||||
}
|
||||
return kmsCfg, nil
|
||||
}
|
||||
|
||||
// LookupVaultConfig extracts the KMS configuration provided by environment
|
||||
// variables and merge them with the provided KMS configuration. The
|
||||
// merging follows the following rules:
|
||||
//
|
||||
// 1. A valid value provided as environment variable is higher prioritized
|
||||
// than the provided configuration and overwrites the value from the
|
||||
// configuration file.
|
||||
//
|
||||
// 2. A value specified as environment variable never changes the configuration
|
||||
// file. So it is never made a persistent setting.
|
||||
//
|
||||
// It sets the global KMS configuration according to the merged configuration
|
||||
// on succes.
|
||||
func LookupVaultConfig(kvs config.KVS) (VaultConfig, error) {
|
||||
if err := config.CheckValidKeys(config.KmsVaultSubSys, kvs, DefaultVaultKVS); err != nil {
|
||||
return VaultConfig{}, err
|
||||
}
|
||||
|
||||
vcfg, err := lookupConfigLegacy(kvs)
|
||||
if err != nil {
|
||||
return vcfg, err
|
||||
}
|
||||
|
||||
if vcfg.Enabled {
|
||||
return vcfg, nil
|
||||
}
|
||||
|
||||
vcfg = VaultConfig{
|
||||
Auth: VaultAuth{
|
||||
Type: "approle",
|
||||
},
|
||||
}
|
||||
|
||||
endpointStr := env.Get(EnvKMSVaultEndpoint, kvs.Get(KMSVaultEndpoint))
|
||||
if endpointStr != "" {
|
||||
// Lookup Hashicorp-Vault configuration & overwrite config entry if ENV var is present
|
||||
endpoint, err := xnet.ParseHTTPURL(endpointStr)
|
||||
if err != nil {
|
||||
return vcfg, err
|
||||
}
|
||||
endpointStr = endpoint.String()
|
||||
}
|
||||
|
||||
vcfg.Endpoint = endpointStr
|
||||
vcfg.CAPath = env.Get(EnvKMSVaultCAPath, kvs.Get(KMSVaultCAPath))
|
||||
vcfg.Auth.Type = env.Get(EnvKMSVaultAuthType, kvs.Get(KMSVaultAuthType))
|
||||
if vcfg.Auth.Type == "" {
|
||||
vcfg.Auth.Type = "approle"
|
||||
}
|
||||
|
||||
vcfg.Auth.AppRole.ID = env.Get(EnvKMSVaultAppRoleID, kvs.Get(KMSVaultAppRoleID))
|
||||
vcfg.Auth.AppRole.Secret = env.Get(EnvKMSVaultAppSecretID, kvs.Get(KMSVaultAppRoleSecret))
|
||||
vcfg.Key.Name = env.Get(EnvKMSVaultKeyName, kvs.Get(KMSVaultKeyName))
|
||||
vcfg.Namespace = env.Get(EnvKMSVaultNamespace, kvs.Get(KMSVaultNamespace))
|
||||
if keyVersion := env.Get(EnvKMSVaultKeyVersion, kvs.Get(KMSVaultKeyVersion)); keyVersion != "" {
|
||||
vcfg.Key.Version, err = strconv.Atoi(keyVersion)
|
||||
if err != nil {
|
||||
return vcfg, Errorf("Unable to parse VaultKeyVersion value (`%s`)", keyVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(vcfg, defaultVaultCfg) {
|
||||
return vcfg, nil
|
||||
}
|
||||
|
||||
// Verify all the proper settings.
|
||||
if err = vcfg.Verify(); err != nil {
|
||||
return vcfg, err
|
||||
}
|
||||
|
||||
vcfg.Enabled = true
|
||||
return vcfg, nil
|
||||
}
|
||||
|
||||
// NewKMS - initialize a new KMS.
|
||||
func NewKMS(cfg KMSConfig) (kms KMS, err error) {
|
||||
// Lookup KMS master kes - only available through ENV.
|
||||
if masterKeyLegacy := env.Get(EnvKMSMasterKeyLegacy, ""); len(masterKeyLegacy) != 0 {
|
||||
if cfg.Vault.Enabled { // Vault and KMS master key provided
|
||||
return kms, errors.New("Ambiguous KMS configuration: vault configuration and a master key are provided at the same time")
|
||||
}
|
||||
if cfg.Kes.Enabled {
|
||||
return kms, errors.New("Ambiguous KMS configuration: kes configuration and a master key are provided at the same time")
|
||||
}
|
||||
kms, err = ParseMasterKey(masterKeyLegacy)
|
||||
if err != nil {
|
||||
return kms, err
|
||||
}
|
||||
} else if masterKey := env.Get(EnvKMSMasterKey, ""); len(masterKey) != 0 {
|
||||
if cfg.Vault.Enabled { // Vault and KMS master key provided
|
||||
return kms, errors.New("Ambiguous KMS configuration: vault configuration and a master key are provided at the same time")
|
||||
}
|
||||
if cfg.Kes.Enabled {
|
||||
return kms, errors.New("Ambiguous KMS configuration: kes configuration and a master key are provided at the same time")
|
||||
}
|
||||
kms, err = ParseMasterKey(masterKey)
|
||||
if err != nil {
|
||||
return kms, err
|
||||
}
|
||||
} else if cfg.Vault.Enabled && cfg.Kes.Enabled {
|
||||
return kms, errors.New("Ambiguous KMS configuration: vault configuration and kes configuration are provided at the same time")
|
||||
} else if cfg.Vault.Enabled {
|
||||
if v, ok := os.LookupEnv("MINIO_KMS_VAULT_DEPRECATION"); !ok || v != "off" { // TODO(aead): Remove once Vault support has been removed
|
||||
return kms, errors.New("Hashicorp Vault is deprecated and will be removed Oct. 2021. To temporarily enable Hashicorp Vault support, set MINIO_KMS_VAULT_DEPRECATION=off")
|
||||
}
|
||||
kms, err = NewVault(cfg.Vault)
|
||||
if err != nil {
|
||||
return kms, err
|
||||
}
|
||||
} else if cfg.Kes.Enabled {
|
||||
kms, err = NewKes(cfg.Kes)
|
||||
if err != nil {
|
||||
return kms, err
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.AutoEncryption && kms == nil {
|
||||
return kms, errors.New("Invalid KMS configuration: auto-encryption is enabled but no valid KMS configuration is present")
|
||||
}
|
||||
return kms, nil
|
||||
// LookupAutoEncryption returns true if and only if
|
||||
// the MINIO_KMS_AUTO_ENCRYPTION env. variable is
|
||||
// set to "on".
|
||||
func LookupAutoEncryption() bool {
|
||||
auto, _ := config.ParseBool(env.Get(EnvKMSAutoEncryption, config.EnableOff))
|
||||
return auto
|
||||
}
|
||||
|
||||
@@ -15,95 +15,3 @@
|
||||
*/
|
||||
|
||||
package crypto
|
||||
|
||||
import "github.com/minio/minio/cmd/config"
|
||||
|
||||
// Help template for KMS vault
|
||||
var (
|
||||
HelpVault = config.HelpKVS{
|
||||
config.HelpKV{
|
||||
Key: KMSVaultEndpoint,
|
||||
Description: `API endpoint e.g. "http://vault-endpoint-ip:8200"`,
|
||||
Type: "url",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSVaultKeyName,
|
||||
Description: `unique transit key name - e.g. "my-minio-key"`,
|
||||
Type: "string",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSVaultAuthType,
|
||||
Description: `supported auth type(s) ["approle"], defaults to "approle"`,
|
||||
Type: "string",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSVaultAppRoleID,
|
||||
Description: `unique role ID for approle`,
|
||||
Type: "string",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSVaultAppRoleSecret,
|
||||
Description: `unique secret ID for approle`,
|
||||
Type: "string",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSVaultNamespace,
|
||||
Description: `optional KMS namespace e.g. "customer1"`,
|
||||
Optional: true,
|
||||
Type: "string",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSVaultKeyVersion,
|
||||
Description: `optional key version number`,
|
||||
Optional: true,
|
||||
Type: "number",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSVaultCAPath,
|
||||
Description: `optional path to PEM-encoded CA certs e.g. "/home/user/custom-certs"`,
|
||||
Optional: true,
|
||||
Type: "path",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.Comment,
|
||||
Description: config.DefaultComment,
|
||||
Optional: true,
|
||||
Type: "sentence",
|
||||
},
|
||||
}
|
||||
|
||||
HelpKes = config.HelpKVS{
|
||||
config.HelpKV{
|
||||
Key: KMSKesEndpoint,
|
||||
Description: `API endpoint - e.g. "https://kes-endpoint:7373"`,
|
||||
Type: "url",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSKesKeyName,
|
||||
Description: `unique key name - e.g. "my-minio-key"`,
|
||||
Type: "string",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSKesCertFile,
|
||||
Description: `path to client certificate for TLS auth - e.g. /etc/keys/public.crt`,
|
||||
Type: "path",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSKesKeyFile,
|
||||
Description: `path to client private key for TLS auth - e.g. /etc/keys/private.key`,
|
||||
Type: "path",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: KMSKesCAPath,
|
||||
Description: `path to PEM-encoded cert(s) to verify kes server cert - e.g. /etc/keys/CAs`,
|
||||
Optional: true,
|
||||
Type: "path",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.Comment,
|
||||
Description: config.DefaultComment,
|
||||
Optional: true,
|
||||
Type: "sentence",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -41,6 +41,9 @@ func GenerateKey(extKey []byte, random io.Reader) (key ObjectKey) {
|
||||
if random == nil {
|
||||
random = rand.Reader
|
||||
}
|
||||
if len(extKey) != 32 { // safety check
|
||||
logger.CriticalIf(context.Background(), errors.New("crypto: invalid key length"))
|
||||
}
|
||||
var nonce [32]byte
|
||||
if _, err := io.ReadFull(random, nonce[:]); err != nil {
|
||||
logger.CriticalIf(context.Background(), errOutOfEntropy)
|
||||
@@ -77,6 +80,9 @@ type SealedKey struct {
|
||||
// key is also cryptographically bound to the object's path (bucket/object) and the
|
||||
// domain (SSE-C or SSE-S3).
|
||||
func (key ObjectKey) Seal(extKey []byte, iv [32]byte, domain, bucket, object string) SealedKey {
|
||||
if len(extKey) != 32 {
|
||||
logger.CriticalIf(context.Background(), errors.New("crypto: invalid key length"))
|
||||
}
|
||||
var (
|
||||
sealingKey [32]byte
|
||||
encryptedKey bytes.Buffer
|
||||
|
||||
@@ -15,17 +15,7 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
|
||||
// Context is a list of key-value pairs cryptographically
|
||||
@@ -37,73 +27,3 @@ type Context = kms.Context
|
||||
// data key generation and unsealing of KMS-generated
|
||||
// data keys.
|
||||
type KMS = kms.KMS
|
||||
|
||||
type masterKeyKMS struct {
|
||||
keyID string
|
||||
masterKey [32]byte
|
||||
}
|
||||
|
||||
// NewMasterKey returns a basic KMS implementation from a single 256 bit master key.
|
||||
//
|
||||
// The KMS accepts any keyID but binds the keyID and context cryptographically
|
||||
// to the generated keys.
|
||||
func NewMasterKey(keyID string, key [32]byte) KMS { return &masterKeyKMS{keyID: keyID, masterKey: key} }
|
||||
|
||||
func (m *masterKeyKMS) Stat() (kms.Status, error) {
|
||||
return kms.Status{
|
||||
Name: "MasterKey",
|
||||
DefaultKey: m.keyID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *masterKeyKMS) CreateKey(keyID string) error {
|
||||
return errors.New("crypto: creating keys is not supported by a static master key")
|
||||
}
|
||||
|
||||
func (m *masterKeyKMS) GenerateKey(keyID string, ctx Context) (kms.DEK, error) {
|
||||
if keyID == "" {
|
||||
keyID = m.keyID
|
||||
}
|
||||
|
||||
var key [32]byte
|
||||
if _, err := io.ReadFull(rand.Reader, key[:]); err != nil {
|
||||
logger.CriticalIf(context.Background(), errOutOfEntropy)
|
||||
}
|
||||
|
||||
var (
|
||||
buffer bytes.Buffer
|
||||
derivedKey = m.deriveKey(keyID, ctx)
|
||||
)
|
||||
if n, err := sio.Encrypt(&buffer, bytes.NewReader(key[:]), sio.Config{Key: derivedKey[:]}); err != nil || n != 64 {
|
||||
logger.CriticalIf(context.Background(), errors.New("KMS: unable to encrypt data key"))
|
||||
}
|
||||
return kms.DEK{
|
||||
KeyID: m.keyID,
|
||||
Plaintext: key[:],
|
||||
Ciphertext: buffer.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *masterKeyKMS) DecryptKey(keyID string, sealedKey []byte, ctx Context) ([]byte, error) {
|
||||
var derivedKey = m.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 (m *masterKeyKMS) deriveKey(keyID string, context Context) (key [32]byte) {
|
||||
if context == nil {
|
||||
context = Context{}
|
||||
}
|
||||
ctxBytes, _ := context.MarshalText()
|
||||
|
||||
mac := hmac.New(sha256.New, m.masterKey[:])
|
||||
mac.Write([]byte(keyID))
|
||||
mac.Write(ctxBytes)
|
||||
mac.Sum(key[:0])
|
||||
return key
|
||||
}
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
// MinIO Cloud Storage, (C) 2015, 2016, 2017, 2018 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var masterKeyKMSTests = []struct {
|
||||
GenKeyID, UnsealKeyID string
|
||||
GenContext, UnsealContext Context
|
||||
|
||||
ShouldFail bool
|
||||
}{
|
||||
{GenKeyID: "", UnsealKeyID: "", GenContext: Context{}, UnsealContext: nil, ShouldFail: false}, // 0
|
||||
{GenKeyID: "ac47be7f", UnsealKeyID: "ac47be7f", GenContext: Context{}, UnsealContext: Context{}, ShouldFail: false}, // 1
|
||||
{GenKeyID: "ac47be7f", UnsealKeyID: "ac47be7f", GenContext: Context{"bucket": "object"}, UnsealContext: Context{"bucket": "object"}, ShouldFail: false}, // 2
|
||||
{GenKeyID: "", UnsealKeyID: "", GenContext: Context{"bucket": path.Join("bucket", "object")}, UnsealContext: Context{"bucket": path.Join("bucket", "object")}, ShouldFail: false}, // 3
|
||||
{GenKeyID: "", UnsealKeyID: "", GenContext: Context{"a": "a", "0": "0", "b": "b"}, UnsealContext: Context{"b": "b", "a": "a", "0": "0"}, ShouldFail: false}, // 4
|
||||
|
||||
{GenKeyID: "ac47be7f", UnsealKeyID: "ac47be7e", GenContext: Context{}, UnsealContext: Context{}, ShouldFail: true}, // 5
|
||||
{GenKeyID: "ac47be7f", UnsealKeyID: "ac47be7f", GenContext: Context{"bucket": "object"}, UnsealContext: Context{"Bucket": "object"}, ShouldFail: true}, // 6
|
||||
{GenKeyID: "", UnsealKeyID: "", GenContext: Context{"bucket": path.Join("bucket", "Object")}, UnsealContext: Context{"bucket": path.Join("bucket", "object")}, ShouldFail: true}, // 7
|
||||
{GenKeyID: "", UnsealKeyID: "", GenContext: Context{"a": "a", "0": "1", "b": "b"}, UnsealContext: Context{"b": "b", "a": "a", "0": "0"}, ShouldFail: true}, // 8
|
||||
}
|
||||
|
||||
func TestMasterKeyKMS(t *testing.T) {
|
||||
for i, test := range masterKeyKMSTests {
|
||||
kms := NewMasterKey(test.GenKeyID, [32]byte{})
|
||||
|
||||
key, err := kms.GenerateKey(test.GenKeyID, test.GenContext)
|
||||
if err != nil {
|
||||
t.Errorf("Test %d: KMS failed to generate key: %v", i, err)
|
||||
}
|
||||
unsealedKey, err := kms.DecryptKey(test.UnsealKeyID, key.Ciphertext, test.UnsealContext)
|
||||
if err != nil && !test.ShouldFail {
|
||||
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 key successfully but should have failed", i)
|
||||
}
|
||||
if !test.ShouldFail && !bytes.Equal(key.Plaintext, unsealedKey[:]) {
|
||||
t.Errorf("Test %d: The generated and unsealed key differ", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var contextMarshalTextTests = []struct {
|
||||
Context Context
|
||||
ExpectedJSON string
|
||||
}{
|
||||
0: {Context: Context{}, ExpectedJSON: "{}"},
|
||||
1: {Context: Context{"a": "b"}, ExpectedJSON: `{"a":"b"}`},
|
||||
2: {Context: Context{"a": "b", "c": "d"}, ExpectedJSON: `{"a":"b","c":"d"}`},
|
||||
3: {Context: Context{"c": "d", "a": "b"}, ExpectedJSON: `{"a":"b","c":"d"}`},
|
||||
4: {Context: Context{"0": "1", "-": "2", ".": "#"}, ExpectedJSON: `{"-":"2",".":"#","0":"1"}`},
|
||||
// rfc 8259 escapes
|
||||
5: {Context: Context{"0": "1", "key\\": "val\tue\r\n", "\"": "\""}, ExpectedJSON: `{"\"":"\"","0":"1","key\\":"val\tue\r\n"}`},
|
||||
// html sensitive escapes
|
||||
6: {Context: Context{"a": "<>&"}, ExpectedJSON: `{"a":"\u003c\u003e\u0026"}`},
|
||||
}
|
||||
|
||||
func TestContextMarshalText(t *testing.T) {
|
||||
for i, test := range contextMarshalTextTests {
|
||||
text, err := test.Context.MarshalText()
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to encode context: %v", i, err)
|
||||
}
|
||||
if string(text) != test.ExpectedJSON {
|
||||
t.Errorf("Test %d: JSON representation differ - got: '%s' want: '%s'", i, string(text), test.ExpectedJSON)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkContext(b *testing.B) {
|
||||
tests := []Context{{}, {"bucket": "warp-benchmark-bucket"}, {"0": "1", "-": "2", ".": "#"}, {"34trg": "dfioutr89", "ikjfdghkjf": "jkedfhgfjkhg", "sdfhsdjkh": "if88889", "asddsirfh804": "kjfdshgdfuhgfg78-45604586#$%<>&"}}
|
||||
for _, test := range tests {
|
||||
b.Run(fmt.Sprintf("%d-elems", len(test)), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := test.MarshalText()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
)
|
||||
|
||||
const (
|
||||
// EnvKMSMasterKeyLegacy is the environment variable used to specify
|
||||
// a KMS master key used to protect SSE-S3 per-object keys.
|
||||
// Valid values must be of the from: "KEY_ID:32_BYTE_HEX_VALUE".
|
||||
EnvKMSMasterKeyLegacy = "MINIO_SSE_MASTER_KEY"
|
||||
|
||||
// EnvAutoEncryptionLegacy is the environment variable used to en/disable
|
||||
// SSE-S3 auto-encryption. SSE-S3 auto-encryption, if enabled,
|
||||
// requires a valid KMS configuration and turns any non-SSE-C
|
||||
// request into an SSE-S3 request.
|
||||
// If present EnvAutoEncryption must be either "on" or "off".
|
||||
EnvAutoEncryptionLegacy = "MINIO_SSE_AUTO_ENCRYPTION"
|
||||
)
|
||||
|
||||
const (
|
||||
// EnvLegacyVaultEndpoint is the environment variable used to specify
|
||||
// the vault HTTPS endpoint.
|
||||
EnvLegacyVaultEndpoint = "MINIO_SSE_VAULT_ENDPOINT"
|
||||
|
||||
// EnvLegacyVaultAuthType is the environment variable used to specify
|
||||
// the authentication type for vault.
|
||||
EnvLegacyVaultAuthType = "MINIO_SSE_VAULT_AUTH_TYPE"
|
||||
|
||||
// EnvLegacyVaultAppRoleID is the environment variable used to specify
|
||||
// the vault AppRole ID.
|
||||
EnvLegacyVaultAppRoleID = "MINIO_SSE_VAULT_APPROLE_ID"
|
||||
|
||||
// EnvLegacyVaultAppSecretID is the environment variable used to specify
|
||||
// the vault AppRole secret corresponding to the AppRole ID.
|
||||
EnvLegacyVaultAppSecretID = "MINIO_SSE_VAULT_APPROLE_SECRET"
|
||||
|
||||
// EnvLegacyVaultKeyVersion is the environment variable used to specify
|
||||
// the vault key version.
|
||||
EnvLegacyVaultKeyVersion = "MINIO_SSE_VAULT_KEY_VERSION"
|
||||
|
||||
// EnvLegacyVaultKeyName is the environment variable used to specify
|
||||
// the vault named key-ring. In the S3 context it's referred as
|
||||
// customer master key ID (CMK-ID).
|
||||
EnvLegacyVaultKeyName = "MINIO_SSE_VAULT_KEY_NAME"
|
||||
|
||||
// EnvLegacyVaultCAPath is the environment variable used to specify the
|
||||
// path to a directory of PEM-encoded CA cert files. These CA cert
|
||||
// files are used to authenticate MinIO to Vault over mTLS.
|
||||
EnvLegacyVaultCAPath = "MINIO_SSE_VAULT_CAPATH"
|
||||
|
||||
// EnvLegacyVaultNamespace is the environment variable used to specify
|
||||
// vault namespace. The vault namespace is used if the enterprise
|
||||
// version of Hashicorp Vault is used.
|
||||
EnvLegacyVaultNamespace = "MINIO_SSE_VAULT_NAMESPACE"
|
||||
)
|
||||
|
||||
// SetKMSConfig helper to migrate from older KMSConfig to new KV.
|
||||
func SetKMSConfig(s config.Config, cfg KMSConfig) {
|
||||
if cfg.Vault.Endpoint == "" {
|
||||
return
|
||||
}
|
||||
s[config.KmsVaultSubSys][config.Default] = config.KVS{
|
||||
config.KV{
|
||||
Key: KMSVaultEndpoint,
|
||||
Value: cfg.Vault.Endpoint,
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultCAPath,
|
||||
Value: cfg.Vault.CAPath,
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultAuthType,
|
||||
Value: func() string {
|
||||
if cfg.Vault.Auth.Type != "" {
|
||||
return cfg.Vault.Auth.Type
|
||||
}
|
||||
return "approle"
|
||||
}(),
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultAppRoleID,
|
||||
Value: cfg.Vault.Auth.AppRole.ID,
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultAppRoleSecret,
|
||||
Value: cfg.Vault.Auth.AppRole.Secret,
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultKeyName,
|
||||
Value: cfg.Vault.Key.Name,
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultKeyVersion,
|
||||
Value: strconv.Itoa(cfg.Vault.Key.Version),
|
||||
},
|
||||
config.KV{
|
||||
Key: KMSVaultNamespace,
|
||||
Value: cfg.Vault.Namespace,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// lookupConfigLegacy extracts the KMS configuration provided by legacy
|
||||
// environment variables and merge them with the provided KMS configuration.
|
||||
// The merging follows the following rules:
|
||||
//
|
||||
// 1. A valid value provided as environment variable has higher priority
|
||||
// than the provided configuration and overwrites the value from the
|
||||
// configuration file.
|
||||
//
|
||||
// 2. A value specified as environment variable never changes the configuration
|
||||
// file. So it is never made a persistent setting.
|
||||
//
|
||||
// It sets the global KMS configuration according to the merged configuration
|
||||
// on success.
|
||||
func lookupConfigLegacy(kvs config.KVS) (VaultConfig, error) {
|
||||
vcfg := VaultConfig{
|
||||
Auth: VaultAuth{
|
||||
Type: "approle",
|
||||
},
|
||||
}
|
||||
|
||||
endpointStr := env.Get(EnvLegacyVaultEndpoint, "")
|
||||
if endpointStr != "" {
|
||||
// Lookup Hashicorp-Vault configuration & overwrite config entry if ENV var is present
|
||||
endpoint, err := xnet.ParseHTTPURL(endpointStr)
|
||||
if err != nil {
|
||||
return vcfg, err
|
||||
}
|
||||
endpointStr = endpoint.String()
|
||||
}
|
||||
|
||||
var err error
|
||||
vcfg.Endpoint = endpointStr
|
||||
vcfg.CAPath = env.Get(EnvLegacyVaultCAPath, "")
|
||||
vcfg.Auth.Type = env.Get(EnvLegacyVaultAuthType, "")
|
||||
if vcfg.Auth.Type == "" {
|
||||
vcfg.Auth.Type = "approle"
|
||||
}
|
||||
vcfg.Auth.AppRole.ID = env.Get(EnvLegacyVaultAppRoleID, "")
|
||||
vcfg.Auth.AppRole.Secret = env.Get(EnvLegacyVaultAppSecretID, "")
|
||||
vcfg.Key.Name = env.Get(EnvLegacyVaultKeyName, "")
|
||||
vcfg.Namespace = env.Get(EnvLegacyVaultNamespace, "")
|
||||
if keyVersion := env.Get(EnvLegacyVaultKeyVersion, ""); keyVersion != "" {
|
||||
vcfg.Key.Version, err = strconv.Atoi(keyVersion)
|
||||
if err != nil {
|
||||
return vcfg, Errorf("Invalid ENV variable: Unable to parse %s value (`%s`)",
|
||||
EnvLegacyVaultKeyVersion, keyVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(vcfg, defaultVaultCfg) {
|
||||
return vcfg, nil
|
||||
}
|
||||
|
||||
if err = vcfg.Verify(); err != nil {
|
||||
return vcfg, err
|
||||
}
|
||||
|
||||
vcfg.Enabled = true
|
||||
return vcfg, nil
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// MinIO Cloud Storage, (C) 2017-2019 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseMasterKey parses the value of the environment variable
|
||||
// `EnvKMSMasterKey` and returns a key-ID and a master-key KMS on success.
|
||||
func ParseMasterKey(envArg string) (KMS, error) {
|
||||
values := strings.SplitN(envArg, ":", 2)
|
||||
if len(values) != 2 {
|
||||
return nil, Errorf("Invalid KMS master key: %s does not contain a ':'", envArg)
|
||||
}
|
||||
var (
|
||||
keyID = values[0]
|
||||
hexKey = values[1]
|
||||
)
|
||||
if len(hexKey) != 64 { // 2 hex bytes = 1 byte
|
||||
return nil, Errorf("Invalid KMS master key: %s not a 32 bytes long HEX value", hexKey)
|
||||
}
|
||||
var masterKey [32]byte
|
||||
if _, err := hex.Decode(masterKey[:], []byte(hexKey)); err != nil {
|
||||
return nil, Errorf("Invalid KMS master key: %v", err)
|
||||
}
|
||||
return NewMasterKey(keyID, masterKey), nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package crypto
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseMasterKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
envValue string
|
||||
expectedKeyID string
|
||||
success bool
|
||||
}{
|
||||
{
|
||||
envValue: "invalid-value",
|
||||
success: false,
|
||||
},
|
||||
{
|
||||
envValue: "too:many:colons",
|
||||
success: false,
|
||||
},
|
||||
{
|
||||
envValue: "myminio-key:not-a-hex",
|
||||
success: false,
|
||||
},
|
||||
{
|
||||
envValue: "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574",
|
||||
expectedKeyID: "my-minio-key",
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.envValue, func(t *testing.T) {
|
||||
kms, err := ParseMasterKey(tt.envValue)
|
||||
if tt.success && err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !tt.success && err == nil {
|
||||
t.Error("Unexpected failure")
|
||||
}
|
||||
if kms != nil {
|
||||
stat, _ := kms.Stat()
|
||||
if err == nil && stat.DefaultKey != tt.expectedKeyID {
|
||||
t.Errorf("Expected keyID %s, got %s", tt.expectedKeyID, stat.DefaultKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -182,45 +182,3 @@ func TestSSECopyUnsealObjectKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var s3UnsealObjectKeyTests = []struct {
|
||||
KMS KMS
|
||||
Bucket, Object string
|
||||
Metadata map[string]string
|
||||
|
||||
ExpectedErr error
|
||||
}{
|
||||
{ // 0 - Valid KMS key-ID and valid metadata entries for bucket/object
|
||||
KMS: NewMasterKey("my-minio-key", [32]byte{}),
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
Metadata: map[string]string{
|
||||
"X-Minio-Internal-Server-Side-Encryption-Iv": "hhVY0LKR1YtZbzAKxTWUfZt5enDfYX6Fxz1ma8Kiudc=",
|
||||
"X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key": "IAAfALhsOeD5AE3s5Zgq3DZ5VFGsOa3B0ksVC86veDcaj+fXv2U0VadhPaOKYr9Emd5ssOsO0uIhIIrKiOy9rA==",
|
||||
"X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key": "IAAfAMRS2iw45FsfiF3QXajSYVWj1lxMpQm6DxDGPtADCX6fJQQ4atHBtfpgqJFyeQmIHsm0FBI+UlHw1Lv4ug==",
|
||||
"X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id": "test-key-1",
|
||||
"X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "DAREv2-HMAC-SHA256",
|
||||
},
|
||||
ExpectedErr: nil,
|
||||
},
|
||||
{ // 1 - Valid KMS key-ID for invalid metadata entries for bucket/object
|
||||
KMS: NewMasterKey("my-minio-key", [32]byte{}),
|
||||
Bucket: "bucket",
|
||||
Object: "object",
|
||||
Metadata: map[string]string{
|
||||
"X-Minio-Internal-Server-Side-Encryption-Iv": "hhVY0LKR1YtZbzAKxTWUfZt5enDfYX6Fxz1ma8Kiudc=",
|
||||
"X-Minio-Internal-Server-Side-Encryption-S3-Sealed-Key": "IAAfALhsOeD5AE3s5Zgq3DZ5VFGsOa3B0ksVC86veDcaj+fXv2U0VadhPaOKYr9Emd5ssOsO0uIhIIrKiOy9rA==",
|
||||
"X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key": "IAAfAMRS2iw45FsfiF3QXajSYVWj1lxMpQm6DxDGPtADCX6fJQQ4atHBtfpgqJFyeQmIHsm0FBI+UlHw1Lv4ug==",
|
||||
"X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Id": "test-key-1",
|
||||
},
|
||||
ExpectedErr: errMissingInternalSealAlgorithm,
|
||||
},
|
||||
}
|
||||
|
||||
func TestS3UnsealObjectKey(t *testing.T) {
|
||||
for i, test := range s3UnsealObjectKeyTests {
|
||||
if _, err := S3.UnsealObjectKey(test.KMS, test.Metadata, test.Bucket, test.Object); err != test.ExpectedErr {
|
||||
t.Errorf("Test %d: got: %v - want: %v", i, err, test.ExpectedErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
// MinIO Cloud Storage, (C) 2015, 2016, 2017, 2018 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
vault "github.com/hashicorp/vault/api"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
)
|
||||
|
||||
var (
|
||||
//ErrKMSAuthLogin is raised when there is a failure authenticating to KMS
|
||||
ErrKMSAuthLogin = Errorf("Vault service did not return auth info")
|
||||
)
|
||||
|
||||
// VaultKey represents vault encryption key-ring.
|
||||
type VaultKey struct {
|
||||
Name string `json:"name"` // The name of the encryption key-ring
|
||||
Version int `json:"version"` // The key version
|
||||
}
|
||||
|
||||
// VaultAuth represents vault authentication type.
|
||||
// Currently the only supported authentication type is AppRole.
|
||||
type VaultAuth struct {
|
||||
Type string `json:"type"` // The authentication type
|
||||
AppRole VaultAppRole `json:"approle"` // The AppRole authentication credentials
|
||||
}
|
||||
|
||||
// VaultAppRole represents vault AppRole authentication credentials
|
||||
type VaultAppRole struct {
|
||||
ID string `json:"id"` // The AppRole access ID
|
||||
Secret string `json:"secret"` // The AppRole secret
|
||||
}
|
||||
|
||||
// VaultConfig represents vault configuration.
|
||||
type VaultConfig struct {
|
||||
Enabled bool `json:"-"`
|
||||
Endpoint string `json:"endpoint"` // The vault API endpoint as URL
|
||||
CAPath string `json:"-"` // The path to PEM-encoded certificate files used for mTLS. Currently not used in config file.
|
||||
Auth VaultAuth `json:"auth"` // The vault authentication configuration
|
||||
Key VaultKey `json:"key-id"` // The named key used for key-generation / decryption.
|
||||
Namespace string `json:"-"` // The vault namespace of enterprise vault instances
|
||||
}
|
||||
|
||||
// vaultService represents a connection to a vault KMS.
|
||||
type vaultService struct {
|
||||
config *VaultConfig
|
||||
client *vault.Client
|
||||
secret *vault.Secret
|
||||
leaseDuration time.Duration
|
||||
}
|
||||
|
||||
var _ KMS = (*vaultService)(nil) // compiler check that *vaultService implements KMS
|
||||
|
||||
// Verify returns a nil error if the vault configuration
|
||||
// is valid. A valid configuration is either empty or
|
||||
// contains valid non-default values.
|
||||
func (v *VaultConfig) Verify() (err error) {
|
||||
switch {
|
||||
case v.Endpoint == "":
|
||||
err = Errorf("crypto: missing hashicorp vault endpoint")
|
||||
case strings.ToLower(v.Auth.Type) != "approle":
|
||||
err = Errorf("crypto: invalid hashicorp vault authentication type: %s is not supported", v.Auth.Type)
|
||||
case v.Auth.AppRole.ID == "":
|
||||
err = Errorf("crypto: missing hashicorp vault AppRole ID")
|
||||
case v.Auth.AppRole.Secret == "":
|
||||
err = Errorf("crypto: missing hashicorp vault AppSecret ID")
|
||||
case v.Key.Name == "":
|
||||
err = Errorf("crypto: missing hashicorp vault key name")
|
||||
case v.Key.Version < 0:
|
||||
err = Errorf("crypto: invalid hashicorp vault key version: The key version must not be negative")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewVault initializes Hashicorp Vault KMS by authenticating
|
||||
// to Vault with the credentials in config and gets a client
|
||||
// token for future api calls.
|
||||
func NewVault(config VaultConfig) (KMS, error) {
|
||||
if !config.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
if err := config.Verify(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vaultCfg := vault.Config{Address: config.Endpoint}
|
||||
if err := vaultCfg.ConfigureTLS(&vault.TLSConfig{CAPath: config.CAPath}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := vault.NewClient(&vaultCfg)
|
||||
if err != nil {
|
||||
return nil, Errorf("crypto: client error %w", err)
|
||||
}
|
||||
if config.Namespace != "" {
|
||||
client.SetNamespace(config.Namespace)
|
||||
}
|
||||
v := &vaultService{client: client, config: &config}
|
||||
if err := v.authenticate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.renewToken()
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// renewToken starts a new go-routine which renews
|
||||
// the vault authentication token periodically and re-authenticates
|
||||
// if the token renewal fails
|
||||
func (v *vaultService) renewToken() {
|
||||
retryDelay := v.leaseDuration / 2
|
||||
go func() {
|
||||
for {
|
||||
if v.secret == nil {
|
||||
if err := v.authenticate(); err != nil {
|
||||
time.Sleep(retryDelay)
|
||||
continue
|
||||
}
|
||||
}
|
||||
s, err := v.client.Auth().Token().RenewSelf(int(v.leaseDuration))
|
||||
if err != nil || s == nil {
|
||||
v.secret = nil
|
||||
time.Sleep(retryDelay)
|
||||
continue
|
||||
}
|
||||
if ok, err := s.TokenIsRenewable(); !ok || err != nil {
|
||||
v.secret = nil
|
||||
continue
|
||||
}
|
||||
ttl, err := s.TokenTTL()
|
||||
if err != nil {
|
||||
v.secret = nil
|
||||
continue
|
||||
}
|
||||
v.secret = s
|
||||
retryDelay = ttl / 2
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// authenticate logs the app to vault, and starts the auto renewer
|
||||
// before secret expires
|
||||
func (v *vaultService) authenticate() (err error) {
|
||||
payload := map[string]interface{}{
|
||||
"role_id": v.config.Auth.AppRole.ID,
|
||||
"secret_id": v.config.Auth.AppRole.Secret,
|
||||
}
|
||||
var tokenID string
|
||||
var ttl time.Duration
|
||||
var secret *vault.Secret
|
||||
secret, err = v.client.Logical().Write("auth/approle/login", payload)
|
||||
if err != nil {
|
||||
err = Errorf("crypto: client error %w", err)
|
||||
return
|
||||
}
|
||||
if secret == nil {
|
||||
err = ErrKMSAuthLogin
|
||||
return
|
||||
}
|
||||
|
||||
tokenID, err = secret.TokenID()
|
||||
if err != nil {
|
||||
err = ErrKMSAuthLogin
|
||||
return
|
||||
}
|
||||
ttl, err = secret.TokenTTL()
|
||||
if err != nil {
|
||||
err = ErrKMSAuthLogin
|
||||
return
|
||||
}
|
||||
v.client.SetToken(tokenID)
|
||||
v.secret = secret
|
||||
v.leaseDuration = ttl
|
||||
return
|
||||
}
|
||||
|
||||
// Info returns some information about the Vault,
|
||||
// configuration - like the endpoints or authentication
|
||||
// method.
|
||||
func (v *vaultService) Stat() (kms.Status, error) {
|
||||
return kms.Status{
|
||||
Endpoints: []string{v.config.Endpoint},
|
||||
Name: "Hashicorp Vault",
|
||||
DefaultKey: v.config.Key.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// cryptographically to the provided context.
|
||||
func (v *vaultService) GenerateKey(keyID string, ctx Context) (kms.DEK, error) {
|
||||
if keyID == "" {
|
||||
keyID = v.config.Key.Name
|
||||
}
|
||||
context, err := ctx.MarshalText()
|
||||
if err != nil {
|
||||
return kms.DEK{}, err
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"context": base64.StdEncoding.EncodeToString(context),
|
||||
}
|
||||
s, err := v.client.Logical().Write(fmt.Sprintf("/transit/datakey/plaintext/%s", keyID), payload)
|
||||
if err != nil {
|
||||
return kms.DEK{}, Errorf("crypto: client error %w", err)
|
||||
}
|
||||
sealKey, ok := s.Data["ciphertext"].(string)
|
||||
if !ok {
|
||||
return kms.DEK{}, Errorf("crypto: incorrect 'ciphertext' key type %v", s.Data["ciphertext"])
|
||||
}
|
||||
|
||||
plainKeyB64, ok := s.Data["plaintext"].(string)
|
||||
if !ok {
|
||||
return kms.DEK{}, Errorf("crypto: incorrect 'plaintext' key type %v", s.Data["plaintext"])
|
||||
}
|
||||
|
||||
plainKey, err := base64.StdEncoding.DecodeString(plainKeyB64)
|
||||
if err != nil {
|
||||
return kms.DEK{}, Errorf("crypto: invalid base64 key %w", err)
|
||||
}
|
||||
return kms.DEK{
|
||||
KeyID: keyID,
|
||||
Plaintext: plainKey,
|
||||
Ciphertext: []byte(sealKey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UnsealKey returns the decrypted sealedKey as plaintext key.
|
||||
// Therefore it sends the sealedKey to the KMS which decrypts
|
||||
// it using the named key referenced by keyID and responses with
|
||||
// the plaintext key.
|
||||
//
|
||||
// The context must be same context as the one provided while
|
||||
// generating the plaintext key / sealedKey.
|
||||
func (v *vaultService) DecryptKey(keyID string, sealedKey []byte, ctx Context) ([]byte, error) {
|
||||
context, err := ctx.MarshalText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"ciphertext": string(sealedKey),
|
||||
"context": base64.StdEncoding.EncodeToString(context),
|
||||
}
|
||||
|
||||
s, err := v.client.Logical().Write(fmt.Sprintf("/transit/decrypt/%s", keyID), payload)
|
||||
if err != nil {
|
||||
return nil, Errorf("crypto: client error %w", err)
|
||||
}
|
||||
|
||||
base64Key, ok := s.Data["plaintext"].(string)
|
||||
if !ok {
|
||||
return nil, Errorf("crypto: incorrect 'plaintext' key type %v", s.Data["plaintext"])
|
||||
}
|
||||
|
||||
plainKey, err := base64.StdEncoding.DecodeString(base64Key)
|
||||
if err != nil {
|
||||
return nil, Errorf("crypto: invalid base64 key %w", err)
|
||||
}
|
||||
return plainKey, nil
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
// Minio Cloud Storage, (C) 2019 Minio, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var verifyVaultConfigTests = []struct {
|
||||
Config VaultConfig
|
||||
ShouldFail bool
|
||||
}{
|
||||
{
|
||||
ShouldFail: true,
|
||||
Config: VaultConfig{
|
||||
Endpoint: "https://127.0.0.1:8080",
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
ShouldFail: true, // 1
|
||||
Config: VaultConfig{
|
||||
Enabled: true,
|
||||
Endpoint: "https://127.0.0.1:8080",
|
||||
Auth: VaultAuth{Type: "unsupported"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ShouldFail: true, // 2
|
||||
Config: VaultConfig{
|
||||
Enabled: true,
|
||||
Endpoint: "https://127.0.0.1:8080",
|
||||
Auth: VaultAuth{
|
||||
Type: "approle",
|
||||
AppRole: VaultAppRole{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ShouldFail: true, // 3
|
||||
Config: VaultConfig{
|
||||
Enabled: true,
|
||||
Endpoint: "https://127.0.0.1:8080",
|
||||
Auth: VaultAuth{
|
||||
Type: "approle",
|
||||
AppRole: VaultAppRole{ID: "123456"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ShouldFail: true, // 4
|
||||
Config: VaultConfig{
|
||||
Enabled: true,
|
||||
Endpoint: "https://127.0.0.1:8080",
|
||||
Auth: VaultAuth{
|
||||
Type: "approle",
|
||||
AppRole: VaultAppRole{ID: "123456", Secret: "abcdef"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ShouldFail: true, // 5
|
||||
Config: VaultConfig{
|
||||
Enabled: true,
|
||||
Endpoint: "https://127.0.0.1:8080",
|
||||
Auth: VaultAuth{
|
||||
Type: "approle",
|
||||
AppRole: VaultAppRole{ID: "123456", Secret: "abcdef"},
|
||||
},
|
||||
Key: VaultKey{Name: "default-key", Version: -1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestVerifyVaultConfig(t *testing.T) {
|
||||
for _, test := range verifyVaultConfigTests {
|
||||
test := test
|
||||
t.Run(test.Config.Endpoint, func(t *testing.T) {
|
||||
err := test.Config.Verify()
|
||||
if test.ShouldFail && err == nil {
|
||||
t.Errorf("Verify should fail but returned 'err == nil'")
|
||||
}
|
||||
if !test.ShouldFail && err != nil {
|
||||
t.Errorf("Verify should succeed but returned err: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -19,14 +19,10 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
@@ -619,7 +615,6 @@ func TestGetDefaultOpts(t *testing.T) {
|
||||
if err == nil {
|
||||
if opts.ServerSideEncryption == nil && test.encryptionType != "" {
|
||||
t.Errorf("Case %d: expected opts to be of %v encryption type", i, test.encryptionType)
|
||||
|
||||
}
|
||||
if opts.ServerSideEncryption != nil && test.encryptionType != opts.ServerSideEncryption.Type() {
|
||||
t.Errorf("Case %d: expected opts to have encryption type %v but was %v ", i, test.encryptionType, opts.ServerSideEncryption.Type())
|
||||
@@ -627,89 +622,3 @@ func TestGetDefaultOpts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
func Test_decryptObjectInfo(t *testing.T) {
|
||||
var testSet []struct {
|
||||
Bucket string
|
||||
Name string
|
||||
UserDef map[string]string
|
||||
}
|
||||
file, err := os.Open("testdata/decryptObjectInfo.json.zst")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
dec, err := zstd.NewReader(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer dec.Close()
|
||||
js := json.NewDecoder(dec)
|
||||
err = js.Decode(&testSet)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574")
|
||||
defer os.Setenv("MINIO_KMS_MASTER_KEY", "")
|
||||
GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var dst [32]byte
|
||||
for i := range testSet {
|
||||
t.Run(fmt.Sprint("case-", i), func(t *testing.T) {
|
||||
test := &testSet[i]
|
||||
_, err := decryptObjectInfo(dst[:], test.Bucket, test.Name, test.UserDef)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_decryptObjectInfo(b *testing.B) {
|
||||
var testSet []struct {
|
||||
Bucket string
|
||||
Name string
|
||||
UserDef map[string]string
|
||||
}
|
||||
file, err := os.Open("testdata/decryptObjectInfo.json.zst")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
dec, err := zstd.NewReader(file)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer dec.Close()
|
||||
js := json.NewDecoder(dec)
|
||||
err = js.Decode(&testSet)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574")
|
||||
defer os.Setenv("MINIO_KMS_MASTER_KEY", "")
|
||||
GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(len(testSet)))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var dst [32]byte
|
||||
for i := range testSet {
|
||||
test := &testSet[i]
|
||||
_, err := decryptObjectInfo(dst[:], test.Bucket, test.Name, test.UserDef)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -29,9 +29,11 @@ import (
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
etcd "go.etcd.io/etcd/clientv3"
|
||||
"go.etcd.io/etcd/mvcc/mvccpb"
|
||||
@@ -85,38 +87,51 @@ func (ies *IAMEtcdStore) runlock() {
|
||||
ies.RUnlock()
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) saveIAMConfig(ctx context.Context, item interface{}, path string, opts ...options) error {
|
||||
func (ies *IAMEtcdStore) saveIAMConfig(ctx context.Context, item interface{}, itemPath string, opts ...options) error {
|
||||
data, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if globalConfigEncrypted {
|
||||
data, err = madmin.EncryptData(globalActiveCred.String(), data)
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, itemPath),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return saveKeyEtcd(ctx, ies.client, path, data, opts...)
|
||||
return saveKeyEtcd(ctx, ies.client, itemPath, data, opts...)
|
||||
}
|
||||
|
||||
func getIAMConfig(item interface{}, value []byte) error {
|
||||
conf := value
|
||||
func getIAMConfig(item interface{}, data []byte, itemPath string) error {
|
||||
var err error
|
||||
if globalConfigEncrypted && !utf8.Valid(value) {
|
||||
conf, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(conf))
|
||||
if err != nil {
|
||||
return err
|
||||
if !utf8.Valid(data) {
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, itemPath),
|
||||
})
|
||||
if err != nil {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.Unmarshal(conf, item)
|
||||
return json.Unmarshal(data, item)
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) loadIAMConfig(ctx context.Context, item interface{}, path string) error {
|
||||
pdata, err := readKeyEtcd(ctx, ies.client, path)
|
||||
data, err := readKeyEtcd(ctx, ies.client, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return getIAMConfig(item, pdata)
|
||||
return getIAMConfig(item, data, path)
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) deleteIAMConfig(ctx context.Context, path string) error {
|
||||
@@ -265,7 +280,7 @@ func (ies *IAMEtcdStore) loadPolicyDoc(ctx context.Context, policy string, m map
|
||||
|
||||
func (ies *IAMEtcdStore) getPolicyDoc(ctx context.Context, kvs *mvccpb.KeyValue, m map[string]iampolicy.Policy) error {
|
||||
var p iampolicy.Policy
|
||||
err := getIAMConfig(&p, kvs.Value)
|
||||
err := getIAMConfig(&p, kvs.Value, string(kvs.Key))
|
||||
if err != nil {
|
||||
if err == errConfigNotFound {
|
||||
return errNoSuchPolicy
|
||||
@@ -298,7 +313,7 @@ func (ies *IAMEtcdStore) loadPolicyDocs(ctx context.Context, m map[string]iampol
|
||||
|
||||
func (ies *IAMEtcdStore) getUser(ctx context.Context, userkv *mvccpb.KeyValue, userType IAMUserType, m map[string]auth.Credentials, basePrefix string) error {
|
||||
var u UserIdentity
|
||||
err := getIAMConfig(&u, userkv.Value)
|
||||
err := getIAMConfig(&u, userkv.Value, string(userkv.Key))
|
||||
if err != nil {
|
||||
if err == errConfigNotFound {
|
||||
return errNoSuchUser
|
||||
@@ -436,7 +451,7 @@ func (ies *IAMEtcdStore) loadMappedPolicy(ctx context.Context, name string, user
|
||||
|
||||
func getMappedPolicy(ctx context.Context, kv *mvccpb.KeyValue, userType IAMUserType, isGroup bool, m map[string]MappedPolicy, basePrefix string) error {
|
||||
var p MappedPolicy
|
||||
err := getIAMConfig(&p, kv.Value)
|
||||
err := getIAMConfig(&p, kv.Value, string(kv.Key))
|
||||
if err != nil {
|
||||
if err == errConfigNotFound {
|
||||
return errNoSuchPolicy
|
||||
|
||||
@@ -27,11 +27,11 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
@@ -204,29 +204,43 @@ func (iamOS *IAMObjectStore) migrateBackendFormat(ctx context.Context) error {
|
||||
return iamOS.migrateToV1(ctx)
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) saveIAMConfig(ctx context.Context, item interface{}, path string, opts ...options) error {
|
||||
func (iamOS *IAMObjectStore) saveIAMConfig(ctx context.Context, item interface{}, objPath string, opts ...options) error {
|
||||
data, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if globalConfigEncrypted {
|
||||
data, err = madmin.EncryptData(globalActiveCred.String(), data)
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, objPath),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return saveConfig(ctx, iamOS.objAPI, path, data)
|
||||
return saveConfig(ctx, iamOS.objAPI, objPath, data)
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) loadIAMConfig(ctx context.Context, item interface{}, path string) error {
|
||||
data, err := readConfig(ctx, iamOS.objAPI, path)
|
||||
func (iamOS *IAMObjectStore) loadIAMConfig(ctx context.Context, item interface{}, objPath string) error {
|
||||
data, err := readConfig(ctx, iamOS.objAPI, objPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if globalConfigEncrypted && !utf8.Valid(data) {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
if !utf8.Valid(data) {
|
||||
if GlobalKMS != nil {
|
||||
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, objPath),
|
||||
})
|
||||
if err != nil {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.Unmarshal(data, item)
|
||||
@@ -280,26 +294,6 @@ func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType
|
||||
return nil
|
||||
}
|
||||
|
||||
// If this is a service account, rotate the session key if needed
|
||||
if globalOldCred.IsValid() && u.Credentials.IsServiceAccount() {
|
||||
if !globalOldCred.Equal(globalActiveCred) {
|
||||
m := jwtgo.MapClaims{}
|
||||
stsTokenCallback := func(t *jwtgo.Token) (interface{}, error) {
|
||||
return []byte(globalOldCred.SecretKey), nil
|
||||
}
|
||||
if _, err := jwtgo.ParseWithClaims(u.Credentials.SessionToken, m, stsTokenCallback); err == nil {
|
||||
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m))
|
||||
if token, err := jwt.SignedString([]byte(globalActiveCred.SecretKey)); err == nil {
|
||||
u.Credentials.SessionToken = token
|
||||
err := iamOS.saveIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if u.Credentials.AccessKey == "" {
|
||||
u.Credentials.AccessKey = user
|
||||
}
|
||||
|
||||
@@ -21,12 +21,11 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/pkg/kms"
|
||||
)
|
||||
|
||||
// Return pointer to testOneByteReadEOF{}
|
||||
@@ -518,10 +517,8 @@ func enableCompression(t *testing.T, encrypt bool) {
|
||||
globalCompressConfigMu.Unlock()
|
||||
if encrypt {
|
||||
globalAutoEncryption = encrypt
|
||||
os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574")
|
||||
defer os.Setenv("MINIO_KMS_MASTER_KEY", "")
|
||||
var err error
|
||||
GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{})
|
||||
GlobalKMS, err = kms.Parse("my-minio-key:5lF+0pJM0OWwlQrvK2S/I7W9mO4a6rJJI7wzj7v09cw=")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -535,10 +532,8 @@ func enableEncrytion(t *testing.T) {
|
||||
globalCompressConfigMu.Unlock()
|
||||
|
||||
globalAutoEncryption = true
|
||||
os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574")
|
||||
defer os.Setenv("MINIO_KMS_MASTER_KEY", "")
|
||||
var err error
|
||||
GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{})
|
||||
GlobalKMS, err = kms.Parse("my-minio-key:5lF+0pJM0OWwlQrvK2S/I7W9mO4a6rJJI7wzj7v09cw=")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -81,12 +81,9 @@ func TestMain(m *testing.M) {
|
||||
|
||||
// disable ENVs which interfere with tests.
|
||||
for _, env := range []string{
|
||||
crypto.EnvAutoEncryptionLegacy,
|
||||
crypto.EnvKMSAutoEncryption,
|
||||
config.EnvAccessKey,
|
||||
config.EnvAccessKeyOld,
|
||||
config.EnvSecretKey,
|
||||
config.EnvSecretKeyOld,
|
||||
} {
|
||||
os.Unsetenv(env)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user