mirror of
https://github.com/minio/minio.git
synced 2024-12-25 14:45:54 -05:00
ecd54b4cba
This reverts commit 091a7ae359
.
- Ensure all actions accessing storage lock properly.
- Behavior change: policies can be deleted only when they
are not associated with any active credentials.
Also adds fix for accidental canned policy removal that was present in the
reverted version of the change.
476 lines
12 KiB
Go
476 lines
12 KiB
Go
// 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/>.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"path"
|
|
"strings"
|
|
"sync"
|
|
"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"
|
|
iampolicy "github.com/minio/pkg/iam/policy"
|
|
)
|
|
|
|
// IAMObjectStore implements IAMStorageAPI
|
|
type IAMObjectStore struct {
|
|
// Protect access to storage within the current server.
|
|
sync.RWMutex
|
|
|
|
*iamCache
|
|
|
|
usersSysType UsersSysType
|
|
|
|
objAPI ObjectLayer
|
|
}
|
|
|
|
func newIAMObjectStore(objAPI ObjectLayer, usersSysType UsersSysType) *IAMObjectStore {
|
|
return &IAMObjectStore{
|
|
iamCache: newIamCache(),
|
|
objAPI: objAPI,
|
|
usersSysType: usersSysType,
|
|
}
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) rlock() *iamCache {
|
|
iamOS.RLock()
|
|
return iamOS.iamCache
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) runlock() {
|
|
iamOS.RUnlock()
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) lock() *iamCache {
|
|
iamOS.Lock()
|
|
return iamOS.iamCache
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) unlock() {
|
|
iamOS.Unlock()
|
|
}
|
|
|
|
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
|
|
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)
|
|
var 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 {
|
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
data, err := json.Marshal(item)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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, objPath, data)
|
|
}
|
|
|
|
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 !utf8.Valid(data) && GlobalKMS != nil {
|
|
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
|
|
minioMetaBucket: path.Join(minioMetaBucket, objPath),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
return json.Unmarshal(data, item)
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) deleteIAMConfig(ctx context.Context, path string) error {
|
|
return deleteConfig(ctx, iamOS.objAPI, path)
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) loadPolicyDoc(ctx context.Context, policy string, m map[string]iampolicy.Policy) error {
|
|
var p iampolicy.Policy
|
|
err := iamOS.loadIAMConfig(ctx, &p, getPolicyDocPath(policy))
|
|
if err != nil {
|
|
if err == errConfigNotFound {
|
|
return errNoSuchPolicy
|
|
}
|
|
return err
|
|
}
|
|
m[policy] = p
|
|
return nil
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]iampolicy.Policy) error {
|
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPoliciesPrefix) {
|
|
if item.Err != nil {
|
|
return item.Err
|
|
}
|
|
|
|
policyName := path.Dir(item.Item)
|
|
if err := iamOS.loadPolicyDoc(ctx, policyName, m); err != nil && err != errNoSuchPolicy {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]auth.Credentials) error {
|
|
var u UserIdentity
|
|
err := iamOS.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
|
|
if err != nil {
|
|
if err == errConfigNotFound {
|
|
return errNoSuchUser
|
|
}
|
|
return err
|
|
}
|
|
|
|
if u.Credentials.IsExpired() {
|
|
// Delete expired identity - ignoring errors here.
|
|
iamOS.deleteIAMConfig(ctx, getUserIdentityPath(user, userType))
|
|
iamOS.deleteIAMConfig(ctx, getMappedPolicyPath(user, userType, false))
|
|
return nil
|
|
}
|
|
|
|
if u.Credentials.AccessKey == "" {
|
|
u.Credentials.AccessKey = user
|
|
}
|
|
|
|
m[user] = u.Credentials
|
|
return nil
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) loadUsers(ctx context.Context, userType IAMUserType, m map[string]auth.Credentials) error {
|
|
var basePrefix string
|
|
switch userType {
|
|
case svcUser:
|
|
basePrefix = iamConfigServiceAccountsPrefix
|
|
case stsUser:
|
|
basePrefix = iamConfigSTSPrefix
|
|
default:
|
|
basePrefix = iamConfigUsersPrefix
|
|
}
|
|
|
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix) {
|
|
if item.Err != nil {
|
|
return item.Err
|
|
}
|
|
|
|
userName := path.Dir(item.Item)
|
|
if err := iamOS.loadUser(ctx, userName, userType, m); err != nil && err != errNoSuchUser {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error {
|
|
var g GroupInfo
|
|
err := iamOS.loadIAMConfig(ctx, &g, getGroupInfoPath(group))
|
|
if err != nil {
|
|
if err == errConfigNotFound {
|
|
return errNoSuchGroup
|
|
}
|
|
return err
|
|
}
|
|
m[group] = g
|
|
return nil
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupInfo) error {
|
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigGroupsPrefix) {
|
|
if item.Err != nil {
|
|
return item.Err
|
|
}
|
|
|
|
group := path.Dir(item.Item)
|
|
if err := iamOS.loadGroup(ctx, group, m); err != nil && err != errNoSuchGroup {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool,
|
|
m map[string]MappedPolicy) error {
|
|
|
|
var p MappedPolicy
|
|
err := iamOS.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup))
|
|
if err != nil {
|
|
if err == errConfigNotFound {
|
|
return errNoSuchPolicy
|
|
}
|
|
return err
|
|
}
|
|
m[name] = p
|
|
return nil
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
|
|
var basePath string
|
|
if isGroup {
|
|
basePath = iamConfigPolicyDBGroupsPrefix
|
|
} else {
|
|
switch userType {
|
|
case svcUser:
|
|
basePath = iamConfigPolicyDBServiceAccountsPrefix
|
|
case stsUser:
|
|
basePath = iamConfigPolicyDBSTSUsersPrefix
|
|
default:
|
|
basePath = iamConfigPolicyDBUsersPrefix
|
|
}
|
|
}
|
|
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePath) {
|
|
if item.Err != nil {
|
|
return item.Err
|
|
}
|
|
|
|
policyFile := item.Item
|
|
userOrGroupName := strings.TrimSuffix(policyFile, ".json")
|
|
if err := iamOS.loadMappedPolicy(ctx, userOrGroupName, userType, isGroup, m); err != nil && err != errNoSuchPolicy {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) savePolicyDoc(ctx context.Context, policyName string, p iampolicy.Policy) error {
|
|
return iamOS.saveIAMConfig(ctx, &p, getPolicyDocPath(policyName))
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) saveMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, mp MappedPolicy, opts ...options) error {
|
|
return iamOS.saveIAMConfig(ctx, mp, getMappedPolicyPath(name, userType, isGroup), opts...)
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) saveUserIdentity(ctx context.Context, name string, userType IAMUserType, u UserIdentity, opts ...options) error {
|
|
return iamOS.saveIAMConfig(ctx, u, getUserIdentityPath(name, userType), opts...)
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) saveGroupInfo(ctx context.Context, name string, gi GroupInfo) error {
|
|
return iamOS.saveIAMConfig(ctx, gi, getGroupInfoPath(name))
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) deletePolicyDoc(ctx context.Context, name string) error {
|
|
err := iamOS.deleteIAMConfig(ctx, getPolicyDocPath(name))
|
|
if err == errConfigNotFound {
|
|
err = errNoSuchPolicy
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) deleteMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool) error {
|
|
err := iamOS.deleteIAMConfig(ctx, getMappedPolicyPath(name, userType, isGroup))
|
|
if err == errConfigNotFound {
|
|
err = errNoSuchPolicy
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) deleteUserIdentity(ctx context.Context, name string, userType IAMUserType) error {
|
|
err := iamOS.deleteIAMConfig(ctx, getUserIdentityPath(name, userType))
|
|
if err == errConfigNotFound {
|
|
err = errNoSuchUser
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (iamOS *IAMObjectStore) deleteGroupInfo(ctx context.Context, name string) error {
|
|
err := iamOS.deleteIAMConfig(ctx, getGroupInfoPath(name))
|
|
if err == errConfigNotFound {
|
|
err = errNoSuchGroup
|
|
}
|
|
return err
|
|
}
|
|
|
|
// helper type for listIAMConfigItems
|
|
type itemOrErr struct {
|
|
Item string
|
|
Err error
|
|
}
|
|
|
|
// Lists files or dirs in the minioMetaBucket at the given path
|
|
// prefix. If dirs is true, only directories are listed, otherwise
|
|
// only objects are listed. All returned items have the pathPrefix
|
|
// removed from their names.
|
|
func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix string) <-chan itemOrErr {
|
|
ch := make(chan itemOrErr)
|
|
|
|
go func() {
|
|
defer close(ch)
|
|
|
|
// Allocate new results channel to receive ObjectInfo.
|
|
objInfoCh := make(chan ObjectInfo)
|
|
|
|
if err := objAPI.Walk(ctx, minioMetaBucket, pathPrefix, objInfoCh, ObjectOptions{}); err != nil {
|
|
select {
|
|
case ch <- itemOrErr{Err: err}:
|
|
case <-ctx.Done():
|
|
}
|
|
return
|
|
}
|
|
|
|
for obj := range objInfoCh {
|
|
item := strings.TrimPrefix(obj.Name, pathPrefix)
|
|
item = strings.TrimSuffix(item, SlashSeparator)
|
|
select {
|
|
case ch <- itemOrErr{Item: item}:
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return ch
|
|
}
|