2021-04-18 15:41:13 -04:00
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2019-11-01 18:53:16 -04:00
package cmd
import (
"bytes"
"context"
2019-12-24 16:49:48 -05:00
"fmt"
2021-04-22 11:45:30 -04:00
"path"
2020-04-27 00:42:41 -04:00
"time"
2019-11-11 21:42:10 -05:00
"unicode/utf8"
2019-11-01 18:53:16 -04:00
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
2021-04-22 11:45:30 -04:00
"github.com/minio/minio/pkg/kms"
2019-11-01 18:53:16 -04:00
"github.com/minio/minio/pkg/madmin"
2020-12-03 14:35:18 -05:00
etcd "go.etcd.io/etcd/clientv3"
2019-11-01 18:53:16 -04:00
)
2020-06-25 06:29:28 -04:00
func handleEncryptedConfigBackend ( objAPI ObjectLayer ) error {
2020-05-04 23:04:06 -04:00
encrypted , err := checkBackendEncrypted ( objAPI )
if err != nil {
return fmt . Errorf ( "Unable to encrypt config %w" , err )
2019-11-01 18:53:16 -04:00
}
2021-04-22 11:45:30 -04:00
if err = migrateConfigPrefixToEncrypted ( objAPI , encrypted ) ; err != nil {
2020-05-04 23:04:06 -04:00
return fmt . Errorf ( "Unable to migrate all config at .minio.sys/config/: %w" , err )
2019-11-01 18:53:16 -04:00
}
2020-05-04 23:04:06 -04:00
return nil
2019-11-01 18:53:16 -04:00
}
2021-04-22 11:45:30 -04:00
const backendEncryptedFile = "backend-encrypted"
2019-11-01 18:53:16 -04:00
2021-04-22 11:45:30 -04:00
var backendEncryptedMigrationComplete = [ ] byte ( "encrypted" )
2019-11-01 18:53:16 -04:00
func checkBackendEtcdEncrypted ( ctx context . Context , client * etcd . Client ) ( bool , error ) {
data , err := readKeyEtcd ( ctx , client , backendEncryptedFile )
if err != nil && err != errConfigNotFound {
return false , err
}
2019-11-09 12:27:23 -05:00
return err == nil && bytes . Equal ( data , backendEncryptedMigrationComplete ) , nil
2019-11-01 18:53:16 -04:00
}
func checkBackendEncrypted ( objAPI ObjectLayer ) ( bool , error ) {
2020-04-09 12:30:02 -04:00
data , err := readConfig ( GlobalContext , objAPI , backendEncryptedFile )
2019-11-01 18:53:16 -04:00
if err != nil && err != errConfigNotFound {
return false , err
}
2019-11-09 12:27:23 -05:00
return err == nil && bytes . Equal ( data , backendEncryptedMigrationComplete ) , nil
2019-11-01 18:53:16 -04:00
}
2019-11-11 15:01:21 -05:00
// decryptData - decrypts input data with more that one credentials,
func decryptData ( edata [ ] byte , creds ... auth . Credentials ) ( [ ] byte , error ) {
var err error
var data [ ] byte
for _ , cred := range creds {
data , err = madmin . DecryptData ( cred . String ( ) , bytes . NewReader ( edata ) )
if err != nil {
if err == madmin . ErrMaliciousData {
continue
}
return nil , err
}
break
}
return data , err
}
2020-04-07 17:26:39 -04:00
func migrateIAMConfigsEtcdToEncrypted ( ctx context . Context , client * etcd . Client ) error {
2019-11-01 18:53:16 -04:00
encrypted , err := checkBackendEtcdEncrypted ( ctx , client )
if err != nil {
return err
}
if encrypted {
2021-04-22 11:45:30 -04:00
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" )
2019-11-01 18:53:16 -04:00
}
}
2020-04-27 00:42:41 -04:00
listCtx , cancel := context . WithTimeout ( ctx , 1 * time . Minute )
defer cancel ( )
r , err := client . Get ( listCtx , minioConfigPrefix , etcd . WithPrefix ( ) , etcd . WithKeysOnly ( ) )
2019-11-01 18:53:16 -04:00
if err != nil {
return err
}
2019-11-09 12:27:23 -05:00
2019-11-01 18:53:16 -04:00
for _ , kv := range r . Kvs {
2021-04-22 11:45:30 -04:00
data , err := readKeyEtcd ( ctx , client , string ( kv . Key ) )
if err == errConfigNotFound { // Perhaps not present or someone deleted it.
continue
}
2019-11-01 18:53:16 -04:00
if err != nil {
return err
}
2019-11-09 12:27:23 -05:00
2021-04-22 11:45:30 -04:00
if ! utf8 . Valid ( data ) {
data , err = decryptData ( data , globalActiveCred )
2019-11-01 18:53:16 -04:00
if err != nil {
2021-04-22 11:45:30 -04:00
return fmt . Errorf ( "Decrypting config failed %w, possibly credentials are incorrect" , err )
2019-11-01 18:53:16 -04:00
}
}
2021-04-22 11:45:30 -04:00
if GlobalKMS != nil {
data , err = config . EncryptBytes ( GlobalKMS , data , kms . Context {
minioMetaBucket : string ( kv . Key ) ,
} )
if err != nil {
return err
2020-01-10 05:35:06 -05:00
}
2019-11-09 12:27:23 -05:00
}
2021-04-22 11:45:30 -04:00
if err = saveKeyEtcd ( ctx , client , string ( kv . Key ) , data ) ; err != nil {
2019-11-01 18:53:16 -04:00
return err
}
}
2020-01-10 05:35:06 -05:00
2021-04-22 11:45:30 -04:00
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" )
}
2019-11-09 12:27:23 -05:00
}
2021-04-22 11:45:30 -04:00
return deleteKeyEtcd ( ctx , client , backendEncryptedFile )
2019-11-01 18:53:16 -04:00
}
2021-04-22 11:45:30 -04:00
func migrateConfigPrefixToEncrypted ( objAPI ObjectLayer , encrypted bool ) error {
if ! encrypted {
return nil
}
2019-11-01 18:53:16 -04:00
if encrypted {
2021-04-22 11:45:30 -04:00
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" )
2019-11-01 18:53:16 -04:00
}
}
var marker string
for {
2021-04-22 11:45:30 -04:00
res , err := objAPI . ListObjects ( GlobalContext , minioMetaBucket , minioConfigPrefix , marker , "" , maxObjectList )
2019-11-01 18:53:16 -04:00
if err != nil {
return err
}
for _ , obj := range res . Objects {
2021-04-22 11:45:30 -04:00
data , err := readConfig ( GlobalContext , objAPI , obj . Name )
2019-11-01 18:53:16 -04:00
if err != nil {
return err
}
2021-04-22 11:45:30 -04:00
if ! utf8 . Valid ( data ) {
data , err = decryptData ( data , globalActiveCred )
2019-11-01 18:53:16 -04:00
if err != nil {
2021-04-22 11:45:30 -04:00
return fmt . Errorf ( "Decrypting config failed %w, possibly credentials are incorrect" , err )
2019-11-01 18:53:16 -04:00
}
}
2021-04-22 11:45:30 -04:00
if GlobalKMS != nil {
data , err = config . EncryptBytes ( GlobalKMS , data , kms . Context {
obj . Bucket : path . Join ( obj . Bucket , obj . Name ) ,
} )
if err != nil {
return err
2020-01-10 05:35:06 -05:00
}
2019-11-09 12:27:23 -05:00
}
2021-04-22 11:45:30 -04:00
if err = saveConfig ( GlobalContext , objAPI , obj . Name , data ) ; err != nil {
2019-11-01 18:53:16 -04:00
return err
}
}
if ! res . IsTruncated {
break
}
marker = res . NextMarker
}
2021-04-22 11:45:30 -04:00
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" )
}
2019-11-09 12:27:23 -05:00
}
2021-04-22 11:45:30 -04:00
return deleteConfig ( GlobalContext , globalObjectAPI , backendEncryptedFile )
2019-11-01 18:53:16 -04:00
}