mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
remove IAM old migration code (#15476)
```
commit 7bdaf9bc50
Author: Aditya Manthramurthy <donatello@users.noreply.github.com>
Date: Wed Jul 24 17:34:23 2019 -0700
Update on-disk storage format for users system (#7949)
```
Bonus: fixes a bug when etcd keys were being re-encrypted.
This commit is contained in:
parent
fcd4b3ba9b
commit
e0b0a351c6
@ -69,6 +69,12 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
|
||||
return err
|
||||
}
|
||||
|
||||
// If backend doesn't have this file means we have already
|
||||
// attempted then migration
|
||||
if !encrypted {
|
||||
return nil
|
||||
}
|
||||
|
||||
if encrypted && GlobalKMS != nil {
|
||||
stat, err := GlobalKMS.Stat(ctx)
|
||||
if err != nil {
|
||||
@ -109,6 +115,8 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
|
||||
return fmt.Errorf("Decrypting IAM config failed %w, possibly credentials are incorrect", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Decrypting IAM config failed %w, possibly credentials are incorrect", err)
|
||||
}
|
||||
}
|
||||
data = pdata
|
||||
@ -131,6 +139,7 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
|
||||
if encrypted && GlobalKMS != nil {
|
||||
logger.Info("Migration of encrypted IAM config data completed. All data is now encrypted on etcd.")
|
||||
}
|
||||
|
||||
return deleteKeyEtcd(ctx, client, backendEncryptedFile)
|
||||
}
|
||||
|
||||
@ -146,44 +155,56 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, encrypted bool) error {
|
||||
logger.Info(fmt.Sprintf("Attempting to re-encrypt config, IAM users and policies on MinIO with %q (%s)", stat.DefaultKey, stat.Name))
|
||||
}
|
||||
|
||||
var marker string
|
||||
for {
|
||||
res, err := objAPI.ListObjects(GlobalContext, minioMetaBucket, minioConfigPrefix, marker, "", maxObjectList)
|
||||
results := make(chan ObjectInfo)
|
||||
if err := objAPI.Walk(GlobalContext, minioMetaBucket, minioConfigPrefix, results, ObjectOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for obj := range results {
|
||||
data, err := readConfig(GlobalContext, objAPI, obj.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, obj := range res.Objects {
|
||||
data, err := readConfig(GlobalContext, objAPI, obj.Name)
|
||||
|
||||
if !utf8.Valid(data) {
|
||||
pdata, err := madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
if GlobalKMS != nil {
|
||||
pdata, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: path.Join(minioMetaBucket, obj.Name),
|
||||
})
|
||||
if err != nil {
|
||||
pdata, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
||||
minioMetaBucket: obj.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Decrypting IAM config failed %w, possibly credentials are incorrect", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Decrypting IAM config failed %w, possibly credentials are incorrect", err)
|
||||
}
|
||||
}
|
||||
data = pdata
|
||||
}
|
||||
|
||||
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 !utf8.Valid(data) {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Decrypting config failed %w, possibly credentials are incorrect", 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, data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !res.IsTruncated {
|
||||
break
|
||||
if err = saveConfig(GlobalContext, objAPI, obj.Name, data); err != nil {
|
||||
return err
|
||||
}
|
||||
marker = res.NextMarker
|
||||
}
|
||||
|
||||
if encrypted && GlobalKMS != nil {
|
||||
logger.Info("Migration of encrypted config data completed. All config data is now encrypted.")
|
||||
}
|
||||
|
||||
return deleteConfig(GlobalContext, objAPI, backendEncryptedFile)
|
||||
}
|
||||
|
@ -57,10 +57,6 @@ func (ids *iamDummyStore) getUsersSysType() UsersSysType {
|
||||
return ids.usersSysType
|
||||
}
|
||||
|
||||
func (ids *iamDummyStore) migrateBackendFormat(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ids *iamDummyStore) loadPolicyDoc(ctx context.Context, policy string, m map[string]PolicyDoc) error {
|
||||
v, ok := ids.iamPolicyDocsMap[policy]
|
||||
if !ok {
|
||||
|
@ -28,7 +28,6 @@ import (
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@ -163,121 +162,6 @@ func (ies *IAMEtcdStore) deleteIAMConfig(ctx context.Context, path string) error
|
||||
return deleteKeyEtcd(ctx, ies.client, path)
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) migrateUsersConfigToV1(ctx context.Context) error {
|
||||
basePrefix := iamConfigUsersPrefix
|
||||
ctx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
|
||||
defer cancel()
|
||||
r, err := ies.client.Get(ctx, basePrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
users := etcdKvsToSet(basePrefix, r.Kvs)
|
||||
for _, user := range users.ToSlice() {
|
||||
{
|
||||
// 1. check if there is a policy file in the old loc.
|
||||
oldPolicyPath := pathJoin(basePrefix, user, iamPolicyFile)
|
||||
var policyName string
|
||||
err := ies.loadIAMConfig(ctx, &policyName, oldPolicyPath)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errConfigNotFound:
|
||||
// No mapped policy or already migrated.
|
||||
default:
|
||||
// corrupt data/read error, etc
|
||||
}
|
||||
goto next
|
||||
}
|
||||
|
||||
// 2. copy policy to new loc.
|
||||
mp := newMappedPolicy(policyName)
|
||||
userType := regUser
|
||||
path := getMappedPolicyPath(user, userType, false)
|
||||
if err := ies.saveIAMConfig(ctx, mp, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. delete policy file in old loc.
|
||||
deleteKeyEtcd(ctx, ies.client, oldPolicyPath)
|
||||
}
|
||||
|
||||
next:
|
||||
// 4. check if user identity has old format.
|
||||
identityPath := pathJoin(basePrefix, user, iamIdentityFile)
|
||||
var cred auth.Credentials
|
||||
if err := ies.loadIAMConfig(ctx, &cred, identityPath); err != nil {
|
||||
switch err {
|
||||
case errConfigNotFound:
|
||||
// This case should not happen.
|
||||
default:
|
||||
// corrupt file or read error
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If the file is already in the new format,
|
||||
// then the parsed auth.Credentials will have
|
||||
// the zero value for the struct.
|
||||
var zeroCred auth.Credentials
|
||||
if cred.Equal(zeroCred) {
|
||||
// nothing to do
|
||||
continue
|
||||
}
|
||||
|
||||
// Found a id file in old format. Copy value
|
||||
// into new format and save it.
|
||||
cred.AccessKey = user
|
||||
u := newUserIdentity(cred)
|
||||
if err := ies.saveIAMConfig(ctx, u, identityPath); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to delete as identity file location
|
||||
// has not changed.
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) migrateToV1(ctx context.Context) error {
|
||||
var iamFmt iamFormat
|
||||
path := getIAMFormatFilePath()
|
||||
if err := ies.loadIAMConfig(ctx, &iamFmt, path); err != nil {
|
||||
switch err {
|
||||
case errConfigNotFound:
|
||||
// Need to migrate to V1.
|
||||
default:
|
||||
// if IAM format
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if iamFmt.Version >= iamFormatVersion1 {
|
||||
// Nothing to do.
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := ies.migrateUsersConfigToV1(ctx); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Save iam format to version 1.
|
||||
if err := ies.saveIAMConfig(ctx, newIAMFormatVersion1(), path); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Should be called under config migration lock
|
||||
func (ies *IAMEtcdStore) migrateBackendFormat(ctx context.Context) error {
|
||||
ies.Lock()
|
||||
defer ies.Unlock()
|
||||
return ies.migrateToV1(ctx)
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) loadPolicyDoc(ctx context.Context, policy string, m map[string]PolicyDoc) error {
|
||||
data, err := ies.loadIAMConfigBytes(ctx, getPolicyDocPath(policy))
|
||||
if err != nil {
|
||||
|
@ -26,7 +26,6 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@ -74,135 +73,6 @@ func (iamOS *IAMObjectStore) getUsersSysType() UsersSysType {
|
||||
return iamOS.usersSysType
|
||||
}
|
||||
|
||||
// Migrate users directory in a single scan.
|
||||
//
|
||||
// 1. Migrate user policy from:
|
||||
//
|
||||
// `iamConfigUsersPrefix + "<username>/policy.json"`
|
||||
//
|
||||
// to:
|
||||
//
|
||||
// `iamConfigPolicyDBUsersPrefix + "<username>.json"`.
|
||||
//
|
||||
// 2. Add versioning to the policy json file in the new
|
||||
// location.
|
||||
//
|
||||
// 3. Migrate user identity json file to include version info.
|
||||
func (iamOS *IAMObjectStore) migrateUsersConfigToV1(ctx context.Context) error {
|
||||
basePrefix := iamConfigUsersPrefix
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix) {
|
||||
if item.Err != nil {
|
||||
return item.Err
|
||||
}
|
||||
|
||||
user := path.Dir(item.Item)
|
||||
{
|
||||
// 1. check if there is policy file in old location.
|
||||
oldPolicyPath := pathJoin(basePrefix, user, iamPolicyFile)
|
||||
var policyName string
|
||||
if err := iamOS.loadIAMConfig(ctx, &policyName, oldPolicyPath); err != nil {
|
||||
switch err {
|
||||
case errConfigNotFound:
|
||||
// This case means it is already
|
||||
// migrated or there is no policy on
|
||||
// user.
|
||||
default:
|
||||
// File may be corrupt or network error
|
||||
}
|
||||
|
||||
// Nothing to do on the policy file,
|
||||
// so move on to check the id file.
|
||||
goto next
|
||||
}
|
||||
|
||||
// 2. copy policy file to new location.
|
||||
mp := newMappedPolicy(policyName)
|
||||
userType := regUser
|
||||
if err := iamOS.saveMappedPolicy(ctx, user, userType, false, mp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. delete policy file from old
|
||||
// location. Ignore error.
|
||||
iamOS.deleteIAMConfig(ctx, oldPolicyPath)
|
||||
}
|
||||
next:
|
||||
// 4. check if user identity has old format.
|
||||
identityPath := pathJoin(basePrefix, user, iamIdentityFile)
|
||||
cred := auth.Credentials{
|
||||
AccessKey: user,
|
||||
}
|
||||
if err := iamOS.loadIAMConfig(ctx, &cred, identityPath); err != nil {
|
||||
switch err {
|
||||
case errConfigNotFound:
|
||||
// This should not happen.
|
||||
default:
|
||||
// File may be corrupt or network error
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If the file is already in the new format,
|
||||
// then the parsed auth.Credentials will have
|
||||
// the zero value for the struct.
|
||||
if !cred.IsValid() {
|
||||
// nothing to do
|
||||
continue
|
||||
}
|
||||
|
||||
u := newUserIdentity(cred)
|
||||
if err := iamOS.saveIAMConfig(ctx, u, identityPath); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to delete as identity file location
|
||||
// has not changed.
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) migrateToV1(ctx context.Context) error {
|
||||
var iamFmt iamFormat
|
||||
path := getIAMFormatFilePath()
|
||||
if err := iamOS.loadIAMConfig(ctx, &iamFmt, path); err != nil {
|
||||
switch err {
|
||||
case errConfigNotFound:
|
||||
// Need to migrate to V1.
|
||||
default:
|
||||
// if IAM format
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if iamFmt.Version >= iamFormatVersion1 {
|
||||
// Nothing to do.
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := iamOS.migrateUsersConfigToV1(ctx); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Save iam format to version 1.
|
||||
if err := iamOS.saveIAMConfig(ctx, newIAMFormatVersion1(), path); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Should be called under config migration lock
|
||||
func (iamOS *IAMObjectStore) migrateBackendFormat(ctx context.Context) error {
|
||||
iamOS.Lock()
|
||||
defer iamOS.Unlock()
|
||||
return iamOS.migrateToV1(ctx)
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) saveIAMConfig(ctx context.Context, item interface{}, objPath string, opts ...options) error {
|
||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
data, err := json.Marshal(item)
|
||||
|
@ -102,6 +102,33 @@ func getUserIdentityPath(user string, userType IAMUserType) string {
|
||||
return pathJoin(basePath, user, iamIdentityFile)
|
||||
}
|
||||
|
||||
func saveIAMFormat(ctx context.Context, store IAMStorageAPI) error {
|
||||
var iamFmt iamFormat
|
||||
path := getIAMFormatFilePath()
|
||||
if err := store.loadIAMConfig(ctx, &iamFmt, path); err != nil {
|
||||
switch err {
|
||||
case errConfigNotFound:
|
||||
// Need to migrate to V1.
|
||||
default:
|
||||
// if IAM format
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if iamFmt.Version >= iamFormatVersion1 {
|
||||
// Nothing to do.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save iam format to version 1.
|
||||
if err := store.saveIAMConfig(ctx, newIAMFormatVersion1(), path); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGroupInfoPath(group string) string {
|
||||
return pathJoin(iamConfigGroupsPrefix, group, iamGroupMembersFile)
|
||||
}
|
||||
@ -374,7 +401,6 @@ type IAMStorageAPI interface {
|
||||
unlock()
|
||||
rlock() *iamCache
|
||||
runlock()
|
||||
migrateBackendFormat(context.Context) error
|
||||
getUsersSysType() UsersSysType
|
||||
loadPolicyDoc(ctx context.Context, policy string, m map[string]PolicyDoc) error
|
||||
loadPolicyDocs(ctx context.Context, m map[string]PolicyDoc) error
|
||||
|
34
cmd/iam.go
34
cmd/iam.go
@ -161,11 +161,6 @@ func (sys *IAMSys) LoadServiceAccount(ctx context.Context, accessKey string) err
|
||||
return sys.store.UserNotificationHandler(ctx, accessKey, svcUser)
|
||||
}
|
||||
|
||||
// Perform IAM configuration migration.
|
||||
func (sys *IAMSys) doIAMConfigMigration(ctx context.Context) error {
|
||||
return sys.store.migrateBackendFormat(ctx)
|
||||
}
|
||||
|
||||
// initStore initializes IAM stores
|
||||
func (sys *IAMSys) initStore(objAPI ObjectLayer, etcdClient *etcd.Client) {
|
||||
if sys.ldapConfig.Enabled {
|
||||
@ -238,31 +233,15 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
// Indicate to our routine to exit cleanly upon return.
|
||||
defer cancel()
|
||||
|
||||
// allocate dynamic timeout once before the loop
|
||||
iamLockTimeout := newDynamicTimeout(5*time.Second, 3*time.Second)
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
// Migrate storage format if needed.
|
||||
for {
|
||||
// Hold the lock for migration only.
|
||||
txnLk := objAPI.NewNSLock(minioMetaBucket, minioConfigPrefix+"/iam.lock")
|
||||
|
||||
// let one of the server acquire the lock, if not let them timeout.
|
||||
// which shall be retried again by this loop.
|
||||
lkctx, err := txnLk.GetLock(retryCtx, iamLockTimeout)
|
||||
if err != nil {
|
||||
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. trying to acquire lock")
|
||||
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
|
||||
continue
|
||||
}
|
||||
|
||||
if etcdClient != nil {
|
||||
// **** WARNING ****
|
||||
// Migrating to encrypted backend on etcd should happen before initialization of
|
||||
// IAM sub-system, make sure that we do not move the above codeblock elsewhere.
|
||||
if err := migrateIAMConfigsEtcdToEncrypted(retryCtx, etcdClient); err != nil {
|
||||
txnLk.Unlock(lkctx.Cancel)
|
||||
if errors.Is(err, errEtcdUnreachable) {
|
||||
logger.Info("Connection to etcd timed out. Retrying..")
|
||||
continue
|
||||
@ -273,25 +252,16 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
|
||||
}
|
||||
}
|
||||
|
||||
// These messages only meant primarily for distributed setup, so only log during distributed setup.
|
||||
if globalIsDistErasure {
|
||||
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. lock acquired")
|
||||
}
|
||||
|
||||
// Migrate IAM configuration, if necessary.
|
||||
if err := sys.doIAMConfigMigration(retryCtx); err != nil {
|
||||
txnLk.Unlock(lkctx.Cancel)
|
||||
if err := saveIAMFormat(retryCtx, sys.store); err != nil {
|
||||
if configRetriableErrors(err) {
|
||||
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. possible cause (%v)", err)
|
||||
continue
|
||||
}
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to migrate IAM users and policies to new format: %w", err))
|
||||
logger.LogIf(ctx, errors.New("IAM sub-system is partially initialized, some users may not be available"))
|
||||
logger.LogIf(ctx, errors.New("IAM sub-system is partially initialized, unable to write the IAM format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Successfully migrated, proceed to load the users.
|
||||
txnLk.Unlock(lkctx.Cancel)
|
||||
break
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user