Migrate all backend at .minio.sys/config to encrypted backend (#8474)

- Supports migrating only when the credential ENVs are set,
  so any FS mode deployments which do not have ENVs set will
  continue to remain as is.
- Credential ENVs can be rotated using MINIO_ACCESS_KEY_OLD
  and MINIO_SECRET_KEY_OLD envs, in such scenarios it allowed
  to rotate the encrypted content to a new admin key.
master
Harshavardhana 5 years ago committed by kannappanr
parent fa325665b1
commit d28bcb4f84
  1. 13
      cmd/common-main.go
  2. 308
      cmd/config-encrypted.go
  3. 29
      cmd/config-migrate.go
  4. 37
      cmd/config.go
  5. 2
      cmd/config/constants.go
  6. 18
      cmd/config/errors.go
  7. 14
      cmd/gateway-common.go
  8. 23
      cmd/gateway-main.go
  9. 3
      cmd/globals.go
  10. 15
      cmd/iam-etcd-store.go
  11. 14
      cmd/iam-object-store.go
  12. 28
      cmd/server-main.go
  13. 4
      cmd/test-utils_test.go
  14. 18
      docs/config/README.md
  15. 13
      pkg/madmin/encrypt.go

@ -29,6 +29,7 @@ import (
"github.com/minio/minio-go/v6/pkg/set" "github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/certs" "github.com/minio/minio/pkg/certs"
"github.com/minio/minio/pkg/env" "github.com/minio/minio/pkg/env"
) )
@ -202,6 +203,18 @@ func handleCommonEnvVars() {
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then // or is not set to 'off', if MINIO_UPDATE is set to 'off' then
// in-place update is off. // in-place update is off.
globalInplaceUpdateDisabled = strings.EqualFold(env.Get(config.EnvUpdate, config.StateOn), config.StateOff) globalInplaceUpdateDisabled = strings.EqualFold(env.Get(config.EnvUpdate, config.StateOn), config.StateOff)
accessKey := env.Get(config.EnvAccessKey, "")
secretKey := env.Get(config.EnvSecretKey, "")
if accessKey != "" && secretKey != "" {
cred, err := auth.CreateCredentials(accessKey, secretKey)
if err != nil {
logger.Fatal(config.ErrInvalidCredentials(err),
"Unable to validate credentials inherited from the shell environment")
}
globalActiveCred = cred
globalConfigEncrypted = true
}
} }
func logStartupMessage(msg string) { func logStartupMessage(msg string) {

@ -0,0 +1,308 @@
/*
* 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 cmd
import (
"bytes"
"context"
"strings"
etcd "github.com/coreos/etcd/clientv3"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/env"
"github.com/minio/minio/pkg/madmin"
)
func handleEncryptedConfigBackend(objAPI ObjectLayer, server bool) error {
if !server {
return nil
}
// If its server mode or nas gateway, migrate the backend.
doneCh := make(chan struct{})
defer close(doneCh)
var encrypted bool
var err error
// Migrating Config backend needs a retry mechanism for
// the following reasons:
// - Read quorum is lost just after the initialization
// of the object layer.
for range newRetryTimerSimple(doneCh) {
if encrypted, err = checkBackendEncrypted(objAPI); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for config backend to be encrypted..")
continue
}
return err
}
break
}
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 errInvalidArgument
}
}
accessKeyOld := env.Get(config.EnvAccessKeyOld, "")
secretKeyOld := env.Get(config.EnvSecretKeyOld, "")
var activeCredOld auth.Credentials
if accessKeyOld != "" && secretKeyOld != "" {
activeCredOld, err = auth.CreateCredentials(accessKeyOld, secretKeyOld)
if err != nil {
return err
}
}
// Migrating Config backend needs a retry mechanism for
// the following reasons:
// - Read quorum is lost just after the initialization
// of the object layer.
for range newRetryTimerSimple(doneCh) {
// Migrate IAM configuration
if err = migrateConfigPrefixToEncrypted(objAPI, activeCredOld, encrypted); err != nil {
if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
logger.Info("Waiting for config backend to be encrypted..")
continue
}
return err
}
break
}
return nil
}
const (
backendEncryptedFile = "backend-encrypted"
)
var (
backendEncryptedFileValue = []byte("encrypted")
)
func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) {
data, err := readKeyEtcd(ctx, client, backendEncryptedFile)
if err != nil && err != errConfigNotFound {
return false, err
}
return err == nil && bytes.Equal(data, backendEncryptedFileValue), nil
}
func checkBackendEncrypted(objAPI ObjectLayer) (bool, error) {
data, err := readConfig(context.Background(), objAPI, backendEncryptedFile)
if err != nil && err != errConfigNotFound {
return false, err
}
return err == nil && bytes.Equal(data, backendEncryptedFileValue), nil
}
func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
encrypted, err := checkBackendEtcdEncrypted(ctx, client)
if err != nil {
return 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 errInvalidArgument
}
}
accessKeyOld := env.Get(config.EnvAccessKeyOld, "")
secretKeyOld := env.Get(config.EnvSecretKeyOld, "")
var activeCredOld auth.Credentials
if accessKeyOld != "" && secretKeyOld != "" {
activeCredOld, err = auth.CreateCredentials(accessKeyOld, secretKeyOld)
if err != nil {
return err
}
}
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
}
}
if !activeCredOld.IsValid() {
logger.Info("Attempting a one time encrypt of all IAM users and policies on etcd")
} else {
logger.Info("Attempting a rotation of encrypted IAM users and policies on etcd with newly supplied credentials")
}
r, err := client.Get(ctx, minioConfigPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if 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
}
// Is rotating of creds requested?
if activeCredOld.IsValid() {
cdata, err = madmin.DecryptData(activeCredOld.String(), bytes.NewReader(cdata))
if err != nil {
if err == madmin.ErrMaliciousData {
return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil)
}
return err
}
}
cencdata, err = madmin.EncryptData(globalActiveCred.String(), cdata)
if err != nil {
return err
}
if err = saveKeyEtcd(ctx, client, string(kv.Key), cencdata); err != nil {
return err
}
}
return saveKeyEtcd(ctx, client, backendEncryptedFile, backendEncryptedFileValue)
}
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
}
}
if !activeCredOld.IsValid() {
logger.Info("Attempting a one time encrypt of all config, IAM users and policies on MinIO backend")
} else {
logger.Info("Attempting a rotation of encrypted config, IAM users and policies on MinIO with newly supplied credentials")
}
// Construct path to config/transaction.lock for locking
transactionConfigPrefix := minioConfigPrefix + "/transaction.lock"
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
// and configFile, take a transaction lock to avoid data race between readConfig()
// and saveConfig().
objLock := globalNSMutex.NewNSLock(context.Background(), minioMetaBucket, transactionConfigPrefix)
if err := objLock.GetLock(globalOperationTimeout); err != nil {
return err
}
defer objLock.Unlock()
var marker string
for {
res, err := objAPI.ListObjects(context.Background(), minioMetaBucket, minioConfigPrefix, marker, "", maxObjectList)
if err != nil {
return err
}
for _, obj := range res.Objects {
var (
cdata []byte
cencdata []byte
)
cdata, err = readConfig(context.Background(), objAPI, obj.Name)
if err != nil {
return err
}
// Is rotating of creds requested?
if activeCredOld.IsValid() {
cdata, err = madmin.DecryptData(activeCredOld.String(), bytes.NewReader(cdata))
if err != nil {
if err == madmin.ErrMaliciousData {
return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil)
}
return err
}
}
cencdata, err = madmin.EncryptData(globalActiveCred.String(), cdata)
if err != nil {
return err
}
if err = saveConfig(context.Background(), objAPI, obj.Name, cencdata); err != nil {
return err
}
}
if !res.IsTruncated {
break
}
marker = res.NextMarker
}
return saveConfig(context.Background(), objAPI, backendEncryptedFile, backendEncryptedFileValue)
}

@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -38,6 +39,7 @@ import (
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/event/target" "github.com/minio/minio/pkg/event/target"
"github.com/minio/minio/pkg/madmin"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/quick" "github.com/minio/minio/pkg/quick"
) )
@ -2442,12 +2444,13 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
} }
}() }()
transactionConfigFile := configFile + ".transaction" // Construct path to config/transaction.lock for locking
transactionConfigPrefix := minioConfigPrefix + "/transaction.lock"
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket // As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
// and configFile, take a transaction lock to avoid data race between readConfig() // and configFile, take a transaction lock to avoid data race between readConfig()
// and saveConfig(). // and saveConfig().
objLock := globalNSMutex.NewNSLock(context.Background(), minioMetaBucket, transactionConfigFile) objLock := globalNSMutex.NewNSLock(context.Background(), minioMetaBucket, transactionConfigPrefix)
if err = objLock.GetLock(globalOperationTimeout); err != nil { if err = objLock.GetLock(globalOperationTimeout); err != nil {
return err return err
} }
@ -2496,13 +2499,13 @@ func migrateMinioSysConfig(objAPI ObjectLayer) error {
return nil return nil
} }
// Construct path to config.json for the given bucket. // Construct path to config/transaction.lock for locking
transactionConfigFile := configFile + ".transaction" transactionConfigPrefix := minioConfigPrefix + "/transaction.lock"
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket // As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
// and configFile, take a transaction lock to avoid data race between readConfig() // and configFile, take a transaction lock to avoid data race between readConfig()
// and saveConfig(). // and saveConfig().
objLock := globalNSMutex.NewNSLock(context.Background(), minioMetaBucket, transactionConfigFile) objLock := globalNSMutex.NewNSLock(context.Background(), minioMetaBucket, transactionConfigPrefix)
if err := objLock.GetLock(globalOperationTimeout); err != nil { if err := objLock.GetLock(globalOperationTimeout); err != nil {
return err return err
} }
@ -2532,6 +2535,16 @@ func checkConfigVersion(objAPI ObjectLayer, configFile string, version string) (
return false, nil, err return false, nil, err
} }
if globalConfigEncrypted {
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
}
}
var versionConfig struct { var versionConfig struct {
Version string `json:"version"` Version string `json:"version"`
} }
@ -2793,13 +2806,13 @@ func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
notify.SetNotifyWebhook(newCfg, k, args) notify.SetNotifyWebhook(newCfg, k, args)
} }
// Construct path to config.json for the given bucket. // Construct path to config/transaction.lock for locking
transactionConfigFile := configFile + ".transaction" transactionConfigPrefix := minioConfigPrefix + "/transaction.lock"
// As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket // As object layer's GetObject() and PutObject() take respective lock on minioMetaBucket
// and configFile, take a transaction lock to avoid data race between readConfig() // and configFile, take a transaction lock to avoid data race between readConfig()
// and saveConfig(). // and saveConfig().
objLock := globalNSMutex.NewNSLock(context.Background(), minioMetaBucket, transactionConfigFile) objLock := globalNSMutex.NewNSLock(context.Background(), minioMetaBucket, transactionConfigPrefix)
if err = objLock.GetLock(globalOperationTimeout); err != nil { if err = objLock.GetLock(globalOperationTimeout); err != nil {
return err return err
} }

@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -82,6 +83,11 @@ func readServerConfigHistory(ctx context.Context, objAPI ObjectLayer, uuidKV str
if err != nil { if err != nil {
return nil, err return nil, err
} }
if globalConfigEncrypted {
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
}
return data, err return data, err
} }
@ -89,6 +95,14 @@ func saveServerConfigHistory(ctx context.Context, objAPI ObjectLayer, kv []byte)
uuidKV := mustGetUUID() + ".kv" uuidKV := mustGetUUID() + ".kv"
historyFile := pathJoin(minioConfigHistoryPrefix, uuidKV) historyFile := pathJoin(minioConfigHistoryPrefix, uuidKV)
var err error
if globalConfigEncrypted {
kv, err = madmin.EncryptData(globalActiveCred.String(), kv)
if err != nil {
return err
}
}
// Save the new config KV settings into the history path. // Save the new config KV settings into the history path.
return saveConfig(ctx, objAPI, historyFile, kv) return saveConfig(ctx, objAPI, historyFile, kv)
} }
@ -121,6 +135,12 @@ func saveServerConfig(ctx context.Context, objAPI ObjectLayer, config interface{
if err != nil { if err != nil {
return err return err
} }
if globalConfigEncrypted {
oldData, err = madmin.EncryptData(globalActiveCred.String(), oldData)
if err != nil {
return err
}
}
} }
// No need to take backups for fresh setups. // No need to take backups for fresh setups.
@ -130,6 +150,13 @@ func saveServerConfig(ctx context.Context, objAPI ObjectLayer, config interface{
} }
} }
if globalConfigEncrypted {
data, err = madmin.EncryptData(globalActiveCred.String(), data)
if err != nil {
return err
}
}
// Save the new config in the std config path // Save the new config in the std config path
return saveConfig(ctx, objAPI, configFile, data) return saveConfig(ctx, objAPI, configFile, data)
} }
@ -141,6 +168,16 @@ func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, e
return nil, err return nil, err
} }
if globalConfigEncrypted {
configData, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(configData))
if err != nil {
if err == madmin.ErrMaliciousData {
return nil, config.ErrInvalidCredentialsBackendEncrypted(nil)
}
return nil, err
}
}
var config = config.New() var config = config.New()
if err = json.Unmarshal(configData, &config); err != nil { if err = json.Unmarshal(configData, &config); err != nil {
return nil, err return nil, err

@ -25,6 +25,8 @@ const (
const ( const (
EnvAccessKey = "MINIO_ACCESS_KEY" EnvAccessKey = "MINIO_ACCESS_KEY"
EnvSecretKey = "MINIO_SECRET_KEY" EnvSecretKey = "MINIO_SECRET_KEY"
EnvAccessKeyOld = "MINIO_ACCESS_KEY_OLD"
EnvSecretKeyOld = "MINIO_SECRET_KEY_OLD"
EnvBrowser = "MINIO_BROWSER" EnvBrowser = "MINIO_BROWSER"
EnvDomain = "MINIO_DOMAIN" EnvDomain = "MINIO_DOMAIN"
EnvRegionName = "MINIO_REGION_NAME" EnvRegionName = "MINIO_REGION_NAME"

@ -72,6 +72,24 @@ var (
"MINIO_CACHE_ENCRYPTION_MASTER_KEY: For more information, please refer to https://docs.min.io/docs/minio-disk-cache-guide", "MINIO_CACHE_ENCRYPTION_MASTER_KEY: For more information, please refer to https://docs.min.io/docs/minio-disk-cache-guide",
) )
ErrInvalidRotatingCredentialsBackendEncrypted = newErrFn(
"Invalid rotating credentials",
"Please set correct rotating credentials in the environment for decryption",
`Detected encrypted config backend, correct old access and secret keys should be specified via environment variables MINIO_ACCESS_KEY_OLD and MINIO_SECRET_KEY_OLD to be able to re-encrypt the MinIO config, user IAM and policies with new credentials`,
)
ErrInvalidCredentialsBackendEncrypted = newErrFn(
"Invalid credentials",
"Please set correct credentials in the environment for decryption",
`Detected encrypted config backend, correct access and secret keys should be specified via environment variables MINIO_ACCESS_KEY and MINIO_SECRET_KEY to be able to decrypt the MinIO config, user IAM and policies`,
)
ErrMissingCredentialsBackendEncrypted = newErrFn(
"Credentials missing",
"Please set your credentials in the environment",
`Detected encrypted config backend, access and secret keys should be specified via environment variables MINIO_ACCESS_KEY and MINIO_SECRET_KEY to be able to decrypt the MinIO config, user IAM and policies`,
)
ErrInvalidCredentials = newErrFn( ErrInvalidCredentials = newErrFn(
"Invalid credentials", "Invalid credentials",
"Please provide correct credentials", "Please provide correct credentials",

@ -23,7 +23,6 @@ import (
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/env" "github.com/minio/minio/pkg/env"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
@ -373,15 +372,14 @@ func parseGatewaySSE(s string) (gatewaySSE, error) {
} }
// handle gateway env vars // handle gateway env vars
func handleGatewayEnvVars() { func gatewayHandleEnvVars() {
accessKey := env.Get(config.EnvAccessKey, "") // Handle common env vars.
secretKey := env.Get(config.EnvSecretKey, "") handleCommonEnvVars()
cred, err := auth.CreateCredentials(accessKey, secretKey)
if err != nil { if !globalActiveCred.IsValid() {
logger.Fatal(config.ErrInvalidCredentials(err), logger.Fatal(config.ErrInvalidCredentials(nil),
"Unable to validate credentials inherited from the shell environment") "Unable to validate credentials inherited from the shell environment")
} }
globalActiveCred = cred
gwsseVal := env.Get("MINIO_GATEWAY_SSE", "") gwsseVal := env.Get("MINIO_GATEWAY_SSE", "")
if len(gwsseVal) != 0 { if len(gwsseVal) != 0 {

@ -136,11 +136,8 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
globalRootCAs, err = config.GetRootCAs(globalCertsCADir.Get()) globalRootCAs, err = config.GetRootCAs(globalCertsCADir.Get())
logger.FatalIf(err, "Failed to read root CAs (%v)", err) logger.FatalIf(err, "Failed to read root CAs (%v)", err)
// Handle common env vars.
handleCommonEnvVars()
// Handle gateway specific env // Handle gateway specific env
handleGatewayEnvVars() gatewayHandleEnvVars()
// Set system resources to maximum. // Set system resources to maximum.
logger.LogIf(context.Background(), setMaxResources()) logger.LogIf(context.Background(), setMaxResources())
@ -230,6 +227,16 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
initFederatorBackend(newObject) initFederatorBackend(newObject)
} }
// Migrate all backend configs to encrypted backend, also handles rotation as well.
// For "nas" gateway we need to specially handle the backend migration as well.
// Internally code handles migrating etcd if enabled automatically.
logger.FatalIf(handleEncryptedConfigBackend(newObject, enableConfigOps),
"Unable to handle encrypted backend for config, iam and policies")
// **** WARNING ****
// Migrating to encrypted backend should happen before initialization of any
// sub-systems, make sure that we do not move the above codeblock elsewhere.
if enableConfigOps { if enableConfigOps {
// Create a new config system. // Create a new config system.
globalConfigSys = NewConfigSys() globalConfigSys = NewConfigSys()
@ -246,6 +253,14 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
globalDeploymentID = env.Get("MINIO_GATEWAY_DEPLOYMENT_ID", mustGetUUID()) globalDeploymentID = env.Get("MINIO_GATEWAY_DEPLOYMENT_ID", mustGetUUID())
logger.SetDeploymentID(globalDeploymentID) logger.SetDeploymentID(globalDeploymentID)
if globalEtcdClient != nil {
// **** WARNING ****
// Migrating to encrypted backend on etcd should happen before initialization of
// IAM sub-systems, make sure that we do not move the above codeblock elsewhere.
logger.FatalIf(migrateIAMConfigsEtcdToEncrypted(globalEtcdClient),
"Unable to handle encrypted backend for iam and policies")
}
if globalCacheConfig.Enabled { if globalCacheConfig.Enabled {
// initialize the new disk cache objects. // initialize the new disk cache objects.
globalCacheObjectAPI, err = newServerCacheObjects(context.Background(), globalCacheConfig) globalCacheObjectAPI, err = newServerCacheObjects(context.Background(), globalCacheConfig)

@ -186,6 +186,9 @@ var (
globalActiveCred auth.Credentials globalActiveCred auth.Credentials
// Indicates if config is to be encrypted
globalConfigEncrypted bool
globalPublicCerts []*x509.Certificate globalPublicCerts []*x509.Certificate
globalDomainNames []string // Root domains for virtual host style requests globalDomainNames []string // Root domains for virtual host style requests

@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
@ -31,6 +32,7 @@ import (
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
iampolicy "github.com/minio/minio/pkg/iam/policy" iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin"
) )
var defaultContextTimeout = 30 * time.Second var defaultContextTimeout = 30 * time.Second
@ -109,6 +111,12 @@ func (ies *IAMEtcdStore) saveIAMConfig(item interface{}, path string) error {
if err != nil { if err != nil {
return err return err
} }
if globalConfigEncrypted {
data, err = madmin.EncryptData(globalActiveCred.String(), data)
if err != nil {
return err
}
}
return saveKeyEtcd(ies.getContext(), ies.client, path, data) return saveKeyEtcd(ies.getContext(), ies.client, path, data)
} }
@ -118,6 +126,13 @@ func (ies *IAMEtcdStore) loadIAMConfig(item interface{}, path string) error {
return err return err
} }
if globalConfigEncrypted {
pdata, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(pdata))
if err != nil {
return err
}
}
return json.Unmarshal(pdata, item) return json.Unmarshal(pdata, item)
} }

@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
@ -27,6 +28,7 @@ import (
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
iampolicy "github.com/minio/minio/pkg/iam/policy" iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin"
) )
// IAMObjectStore implements IAMStorageAPI // IAMObjectStore implements IAMStorageAPI
@ -215,6 +217,12 @@ func (iamOS *IAMObjectStore) saveIAMConfig(item interface{}, path string) error
if err != nil { if err != nil {
return err return err
} }
if globalConfigEncrypted {
data, err = madmin.EncryptData(globalActiveCred.String(), data)
if err != nil {
return err
}
}
return saveConfig(context.Background(), objectAPI, path, data) return saveConfig(context.Background(), objectAPI, path, data)
} }
@ -224,6 +232,12 @@ func (iamOS *IAMObjectStore) loadIAMConfig(item interface{}, path string) error
if err != nil { if err != nil {
return err return err
} }
if globalConfigEncrypted {
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
if err != nil {
return err
}
}
return json.Unmarshal(data, item) return json.Unmarshal(data, item)
} }

@ -178,18 +178,6 @@ func serverHandleCmdArgs(ctx *cli.Context) {
func serverHandleEnvVars() { func serverHandleEnvVars() {
// Handle common environment variables. // Handle common environment variables.
handleCommonEnvVars() handleCommonEnvVars()
accessKey := env.Get(config.EnvAccessKey, "")
secretKey := env.Get(config.EnvSecretKey, "")
if accessKey != "" && secretKey != "" {
cred, err := auth.CreateCredentials(accessKey, secretKey)
if err != nil {
logger.Fatal(config.ErrInvalidCredentials(err),
"Unable to validate credentials inherited from the shell environment")
}
globalActiveCred = cred
}
} }
func initAllSubsystems(newObject ObjectLayer) { func initAllSubsystems(newObject ObjectLayer) {
@ -350,9 +338,25 @@ func serverMain(ctx *cli.Context) {
// Re-enable logging // Re-enable logging
logger.Disable = false logger.Disable = false
// Migrate all backend configs to encrypted backend, also handles rotation as well.
logger.FatalIf(handleEncryptedConfigBackend(newObject, true),
"Unable to handle encrypted backend for config, iam and policies")
// **** WARNING ****
// Migrating to encrypted backend should happen before initialization of any
// sub-systems, make sure that we do not move the above codeblock elsewhere.
// Validate and initialize all subsystems. // Validate and initialize all subsystems.
initAllSubsystems(newObject) initAllSubsystems(newObject)
if globalEtcdClient != nil {
// **** WARNING ****
// Migrating to encrypted backend on etcd should happen before initialization of
// IAM sub-systems, make sure that we do not move the above codeblock elsewhere.
logger.FatalIf(migrateIAMConfigsEtcdToEncrypted(globalEtcdClient),
"Unable to handle encrypted backend for iam and policies")
}
if globalCacheConfig.Enabled { if globalCacheConfig.Enabled {
logger.StartupMessage(color.Red(color.Bold("Disk caching is recommended only for gateway deployments"))) logger.StartupMessage(color.Red(color.Bold("Disk caching is recommended only for gateway deployments")))

@ -523,13 +523,13 @@ func newTestConfig(bucketLocation string, obj ObjectLayer) (err error) {
return err return err
} }
logger.Disable = true
globalActiveCred = auth.Credentials{ globalActiveCred = auth.Credentials{
AccessKey: auth.DefaultAccessKey, AccessKey: auth.DefaultAccessKey,
SecretKey: auth.DefaultSecretKey, SecretKey: auth.DefaultSecretKey,
} }
globalConfigEncrypted = true
// Set a default region. // Set a default region.
config.SetRegion(globalServerConfig, bucketLocation) config.SetRegion(globalServerConfig, bucketLocation)

@ -32,7 +32,7 @@ $ mc tree --files ~/.minio
You can provide a custom certs directory using `--certs-dir` command line option. You can provide a custom certs directory using `--certs-dir` command line option.
#### Credentials #### Credentials
On MinIO admin credentials or root credentials are only allowed to be changed using ENVs `MINIO_ACCESS_KEY` and `MINIO_SECRET_KEY`. On MinIO admin credentials or root credentials are only allowed to be changed using ENVs namely `MINIO_ACCESS_KEY` and `MINIO_SECRET_KEY`. Using the combination of these two values MinIO encrypts the config stored at the backend.
``` ```
export MINIO_ACCESS_KEY=minio export MINIO_ACCESS_KEY=minio
@ -40,6 +40,22 @@ export MINIO_SECRET_KEY=minio13
minio server /data minio server /data
``` ```
##### Rotating encryption with new credentials
Additionally if you wish to change the admin credentials, then MinIO will automatically detect this and re-encrypt with new credentials as shown below. For one time only special ENVs as shown below needs to be set for rotating the encryption config.
> Old ENVs are never remembered in memory and are destroyed right after they are used to migrate your existing content with new credentials. You are safe to remove them after the server as successfully started, by restarting the services once again.
```
export MINIO_ACCESS_KEY=newminio
export MINIO_SECRET_KEY=newminio123
export MINIO_ACCESS_KEY_OLD=minio
export MINIO_SECRET_KEY_OLD=minio123
minio server /data
```
Once the migration is complete and server has started successfully remove `MINIO_ACCESS_KEY_OLD` and `MINIO_SECRET_KEY_OLD` environment variables, restart the server.
#### Region #### Region
| Field | Type | Description | | Field | Type | Description |
|:--------------------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |:--------------------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

@ -76,6 +76,10 @@ func EncryptData(password string, data []byte) ([]byte, error) {
return ciphertext.Bytes(), nil return ciphertext.Bytes(), nil
} }
// ErrMaliciousData indicates that the stream cannot be
// decrypted by provided credentials.
var ErrMaliciousData = sio.NotAuthentic
// DecryptData decrypts the data with the key derived // DecryptData decrypts the data with the key derived
// from the salt (part of data) and the password using // from the salt (part of data) and the password using
// the PBKDF used in EncryptData. DecryptData returns // the PBKDF used in EncryptData. DecryptData returns
@ -116,7 +120,14 @@ func DecryptData(password string, data io.Reader) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ioutil.ReadAll(stream.DecryptReader(data, nonce[:], nil))
enBytes, err := ioutil.ReadAll(stream.DecryptReader(data, nonce[:], nil))
if err != nil {
if err == sio.NotAuthentic {
return enBytes, ErrMaliciousData
}
}
return enBytes, err
} }
const ( const (

Loading…
Cancel
Save