github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/iam-object-store.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"path"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  	"unicode/utf8"
    30  
    31  	jsoniter "github.com/json-iterator/go"
    32  	"github.com/minio/madmin-go/v3"
    33  	"github.com/minio/minio-go/v7/pkg/set"
    34  	"github.com/minio/minio/internal/config"
    35  	xioutil "github.com/minio/minio/internal/ioutil"
    36  	"github.com/minio/minio/internal/kms"
    37  	"github.com/minio/minio/internal/logger"
    38  	"github.com/puzpuzpuz/xsync/v3"
    39  )
    40  
    41  // IAMObjectStore implements IAMStorageAPI
    42  type IAMObjectStore struct {
    43  	// Protect access to storage within the current server.
    44  	sync.RWMutex
    45  
    46  	*iamCache
    47  
    48  	usersSysType UsersSysType
    49  
    50  	objAPI ObjectLayer
    51  }
    52  
    53  func newIAMObjectStore(objAPI ObjectLayer, usersSysType UsersSysType) *IAMObjectStore {
    54  	return &IAMObjectStore{
    55  		iamCache:     newIamCache(),
    56  		objAPI:       objAPI,
    57  		usersSysType: usersSysType,
    58  	}
    59  }
    60  
    61  func (iamOS *IAMObjectStore) rlock() *iamCache {
    62  	iamOS.RLock()
    63  	return iamOS.iamCache
    64  }
    65  
    66  func (iamOS *IAMObjectStore) runlock() {
    67  	iamOS.RUnlock()
    68  }
    69  
    70  func (iamOS *IAMObjectStore) lock() *iamCache {
    71  	iamOS.Lock()
    72  	return iamOS.iamCache
    73  }
    74  
    75  func (iamOS *IAMObjectStore) unlock() {
    76  	iamOS.Unlock()
    77  }
    78  
    79  func (iamOS *IAMObjectStore) getUsersSysType() UsersSysType {
    80  	return iamOS.usersSysType
    81  }
    82  
    83  func (iamOS *IAMObjectStore) saveIAMConfig(ctx context.Context, item interface{}, objPath string, opts ...options) error {
    84  	json := jsoniter.ConfigCompatibleWithStandardLibrary
    85  	data, err := json.Marshal(item)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	if GlobalKMS != nil {
    90  		data, err = config.EncryptBytes(GlobalKMS, data, kms.Context{
    91  			minioMetaBucket: path.Join(minioMetaBucket, objPath),
    92  		})
    93  		if err != nil {
    94  			return err
    95  		}
    96  	}
    97  	return saveConfig(ctx, iamOS.objAPI, objPath, data)
    98  }
    99  
   100  func decryptData(data []byte, objPath string) ([]byte, error) {
   101  	if utf8.Valid(data) {
   102  		return data, nil
   103  	}
   104  
   105  	pdata, err := madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
   106  	if err == nil {
   107  		return pdata, nil
   108  	}
   109  	if GlobalKMS != nil {
   110  		pdata, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
   111  			minioMetaBucket: path.Join(minioMetaBucket, objPath),
   112  		})
   113  		if err == nil {
   114  			return pdata, nil
   115  		}
   116  		pdata, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
   117  			minioMetaBucket: objPath,
   118  		})
   119  		if err == nil {
   120  			return pdata, nil
   121  		}
   122  	}
   123  	return nil, err
   124  }
   125  
   126  func (iamOS *IAMObjectStore) loadIAMConfigBytesWithMetadata(ctx context.Context, objPath string) ([]byte, ObjectInfo, error) {
   127  	data, meta, err := readConfigWithMetadata(ctx, iamOS.objAPI, objPath, ObjectOptions{})
   128  	if err != nil {
   129  		return nil, meta, err
   130  	}
   131  	data, err = decryptData(data, objPath)
   132  	if err != nil {
   133  		return nil, meta, err
   134  	}
   135  	return data, meta, nil
   136  }
   137  
   138  func (iamOS *IAMObjectStore) loadIAMConfig(ctx context.Context, item interface{}, objPath string) error {
   139  	data, _, err := iamOS.loadIAMConfigBytesWithMetadata(ctx, objPath)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	json := jsoniter.ConfigCompatibleWithStandardLibrary
   144  	return json.Unmarshal(data, item)
   145  }
   146  
   147  func (iamOS *IAMObjectStore) deleteIAMConfig(ctx context.Context, path string) error {
   148  	return deleteConfig(ctx, iamOS.objAPI, path)
   149  }
   150  
   151  func (iamOS *IAMObjectStore) loadPolicyDocWithRetry(ctx context.Context, policy string, m map[string]PolicyDoc, retries int) error {
   152  	for {
   153  	retry:
   154  		data, objInfo, err := iamOS.loadIAMConfigBytesWithMetadata(ctx, getPolicyDocPath(policy))
   155  		if err != nil {
   156  			if err == errConfigNotFound {
   157  				return errNoSuchPolicy
   158  			}
   159  			retries--
   160  			if retries <= 0 {
   161  				return err
   162  			}
   163  			time.Sleep(500 * time.Millisecond)
   164  			goto retry
   165  		}
   166  
   167  		var p PolicyDoc
   168  		err = p.parseJSON(data)
   169  		if err != nil {
   170  			return err
   171  		}
   172  
   173  		if p.Version == 0 {
   174  			// This means that policy was in the old version (without any
   175  			// timestamp info). We fetch the mod time of the file and save
   176  			// that as create and update date.
   177  			p.CreateDate = objInfo.ModTime
   178  			p.UpdateDate = objInfo.ModTime
   179  		}
   180  
   181  		m[policy] = p
   182  		return nil
   183  	}
   184  }
   185  
   186  func (iamOS *IAMObjectStore) loadPolicyDoc(ctx context.Context, policy string, m map[string]PolicyDoc) error {
   187  	data, objInfo, err := iamOS.loadIAMConfigBytesWithMetadata(ctx, getPolicyDocPath(policy))
   188  	if err != nil {
   189  		if err == errConfigNotFound {
   190  			return errNoSuchPolicy
   191  		}
   192  		return err
   193  	}
   194  
   195  	var p PolicyDoc
   196  	err = p.parseJSON(data)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	if p.Version == 0 {
   202  		// This means that policy was in the old version (without any
   203  		// timestamp info). We fetch the mod time of the file and save
   204  		// that as create and update date.
   205  		p.CreateDate = objInfo.ModTime
   206  		p.UpdateDate = objInfo.ModTime
   207  	}
   208  
   209  	m[policy] = p
   210  	return nil
   211  }
   212  
   213  func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]PolicyDoc) error {
   214  	ctx, cancel := context.WithCancel(ctx)
   215  	defer cancel()
   216  	for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPoliciesPrefix) {
   217  		if item.Err != nil {
   218  			return item.Err
   219  		}
   220  
   221  		policyName := path.Dir(item.Item)
   222  		if err := iamOS.loadPolicyDoc(ctx, policyName, m); err != nil && !errors.Is(err, errNoSuchPolicy) {
   223  			return err
   224  		}
   225  	}
   226  	return nil
   227  }
   228  
   229  func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error {
   230  	var u UserIdentity
   231  	err := iamOS.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
   232  	if err != nil {
   233  		if err == errConfigNotFound {
   234  			return errNoSuchUser
   235  		}
   236  		return err
   237  	}
   238  
   239  	if u.Credentials.IsExpired() {
   240  		// Delete expired identity - ignoring errors here.
   241  		iamOS.deleteIAMConfig(ctx, getUserIdentityPath(user, userType))
   242  		iamOS.deleteIAMConfig(ctx, getMappedPolicyPath(user, userType, false))
   243  		return nil
   244  	}
   245  
   246  	if u.Credentials.AccessKey == "" {
   247  		u.Credentials.AccessKey = user
   248  	}
   249  
   250  	if u.Credentials.SessionToken != "" {
   251  		jwtClaims, err := extractJWTClaims(u)
   252  		if err != nil {
   253  			if u.Credentials.IsTemp() {
   254  				// We should delete such that the client can re-request
   255  				// for the expiring credentials.
   256  				iamOS.deleteIAMConfig(ctx, getUserIdentityPath(user, userType))
   257  				iamOS.deleteIAMConfig(ctx, getMappedPolicyPath(user, userType, false))
   258  				return nil
   259  			}
   260  			return err
   261  
   262  		}
   263  		u.Credentials.Claims = jwtClaims.Map()
   264  	}
   265  
   266  	if u.Credentials.Description == "" {
   267  		u.Credentials.Description = u.Credentials.Comment
   268  	}
   269  
   270  	m[user] = u
   271  	return nil
   272  }
   273  
   274  func (iamOS *IAMObjectStore) loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error {
   275  	var basePrefix string
   276  	switch userType {
   277  	case svcUser:
   278  		basePrefix = iamConfigServiceAccountsPrefix
   279  	case stsUser:
   280  		basePrefix = iamConfigSTSPrefix
   281  	default:
   282  		basePrefix = iamConfigUsersPrefix
   283  	}
   284  
   285  	ctx, cancel := context.WithCancel(ctx)
   286  	defer cancel()
   287  	for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix) {
   288  		if item.Err != nil {
   289  			return item.Err
   290  		}
   291  
   292  		userName := path.Dir(item.Item)
   293  		if err := iamOS.loadUser(ctx, userName, userType, m); err != nil && err != errNoSuchUser {
   294  			return err
   295  		}
   296  	}
   297  	return nil
   298  }
   299  
   300  func (iamOS *IAMObjectStore) loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error {
   301  	var g GroupInfo
   302  	err := iamOS.loadIAMConfig(ctx, &g, getGroupInfoPath(group))
   303  	if err != nil {
   304  		if err == errConfigNotFound {
   305  			return errNoSuchGroup
   306  		}
   307  		return err
   308  	}
   309  	m[group] = g
   310  	return nil
   311  }
   312  
   313  func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupInfo) error {
   314  	ctx, cancel := context.WithCancel(ctx)
   315  	defer cancel()
   316  	for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigGroupsPrefix) {
   317  		if item.Err != nil {
   318  			return item.Err
   319  		}
   320  
   321  		group := path.Dir(item.Item)
   322  		if err := iamOS.loadGroup(ctx, group, m); err != nil && err != errNoSuchGroup {
   323  			return err
   324  		}
   325  	}
   326  	return nil
   327  }
   328  
   329  func (iamOS *IAMObjectStore) loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy], retries int) error {
   330  	for {
   331  	retry:
   332  		var p MappedPolicy
   333  		err := iamOS.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup))
   334  		if err != nil {
   335  			if err == errConfigNotFound {
   336  				return errNoSuchPolicy
   337  			}
   338  			retries--
   339  			if retries <= 0 {
   340  				return err
   341  			}
   342  			time.Sleep(500 * time.Millisecond)
   343  			goto retry
   344  		}
   345  
   346  		m.Store(name, p)
   347  		return nil
   348  	}
   349  }
   350  
   351  func (iamOS *IAMObjectStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
   352  	var p MappedPolicy
   353  	err := iamOS.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup))
   354  	if err != nil {
   355  		if err == errConfigNotFound {
   356  			return errNoSuchPolicy
   357  		}
   358  		return err
   359  	}
   360  
   361  	m.Store(name, p)
   362  	return nil
   363  }
   364  
   365  func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
   366  	var basePath string
   367  	if isGroup {
   368  		basePath = iamConfigPolicyDBGroupsPrefix
   369  	} else {
   370  		switch userType {
   371  		case svcUser:
   372  			basePath = iamConfigPolicyDBServiceAccountsPrefix
   373  		case stsUser:
   374  			basePath = iamConfigPolicyDBSTSUsersPrefix
   375  		default:
   376  			basePath = iamConfigPolicyDBUsersPrefix
   377  		}
   378  	}
   379  	ctx, cancel := context.WithCancel(ctx)
   380  	defer cancel()
   381  	for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePath) {
   382  		if item.Err != nil {
   383  			return item.Err
   384  		}
   385  
   386  		policyFile := item.Item
   387  		userOrGroupName := strings.TrimSuffix(policyFile, ".json")
   388  		if err := iamOS.loadMappedPolicy(ctx, userOrGroupName, userType, isGroup, m); err != nil && !errors.Is(err, errNoSuchPolicy) {
   389  			return err
   390  		}
   391  	}
   392  	return nil
   393  }
   394  
   395  var (
   396  	usersListKey                   = "users/"
   397  	svcAccListKey                  = "service-accounts/"
   398  	groupsListKey                  = "groups/"
   399  	policiesListKey                = "policies/"
   400  	stsListKey                     = "sts/"
   401  	policyDBUsersListKey           = "policydb/users/"
   402  	policyDBSTSUsersListKey        = "policydb/sts-users/"
   403  	policyDBServiceAccountsListKey = "policydb/service-accounts/"
   404  	policyDBGroupsListKey          = "policydb/groups/"
   405  
   406  	// List of directories from which to read iam data into memory.
   407  	allListKeys = []string{
   408  		usersListKey,
   409  		svcAccListKey,
   410  		groupsListKey,
   411  		policiesListKey,
   412  		stsListKey,
   413  		policyDBUsersListKey,
   414  		policyDBSTSUsersListKey,
   415  		policyDBServiceAccountsListKey,
   416  		policyDBGroupsListKey,
   417  	}
   418  
   419  	// List of directories to skip: we do not read STS directories for better
   420  	// performance. STS credentials would be stored in memory when they are
   421  	// first used.
   422  	iamLoadSkipListKeySet = set.CreateStringSet(
   423  		stsListKey,
   424  		policyDBSTSUsersListKey,
   425  	)
   426  )
   427  
   428  func (iamOS *IAMObjectStore) listAllIAMConfigItems(ctx context.Context) (map[string][]string, error) {
   429  	res := make(map[string][]string)
   430  	ctx, cancel := context.WithCancel(ctx)
   431  	defer cancel()
   432  	for _, listKey := range allListKeys {
   433  		if iamLoadSkipListKeySet.Contains(listKey) {
   434  			continue
   435  		}
   436  		for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator+listKey) {
   437  			if item.Err != nil {
   438  				return nil, item.Err
   439  			}
   440  			res[listKey] = append(res[listKey], item.Item)
   441  		}
   442  	}
   443  	return res, nil
   444  }
   445  
   446  // PurgeExpiredSTS - purge expired STS credentials from object store.
   447  func (iamOS *IAMObjectStore) PurgeExpiredSTS(ctx context.Context) error {
   448  	if iamOS.objAPI == nil {
   449  		return errServerNotInitialized
   450  	}
   451  
   452  	bootstrapTraceMsg("purging expired STS credentials")
   453  	// Scan STS users on disk and purge expired ones. We do not need to hold a
   454  	// lock with store.lock() here.
   455  	for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator+stsListKey) {
   456  		if item.Err != nil {
   457  			return item.Err
   458  		}
   459  		userName := path.Dir(item.Item)
   460  		// loadUser() will delete expired user during the load - we do not need
   461  		// to keep the loaded user around in memory, so we reinitialize the map
   462  		// each time.
   463  		m := map[string]UserIdentity{}
   464  		if err := iamOS.loadUser(ctx, userName, stsUser, m); err != nil && err != errNoSuchUser {
   465  			logger.LogIf(GlobalContext, fmt.Errorf("unable to load user during STS purge: %w (%s)", err, item.Item))
   466  		}
   467  
   468  	}
   469  	return nil
   470  }
   471  
   472  // Assumes cache is locked by caller.
   473  func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iamCache) error {
   474  	if iamOS.objAPI == nil {
   475  		return errServerNotInitialized
   476  	}
   477  
   478  	bootstrapTraceMsg("loading all IAM items")
   479  
   480  	listedConfigItems, err := iamOS.listAllIAMConfigItems(ctx)
   481  	if err != nil {
   482  		return fmt.Errorf("unable to list IAM data: %w", err)
   483  	}
   484  
   485  	// Loads things in the same order as `LoadIAMCache()`
   486  
   487  	bootstrapTraceMsg("loading policy documents")
   488  
   489  	policiesList := listedConfigItems[policiesListKey]
   490  	for _, item := range policiesList {
   491  		policyName := path.Dir(item)
   492  		if err := iamOS.loadPolicyDoc(ctx, policyName, cache.iamPolicyDocsMap); err != nil && !errors.Is(err, errNoSuchPolicy) {
   493  			return fmt.Errorf("unable to load the policy doc `%s`: %w", policyName, err)
   494  		}
   495  	}
   496  	setDefaultCannedPolicies(cache.iamPolicyDocsMap)
   497  
   498  	if iamOS.usersSysType == MinIOUsersSysType {
   499  		bootstrapTraceMsg("loading regular IAM users")
   500  		regUsersList := listedConfigItems[usersListKey]
   501  		for _, item := range regUsersList {
   502  			userName := path.Dir(item)
   503  			if err := iamOS.loadUser(ctx, userName, regUser, cache.iamUsersMap); err != nil && err != errNoSuchUser {
   504  				return fmt.Errorf("unable to load the user `%s`: %w", userName, err)
   505  			}
   506  		}
   507  
   508  		bootstrapTraceMsg("loading regular IAM groups")
   509  		groupsList := listedConfigItems[groupsListKey]
   510  		for _, item := range groupsList {
   511  			group := path.Dir(item)
   512  			if err := iamOS.loadGroup(ctx, group, cache.iamGroupsMap); err != nil && err != errNoSuchGroup {
   513  				return fmt.Errorf("unable to load the group `%s`: %w", group, err)
   514  			}
   515  		}
   516  	}
   517  
   518  	bootstrapTraceMsg("loading user policy mapping")
   519  	userPolicyMappingsList := listedConfigItems[policyDBUsersListKey]
   520  	for _, item := range userPolicyMappingsList {
   521  		userName := strings.TrimSuffix(item, ".json")
   522  		if err := iamOS.loadMappedPolicy(ctx, userName, regUser, false, cache.iamUserPolicyMap); err != nil && !errors.Is(err, errNoSuchPolicy) {
   523  			return fmt.Errorf("unable to load the policy mapping for the user `%s`: %w", userName, err)
   524  		}
   525  	}
   526  
   527  	bootstrapTraceMsg("loading group policy mapping")
   528  	groupPolicyMappingsList := listedConfigItems[policyDBGroupsListKey]
   529  	for _, item := range groupPolicyMappingsList {
   530  		groupName := strings.TrimSuffix(item, ".json")
   531  		if err := iamOS.loadMappedPolicy(ctx, groupName, regUser, true, cache.iamGroupPolicyMap); err != nil && !errors.Is(err, errNoSuchPolicy) {
   532  			return fmt.Errorf("unable to load the policy mapping for the group `%s`: %w", groupName, err)
   533  		}
   534  	}
   535  
   536  	bootstrapTraceMsg("loading service accounts")
   537  	svcAccList := listedConfigItems[svcAccListKey]
   538  	svcUsersMap := make(map[string]UserIdentity, len(svcAccList))
   539  	for _, item := range svcAccList {
   540  		userName := path.Dir(item)
   541  		if err := iamOS.loadUser(ctx, userName, svcUser, svcUsersMap); err != nil && err != errNoSuchUser {
   542  			return fmt.Errorf("unable to load the service account `%s`: %w", userName, err)
   543  		}
   544  	}
   545  	for _, svcAcc := range svcUsersMap {
   546  		svcParent := svcAcc.Credentials.ParentUser
   547  		if _, ok := cache.iamUsersMap[svcParent]; !ok {
   548  			// If a service account's parent user is not in iamUsersMap, the
   549  			// parent is an STS account. Such accounts may have a policy mapped
   550  			// on the parent user, so we load them. This is not needed for the
   551  			// initial server startup, however, it is needed for the case where
   552  			// the STS account's policy mapping (for example in LDAP mode) may
   553  			// be changed and the user's policy mapping in memory is stale
   554  			// (because the policy change notification was missed by the current
   555  			// server).
   556  			//
   557  			// The "policy not found" error is ignored because the STS account may
   558  			// not have a policy mapped via its parent (for e.g. in
   559  			// OIDC/AssumeRoleWithCustomToken/AssumeRoleWithCertificate).
   560  			err := iamOS.loadMappedPolicy(ctx, svcParent, stsUser, false, cache.iamSTSPolicyMap)
   561  			if err != nil && !errors.Is(err, errNoSuchPolicy) {
   562  				return fmt.Errorf("unable to load the policy mapping for the STS user `%s`: %w", svcParent, err)
   563  			}
   564  		}
   565  	}
   566  	// Copy svcUsersMap to cache.iamUsersMap
   567  	for k, v := range svcUsersMap {
   568  		cache.iamUsersMap[k] = v
   569  	}
   570  
   571  	cache.buildUserGroupMemberships()
   572  	return nil
   573  }
   574  
   575  func (iamOS *IAMObjectStore) savePolicyDoc(ctx context.Context, policyName string, p PolicyDoc) error {
   576  	return iamOS.saveIAMConfig(ctx, &p, getPolicyDocPath(policyName))
   577  }
   578  
   579  func (iamOS *IAMObjectStore) saveMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, mp MappedPolicy, opts ...options) error {
   580  	return iamOS.saveIAMConfig(ctx, mp, getMappedPolicyPath(name, userType, isGroup), opts...)
   581  }
   582  
   583  func (iamOS *IAMObjectStore) saveUserIdentity(ctx context.Context, name string, userType IAMUserType, u UserIdentity, opts ...options) error {
   584  	return iamOS.saveIAMConfig(ctx, u, getUserIdentityPath(name, userType), opts...)
   585  }
   586  
   587  func (iamOS *IAMObjectStore) saveGroupInfo(ctx context.Context, name string, gi GroupInfo) error {
   588  	return iamOS.saveIAMConfig(ctx, gi, getGroupInfoPath(name))
   589  }
   590  
   591  func (iamOS *IAMObjectStore) deletePolicyDoc(ctx context.Context, name string) error {
   592  	err := iamOS.deleteIAMConfig(ctx, getPolicyDocPath(name))
   593  	if err == errConfigNotFound {
   594  		err = errNoSuchPolicy
   595  	}
   596  	return err
   597  }
   598  
   599  func (iamOS *IAMObjectStore) deleteMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool) error {
   600  	err := iamOS.deleteIAMConfig(ctx, getMappedPolicyPath(name, userType, isGroup))
   601  	if err == errConfigNotFound {
   602  		err = errNoSuchPolicy
   603  	}
   604  	return err
   605  }
   606  
   607  func (iamOS *IAMObjectStore) deleteUserIdentity(ctx context.Context, name string, userType IAMUserType) error {
   608  	err := iamOS.deleteIAMConfig(ctx, getUserIdentityPath(name, userType))
   609  	if err == errConfigNotFound {
   610  		err = errNoSuchUser
   611  	}
   612  	return err
   613  }
   614  
   615  func (iamOS *IAMObjectStore) deleteGroupInfo(ctx context.Context, name string) error {
   616  	err := iamOS.deleteIAMConfig(ctx, getGroupInfoPath(name))
   617  	if err == errConfigNotFound {
   618  		err = errNoSuchGroup
   619  	}
   620  	return err
   621  }
   622  
   623  // helper type for listIAMConfigItems
   624  type itemOrErr struct {
   625  	Item string
   626  	Err  error
   627  }
   628  
   629  // Lists files or dirs in the minioMetaBucket at the given path
   630  // prefix. If dirs is true, only directories are listed, otherwise
   631  // only objects are listed. All returned items have the pathPrefix
   632  // removed from their names.
   633  func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix string) <-chan itemOrErr {
   634  	ch := make(chan itemOrErr)
   635  
   636  	go func() {
   637  		defer xioutil.SafeClose(ch)
   638  
   639  		// Allocate new results channel to receive ObjectInfo.
   640  		objInfoCh := make(chan ObjectInfo)
   641  
   642  		if err := objAPI.Walk(ctx, minioMetaBucket, pathPrefix, objInfoCh, WalkOptions{}); err != nil {
   643  			select {
   644  			case ch <- itemOrErr{Err: err}:
   645  			case <-ctx.Done():
   646  			}
   647  			return
   648  		}
   649  
   650  		for obj := range objInfoCh {
   651  			item := strings.TrimPrefix(obj.Name, pathPrefix)
   652  			item = strings.TrimSuffix(item, SlashSeparator)
   653  			select {
   654  			case ch <- itemOrErr{Item: item}:
   655  			case <-ctx.Done():
   656  				return
   657  			}
   658  		}
   659  	}()
   660  
   661  	return ch
   662  }