VYPR
Critical severityNVD Advisory· Published Dec 16, 2024· Updated Apr 15, 2026

CVE-2024-55949

CVE-2024-55949

Description

MinIO is a high-performance, S3 compatible object store, open sourced under GNU AGPLv3 license. Minio is subject to a privilege escalation in IAM import API, all users are impacted since MinIO commit 580d9db85e04f1b63cc2909af50f0ed08afa965f. This issue has been addressed in commit f246c9053f9603e610d98439799bdd2a6b293427 which is included in RELEASE.2024-12-13T22-19-12Z. There are no workarounds possible, all users are advised to upgrade immediately.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/minio/minioGo
>= 0.0.0-20220623162515-580d9db85e04, < 0.0.0-20241213221912-68b004a48f410.0.0-20241213221912-68b004a48f41

Patches

2
f246c9053f96

fix: Privilege escalation in IAM import API (#20756)

https://github.com/minio/minioAditya ManthramurthyDec 12, 2024via ghsa
1 file changed · +4 44
  • cmd/admin-handlers-users.go+4 44 modified
    @@ -2032,6 +2032,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
     	// Get current object layer instance.
     	objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportIAMAction)
     	if objectAPI == nil {
    +		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
     		return
     	}
     	// Initialize a zip writer which will provide a zipped content
    @@ -2254,17 +2255,13 @@ func (a adminAPIHandlers) ImportIAMV2(w http.ResponseWriter, r *http.Request) {
     func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiVer string) {
     	ctx := r.Context()
     
    -	// Get current object layer instance.
    -	objectAPI := newObjectLayerFn()
    +	// Validate signature, permissions and get current object layer instance.
    +	objectAPI, _ := validateAdminReq(ctx, w, r, policy.ImportIAMAction)
     	if objectAPI == nil || globalNotificationSys == nil {
     		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
     		return
     	}
    -	cred, owner, s3Err := validateAdminSignature(ctx, r, "")
    -	if s3Err != ErrNone {
    -		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
    -		return
    -	}
    +
     	data, err := io.ReadAll(r.Body)
     	if err != nil {
     		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
    @@ -2354,38 +2351,12 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
     					return
     				}
     
    -				if (cred.IsTemp() || cred.IsServiceAccount()) && cred.ParentUser == accessKey {
    -					// Incoming access key matches parent user then we should
    -					// reject password change requests.
    -					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAddUserInvalidArgument, err, allUsersFile, accessKey), r.URL)
    -					return
    -				}
    -
     				// Check if accessKey has beginning and end space characters, this only applies to new users.
     				if !exists && hasSpaceBE(accessKey) {
     					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, err, allUsersFile, accessKey), r.URL)
     					return
     				}
     
    -				checkDenyOnly := false
    -				if accessKey == cred.AccessKey {
    -					// Check that there is no explicit deny - otherwise it's allowed
    -					// to change one's own password.
    -					checkDenyOnly = true
    -				}
    -
    -				if !globalIAMSys.IsAllowed(policy.Args{
    -					AccountName:     cred.AccessKey,
    -					Groups:          cred.Groups,
    -					Action:          policy.CreateUserAdminAction,
    -					ConditionValues: getConditionValues(r, "", cred),
    -					IsOwner:         owner,
    -					Claims:          cred.Claims,
    -					DenyOnly:        checkDenyOnly,
    -				}) {
    -					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allUsersFile, accessKey), r.URL)
    -					return
    -				}
     				if _, err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil {
     					failed.Users = append(failed.Users, madmin.IAMErrEntity{Name: accessKey, Error: err})
     				} else {
    @@ -2485,17 +2456,6 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
     					writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
     					return
     				}
    -				if !globalIAMSys.IsAllowed(policy.Args{
    -					AccountName:     cred.AccessKey,
    -					Groups:          cred.Groups,
    -					Action:          policy.CreateServiceAccountAdminAction,
    -					ConditionValues: getConditionValues(r, "", cred),
    -					IsOwner:         owner,
    -					Claims:          cred.Claims,
    -				}) {
    -					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allSvcAcctsFile, user), r.URL)
    -					return
    -				}
     				updateReq := true
     				_, _, err = globalIAMSys.GetServiceAccount(ctx, svcAcctReq.AccessKey)
     				if err != nil {
    
580d9db85e04

Add APIs to import/export IAM data (#15014)

https://github.com/minio/minioPoornaJun 23, 2022via ghsa
6 files changed · +696 5
  • cmd/admin-bucket-handlers.go+1 1 modified
    @@ -804,7 +804,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
     	for _, file := range zr.File {
     		reader, err := file.Open()
     		if err != nil {
    -			writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
    +			writeErrorResponse(ctx, w, importError(ctx, err, file.Name, ""), r.URL)
     			return
     		}
     		sz := file.FileInfo().Size()
    
  • cmd/admin-handlers-users.go+678 1 modified
    @@ -24,9 +24,12 @@ import (
     	"io"
     	"io/ioutil"
     	"net/http"
    +	"os"
     	"sort"
    +	"time"
     
     	"github.com/gorilla/mux"
    +	"github.com/klauspost/compress/zip"
     	"github.com/minio/madmin-go"
     	"github.com/minio/minio/internal/auth"
     	"github.com/minio/minio/internal/config/dns"
    @@ -629,7 +632,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
     			opts.claims[k] = v
     		}
     	} else {
    -		// Need permission if we are creating a service acccount for a
    +		// Need permission if we are creating a service account for a
     		// user <> to the request sender
     		if !globalIAMSys.IsAllowed(iampolicy.Args{
     			AccountName:     requestorUser,
    @@ -1512,3 +1515,677 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
     		return
     	}
     }
    +
    +const (
    +	allPoliciesFile            = "policies.json"
    +	allUsersFile               = "users.json"
    +	allGroupsFile              = "groups.json"
    +	allSvcAcctsFile            = "svcaccts.json"
    +	userPolicyMappingsFile     = "user_mappings.json"
    +	groupPolicyMappingsFile    = "group_mappings.json"
    +	stsUserPolicyMappingsFile  = "stsuser_mappings.json"
    +	stsGroupPolicyMappingsFile = "stsgroup_mappings.json"
    +)
    +
    +// ExportIAMHandler - exports all iam info as a zipped file
    +func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
    +	ctx := newContext(r, w, "ExportIAM")
    +	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
    +
    +	// Get current object layer instance.
    +	objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportIAMAction)
    +	if objectAPI == nil {
    +		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
    +		return
    +	}
    +	// Initialize a zip writer which will provide a zipped content
    +	// of bucket metadata
    +	zipWriter := zip.NewWriter(w)
    +	defer zipWriter.Close()
    +	rawDataFn := func(r io.Reader, filename string, sz int) error {
    +		header, zerr := zip.FileInfoHeader(dummyFileInfo{
    +			name:    filename,
    +			size:    int64(sz),
    +			mode:    0o600,
    +			modTime: time.Now(),
    +			isDir:   false,
    +			sys:     nil,
    +		})
    +		if zerr != nil {
    +			logger.LogIf(ctx, zerr)
    +			return nil
    +		}
    +		header.Method = zip.Deflate
    +		zwriter, zerr := zipWriter.CreateHeader(header)
    +		if zerr != nil {
    +			logger.LogIf(ctx, zerr)
    +			return nil
    +		}
    +		if _, err := io.Copy(zwriter, r); err != nil {
    +			logger.LogIf(ctx, err)
    +		}
    +		return nil
    +	}
    +
    +	iamFiles := []string{
    +		allPoliciesFile,
    +		allUsersFile,
    +		allGroupsFile,
    +		allSvcAcctsFile,
    +		userPolicyMappingsFile,
    +		groupPolicyMappingsFile,
    +		stsUserPolicyMappingsFile,
    +		stsGroupPolicyMappingsFile,
    +	}
    +	for _, iamFile := range iamFiles {
    +		switch iamFile {
    +		case allPoliciesFile:
    +			allPolicies, err := globalIAMSys.ListPolicies(ctx, "")
    +			if err != nil {
    +				logger.LogIf(ctx, err)
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +
    +			policiesData, err := json.Marshal(allPolicies)
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			if err = rawDataFn(bytes.NewReader(policiesData), iamFile, len(policiesData)); err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +		case allUsersFile:
    +			userCreds := make(map[string]auth.Credentials)
    +			globalIAMSys.store.rlock()
    +			err := globalIAMSys.store.loadUsers(ctx, regUser, userCreds)
    +			globalIAMSys.store.runlock()
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			userAccounts := make(map[string]madmin.AddOrUpdateUserReq)
    +			for u, cred := range userCreds {
    +				status := madmin.AccountDisabled
    +				if cred.IsValid() {
    +					status = madmin.AccountEnabled
    +				}
    +				userAccounts[u] = madmin.AddOrUpdateUserReq{
    +					SecretKey: cred.SecretKey,
    +					Status:    status,
    +				}
    +			}
    +			userData, err := json.Marshal(userAccounts)
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +
    +			if err = rawDataFn(bytes.NewReader(userData), iamFile, len(userData)); err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +		case allGroupsFile:
    +			groups := make(map[string]GroupInfo)
    +			globalIAMSys.store.rlock()
    +			err := globalIAMSys.store.loadGroups(ctx, groups)
    +			globalIAMSys.store.runlock()
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			groupData, err := json.Marshal(groups)
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +
    +			if err = rawDataFn(bytes.NewReader(groupData), iamFile, len(groupData)); err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +		case allSvcAcctsFile:
    +			serviceAccounts := make(map[string]auth.Credentials)
    +			globalIAMSys.store.rlock()
    +			err := globalIAMSys.store.loadUsers(ctx, svcUser, serviceAccounts)
    +			globalIAMSys.store.runlock()
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			svcAccts := make(map[string]madmin.SRSvcAccCreate)
    +			for user, acc := range serviceAccounts {
    +				if user == siteReplicatorSvcAcc {
    +					// skip the site replicate svc account as it should be created automatically if
    +					// site replication is enabled.
    +					continue
    +				}
    +				claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.AccessKey)
    +				if err != nil {
    +					writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +					return
    +				}
    +				_, policy, err := globalIAMSys.GetServiceAccount(ctx, acc.AccessKey)
    +				if err != nil {
    +					writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +					return
    +				}
    +
    +				var policyJSON []byte
    +				if policy != nil {
    +					policyJSON, err = json.Marshal(policy)
    +					if err != nil {
    +						writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +						return
    +					}
    +				}
    +				svcAccts[user] = madmin.SRSvcAccCreate{
    +					Parent:        acc.ParentUser,
    +					AccessKey:     user,
    +					SecretKey:     acc.SecretKey,
    +					Groups:        acc.Groups,
    +					Claims:        claims,
    +					SessionPolicy: json.RawMessage(policyJSON),
    +					Status:        acc.Status,
    +				}
    +			}
    +
    +			svcAccData, err := json.Marshal(svcAccts)
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +
    +			if err = rawDataFn(bytes.NewReader(svcAccData), iamFile, len(svcAccData)); err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +		case userPolicyMappingsFile:
    +			userPolicyMap := make(map[string]MappedPolicy)
    +			globalIAMSys.store.rlock()
    +			err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, false, userPolicyMap)
    +			globalIAMSys.store.runlock()
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			userPolData, err := json.Marshal(userPolicyMap)
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +
    +			if err = rawDataFn(bytes.NewReader(userPolData), iamFile, len(userPolData)); err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +		case groupPolicyMappingsFile:
    +			groupPolicyMap := make(map[string]MappedPolicy)
    +			globalIAMSys.store.rlock()
    +			err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, true, groupPolicyMap)
    +			globalIAMSys.store.runlock()
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			grpPolData, err := json.Marshal(groupPolicyMap)
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +
    +			if err = rawDataFn(bytes.NewReader(grpPolData), iamFile, len(grpPolData)); err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +		case stsUserPolicyMappingsFile:
    +			userPolicyMap := make(map[string]MappedPolicy)
    +			globalIAMSys.store.rlock()
    +			err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, userPolicyMap)
    +			globalIAMSys.store.runlock()
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			userPolData, err := json.Marshal(userPolicyMap)
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			if err = rawDataFn(bytes.NewReader(userPolData), iamFile, len(userPolData)); err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +		case stsGroupPolicyMappingsFile:
    +			groupPolicyMap := make(map[string]MappedPolicy)
    +			globalIAMSys.store.rlock()
    +			err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, true, groupPolicyMap)
    +			globalIAMSys.store.runlock()
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			grpPolData, err := json.Marshal(groupPolicyMap)
    +			if err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +			if err = rawDataFn(bytes.NewReader(grpPolData), iamFile, len(grpPolData)); err != nil {
    +				writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
    +				return
    +			}
    +		}
    +	}
    +}
    +
    +// ImportIAM - imports all IAM info into MinIO
    +func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
    +	ctx := newContext(r, w, "ImportIAM")
    +
    +	defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
    +
    +	// Get current object layer instance.
    +	objectAPI := newObjectLayerFn()
    +	if objectAPI == nil || globalNotificationSys == nil {
    +		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
    +		return
    +	}
    +	cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
    +	if s3Err != ErrNone {
    +		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
    +		return
    +	}
    +	data, err := ioutil.ReadAll(r.Body)
    +	if err != nil {
    +		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
    +		return
    +	}
    +	reader := bytes.NewReader(data)
    +	zr, err := zip.NewReader(reader, int64(len(data)))
    +	if err != nil {
    +		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
    +		return
    +	}
    +	// import policies first
    +	{
    +		f, err := zr.Open(allPoliciesFile)
    +		switch {
    +		case errors.Is(err, os.ErrNotExist):
    +		case err != nil:
    +			writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL)
    +			return
    +		default:
    +			defer f.Close()
    +			var allPolicies map[string]iampolicy.Policy
    +			data, err = ioutil.ReadAll(f)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL)
    +				return
    +			}
    +			err = json.Unmarshal(data, &allPolicies)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, allPoliciesFile, ""), r.URL)
    +				return
    +			}
    +			for policyName, policy := range allPolicies {
    +				if policy.IsEmpty() {
    +					err = globalIAMSys.DeletePolicy(ctx, policyName, true)
    +				} else {
    +					err = globalIAMSys.SetPolicy(ctx, policyName, policy)
    +				}
    +				if err != nil {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, err, allPoliciesFile, policyName), r.URL)
    +					return
    +				}
    +			}
    +		}
    +	}
    +
    +	// import users
    +	{
    +		f, err := zr.Open(allUsersFile)
    +		switch {
    +		case errors.Is(err, os.ErrNotExist):
    +		case err != nil:
    +			writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allUsersFile, ""), r.URL)
    +			return
    +		default:
    +			defer f.Close()
    +			var userAccts map[string]madmin.AddOrUpdateUserReq
    +			data, err := ioutil.ReadAll(f)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allUsersFile, ""), r.URL)
    +				return
    +			}
    +			err = json.Unmarshal(data, &userAccts)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, allUsersFile, ""), r.URL)
    +				return
    +			}
    +			for accessKey, ureq := range userAccts {
    +				// Not allowed to add a user with same access key as root credential
    +				if owner && accessKey == cred.AccessKey {
    +					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAddUserInvalidArgument, err, allUsersFile, accessKey), r.URL)
    +					return
    +				}
    +
    +				userCred, exists := globalIAMSys.GetUser(ctx, accessKey)
    +				if exists && (userCred.IsTemp() || userCred.IsServiceAccount()) {
    +					// Updating STS credential is not allowed, and this API does not
    +					// support updating service accounts.
    +					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAddUserInvalidArgument, err, allUsersFile, accessKey), r.URL)
    +					return
    +				}
    +
    +				if (cred.IsTemp() || cred.IsServiceAccount()) && cred.ParentUser == accessKey {
    +					// Incoming access key matches parent user then we should
    +					// reject password change requests.
    +					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAddUserInvalidArgument, err, allUsersFile, accessKey), r.URL)
    +					return
    +				}
    +
    +				// Check if accessKey has beginning and end space characters, this only applies to new users.
    +				if !exists && hasSpaceBE(accessKey) {
    +					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, err, allUsersFile, accessKey), r.URL)
    +					return
    +				}
    +
    +				checkDenyOnly := false
    +				if accessKey == cred.AccessKey {
    +					// Check that there is no explicit deny - otherwise it's allowed
    +					// to change one's own password.
    +					checkDenyOnly = true
    +				}
    +
    +				if !globalIAMSys.IsAllowed(iampolicy.Args{
    +					AccountName:     cred.AccessKey,
    +					Groups:          cred.Groups,
    +					Action:          iampolicy.CreateUserAdminAction,
    +					ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
    +					IsOwner:         owner,
    +					Claims:          claims,
    +					DenyOnly:        checkDenyOnly,
    +				}) {
    +					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allUsersFile, accessKey), r.URL)
    +					return
    +				}
    +				if err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil {
    +					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, toAdminAPIErrCode(ctx, err), err, allUsersFile, accessKey), r.URL)
    +					return
    +				}
    +
    +			}
    +		}
    +	}
    +
    +	// import groups
    +	{
    +		f, err := zr.Open(allGroupsFile)
    +		switch {
    +		case errors.Is(err, os.ErrNotExist):
    +		case err != nil:
    +			writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allGroupsFile, ""), r.URL)
    +			return
    +		default:
    +			defer f.Close()
    +			var grpInfos map[string]GroupInfo
    +			data, err := ioutil.ReadAll(f)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allGroupsFile, ""), r.URL)
    +				return
    +			}
    +			if err = json.Unmarshal(data, &grpInfos); err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, allGroupsFile, ""), r.URL)
    +				return
    +			}
    +			for group, grpInfo := range grpInfos {
    +				// Check if group already exists
    +				if _, gerr := globalIAMSys.GetGroupDescription(group); gerr != nil {
    +					// If group does not exist, then check if the group has beginning and end space characters
    +					// we will reject such group names.
    +					if errors.Is(gerr, errNoSuchGroup) && hasSpaceBE(group) {
    +						writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, err, allGroupsFile, group), r.URL)
    +						return
    +					}
    +				}
    +				if gerr := globalIAMSys.AddUsersToGroup(ctx, group, grpInfo.Members); gerr != nil {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, err, allGroupsFile, group), r.URL)
    +					return
    +				}
    +			}
    +		}
    +	}
    +
    +	// import service accounts
    +	{
    +		f, err := zr.Open(allSvcAcctsFile)
    +		switch {
    +		case errors.Is(err, os.ErrNotExist):
    +		case err != nil:
    +			writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allSvcAcctsFile, ""), r.URL)
    +			return
    +		default:
    +			defer f.Close()
    +			var serviceAcctReqs map[string]madmin.SRSvcAccCreate
    +			data, err := ioutil.ReadAll(f)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allSvcAcctsFile, ""), r.URL)
    +				return
    +			}
    +			if err = json.Unmarshal(data, &serviceAcctReqs); err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, allSvcAcctsFile, ""), r.URL)
    +				return
    +			}
    +			for user, svcAcctReq := range serviceAcctReqs {
    +				var sp *iampolicy.Policy
    +				var err error
    +				if len(svcAcctReq.SessionPolicy) > 0 {
    +					sp, err = iampolicy.ParseConfig(bytes.NewReader(svcAcctReq.SessionPolicy))
    +					if err != nil {
    +						writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
    +						return
    +					}
    +				}
    +				// service account access key cannot have space characters beginning and end of the string.
    +				if hasSpaceBE(svcAcctReq.AccessKey) {
    +					writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
    +					return
    +				}
    +				if !globalIAMSys.IsAllowed(iampolicy.Args{
    +					AccountName:     svcAcctReq.AccessKey,
    +					Groups:          svcAcctReq.Groups,
    +					Action:          iampolicy.CreateServiceAccountAdminAction,
    +					ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
    +					IsOwner:         owner,
    +					Claims:          claims,
    +				}) {
    +					writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allSvcAcctsFile, user), r.URL)
    +					return
    +				}
    +				updateReq := true
    +				_, _, err = globalIAMSys.GetServiceAccount(ctx, svcAcctReq.AccessKey)
    +				if err != nil {
    +					if !errors.Is(err, errNoSuchServiceAccount) {
    +						writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
    +						return
    +					}
    +					updateReq = false
    +				}
    +				if updateReq {
    +					opts := updateServiceAccountOpts{
    +						secretKey:     svcAcctReq.SecretKey,
    +						status:        svcAcctReq.Status,
    +						sessionPolicy: sp,
    +					}
    +					err = globalIAMSys.UpdateServiceAccount(ctx, svcAcctReq.AccessKey, opts)
    +					if err != nil {
    +						writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
    +						return
    +					}
    +					continue
    +				}
    +				opts := newServiceAccountOpts{
    +					accessKey:     user,
    +					secretKey:     svcAcctReq.SecretKey,
    +					sessionPolicy: sp,
    +					claims:        svcAcctReq.Claims,
    +				}
    +
    +				// In case of LDAP we need to resolve the targetUser to a DN and
    +				// query their groups:
    +				if globalLDAPConfig.Enabled {
    +					opts.claims[ldapUserN] = svcAcctReq.AccessKey // simple username
    +					targetUser, _, err := globalLDAPConfig.LookupUserDN(svcAcctReq.AccessKey)
    +					if err != nil {
    +						writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
    +						return
    +					}
    +					opts.claims[ldapUser] = targetUser // username DN
    +				}
    +
    +				if _, err = globalIAMSys.NewServiceAccount(ctx, svcAcctReq.Parent, svcAcctReq.Groups, opts); err != nil {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
    +					return
    +				}
    +
    +			}
    +		}
    +	}
    +
    +	// import user policy mappings
    +	{
    +		f, err := zr.Open(userPolicyMappingsFile)
    +		switch {
    +		case errors.Is(err, os.ErrNotExist):
    +		case err != nil:
    +			writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, userPolicyMappingsFile, ""), r.URL)
    +			return
    +		default:
    +			defer f.Close()
    +			var userPolicyMap map[string]MappedPolicy
    +			data, err := ioutil.ReadAll(f)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, userPolicyMappingsFile, ""), r.URL)
    +				return
    +			}
    +			if err = json.Unmarshal(data, &userPolicyMap); err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, userPolicyMappingsFile, ""), r.URL)
    +				return
    +			}
    +			for u, pm := range userPolicyMap {
    +				// disallow setting policy mapping if user is a temporary user
    +				ok, _, err := globalIAMSys.IsTempUser(u)
    +				if err != nil && err != errNoSuchUser {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, err, userPolicyMappingsFile, u), r.URL)
    +					return
    +				}
    +				if ok {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, errIAMActionNotAllowed, userPolicyMappingsFile, u), r.URL)
    +					return
    +				}
    +				if err := globalIAMSys.PolicyDBSet(ctx, u, pm.Policies, false); err != nil {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, err, userPolicyMappingsFile, u), r.URL)
    +					return
    +				}
    +			}
    +		}
    +	}
    +
    +	// import group policy mappings
    +	{
    +		f, err := zr.Open(groupPolicyMappingsFile)
    +		switch {
    +		case errors.Is(err, os.ErrNotExist):
    +		case err != nil:
    +			writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, groupPolicyMappingsFile, ""), r.URL)
    +			return
    +		default:
    +			defer f.Close()
    +			var grpPolicyMap map[string]MappedPolicy
    +			data, err := ioutil.ReadAll(f)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, groupPolicyMappingsFile, ""), r.URL)
    +				return
    +			}
    +			if err = json.Unmarshal(data, &grpPolicyMap); err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, groupPolicyMappingsFile, ""), r.URL)
    +				return
    +			}
    +			for g, pm := range grpPolicyMap {
    +				if err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, true); err != nil {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, err, groupPolicyMappingsFile, g), r.URL)
    +					return
    +				}
    +			}
    +		}
    +	}
    +
    +	// import sts user policy mappings
    +	{
    +		f, err := zr.Open(stsUserPolicyMappingsFile)
    +		switch {
    +		case errors.Is(err, os.ErrNotExist):
    +		case err != nil:
    +			writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsUserPolicyMappingsFile, ""), r.URL)
    +			return
    +		default:
    +			defer f.Close()
    +			var userPolicyMap map[string]MappedPolicy
    +			data, err := ioutil.ReadAll(f)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsUserPolicyMappingsFile, ""), r.URL)
    +				return
    +			}
    +			if err = json.Unmarshal(data, &userPolicyMap); err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, stsUserPolicyMappingsFile, ""), r.URL)
    +				return
    +			}
    +			for u, pm := range userPolicyMap {
    +				// disallow setting policy mapping if user is a temporary user
    +				ok, _, err := globalIAMSys.IsTempUser(u)
    +				if err != nil && err != errNoSuchUser {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, err, stsUserPolicyMappingsFile, u), r.URL)
    +					return
    +				}
    +				if ok {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, errIAMActionNotAllowed, stsUserPolicyMappingsFile, u), r.URL)
    +					return
    +				}
    +				if err := globalIAMSys.PolicyDBSet(ctx, u, pm.Policies, false); err != nil {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, err, stsUserPolicyMappingsFile, u), r.URL)
    +					return
    +				}
    +			}
    +		}
    +	}
    +
    +	// import sts group policy mappings
    +	{
    +		f, err := zr.Open(stsGroupPolicyMappingsFile)
    +		switch {
    +		case errors.Is(err, os.ErrNotExist):
    +		case err != nil:
    +			writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsGroupPolicyMappingsFile, ""), r.URL)
    +			return
    +		default:
    +			defer f.Close()
    +			var grpPolicyMap map[string]MappedPolicy
    +			data, err := ioutil.ReadAll(f)
    +			if err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, stsGroupPolicyMappingsFile, ""), r.URL)
    +				return
    +			}
    +			if err = json.Unmarshal(data, &grpPolicyMap); err != nil {
    +				writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, stsGroupPolicyMappingsFile, ""), r.URL)
    +				return
    +			}
    +			for g, pm := range grpPolicyMap {
    +				if err := globalIAMSys.PolicyDBSet(ctx, g, pm.Policies, true); err != nil {
    +					writeErrorResponseJSON(ctx, w, importError(ctx, err, stsGroupPolicyMappingsFile, g), r.URL)
    +					return
    +				}
    +			}
    +		}
    +	}
    +}
    
  • cmd/admin-handler-utils.go+8 0 modified
    @@ -240,3 +240,11 @@ func importError(ctx context.Context, err error, fname, entity string) APIError
     	}
     	return toAPIError(ctx, fmt.Errorf("error importing %s from %s with: %w", entity, fname, err))
     }
    +
    +// wraps import error for more context
    +func importErrorWithAPIErr(ctx context.Context, apiErr APIErrorCode, err error, fname, entity string) APIError {
    +	if entity == "" {
    +		return errorCodes.ToAPIErrWithErr(apiErr, fmt.Errorf("error importing %s with: %w", fname, err))
    +	}
    +	return errorCodes.ToAPIErrWithErr(apiErr, fmt.Errorf("error importing %s from %s with: %w", entity, fname, err))
    +}
    
  • cmd/admin-router.go+6 0 modified
    @@ -170,6 +170,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
     		// Set Group Status
     		adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetGroupStatus))).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
     
    +		// Export IAM info to zipped file
    +		adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-iam").HandlerFunc(httpTraceHdrs(adminAPI.ExportIAM))
    +
    +		// Import IAM info
    +		adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(httpTraceHdrs(adminAPI.ImportIAM))
    +
     		// GetBucketQuotaConfig
     		adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
     			gz(httpTraceHdrs(adminAPI.GetBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
    
  • go.mod+1 1 modified
    @@ -50,7 +50,7 @@ require (
     	github.com/minio/kes v0.19.2
     	github.com/minio/madmin-go v1.3.14
     	github.com/minio/minio-go/v7 v7.0.29
    -	github.com/minio/pkg v1.1.25
    +	github.com/minio/pkg v1.1.26
     	github.com/minio/selfupdate v0.4.0
     	github.com/minio/sha256-simd v1.0.0
     	github.com/minio/simdjson-go v0.4.2
    
  • go.sum+2 2 modified
    @@ -636,8 +636,8 @@ github.com/minio/minio-go/v7 v7.0.23/go.mod h1:ei5JjmxwHaMrgsMrn4U/+Nmg+d8MKS1U2
     github.com/minio/minio-go/v7 v7.0.29 h1:7md6lIq1s6zPzUiDRX1BVLHolA4pDM8RMQqIszaJbY0=
     github.com/minio/minio-go/v7 v7.0.29/go.mod h1:x81+AX5gHSfCSqw7jxRKHvxUXMlE5uKX0Vb75Xk5yYg=
     github.com/minio/pkg v1.1.20/go.mod h1:Xo7LQshlxGa9shKwJ7NzQbgW4s8T/Wc1cOStR/eUiMY=
    -github.com/minio/pkg v1.1.25 h1:QYLzmTFUV5D3bY9qXKzDj7eW2C+HOPcdtIZft9q2Azo=
    -github.com/minio/pkg v1.1.25/go.mod h1:z9PfmEI804KFkF6eY4LoGe8IDVvTCsYGVuaf58Dr0WI=
    +github.com/minio/pkg v1.1.26 h1:a8x4sHNBxCiHEkxZ/0EBTLqvV3nMtM2G/A6lXNfXN3U=
    +github.com/minio/pkg v1.1.26/go.mod h1:z9PfmEI804KFkF6eY4LoGe8IDVvTCsYGVuaf58Dr0WI=
     github.com/minio/selfupdate v0.4.0 h1:A7t07pN4Ch1tBTIRStW0KhUVyykz+2muCqFsITQeEW8=
     github.com/minio/selfupdate v0.4.0/go.mod h1:mcDkzMgq8PRcpCRJo/NlPY7U45O5dfYl2Y0Rg7IustY=
     github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.