diff --git a/cmd/admin-handlers-users.go b/cmd/admin-handlers-users.go index 1d4ceb7e4..8fd5fbbfb 100644 --- a/cmd/admin-handlers-users.go +++ b/cmd/admin-handlers-users.go @@ -271,7 +271,14 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ return } } - updatedAt, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) + + if globalIAMSys.LDAPConfig.Enabled() { + // We don't allow internal group manipulation in this API when LDAP + // is enabled for now. + err = errIAMActionNotAllowed + } else { + updatedAt, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) + } } if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) @@ -507,6 +514,12 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) { return } + // We don't allow internal user creation with LDAP enabled for now. + if globalIAMSys.LDAPConfig.Enabled() { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL) + return + } + updatedAt, err := globalIAMSys.CreateUser(ctx, accessKey, ureq) if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) @@ -1556,7 +1569,12 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request })) } -// SetPolicyForUserOrGroup - PUT /minio/admin/v3/set-policy?policy=xxx&user-or-group=?[&is-group] +// SetPolicyForUserOrGroup - sets a policy on a user or a group. +// +// PUT /minio/admin/v3/set-policy?policy=xxx&user-or-group=?[&is-group] +// +// Deprecated: This API is replaced by attach/detach policy APIs for specific +// type of users (builtin or LDAP). func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1608,6 +1626,31 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http userType := regUser if globalIAMSys.GetUsersSysType() == LDAPUsersSysType { userType = stsUser + + // Validate that the user or group exists in LDAP and use the normalized + // form of the entityName (which will be an LDAP DN). + var err error + if isGroup { + var foundGroupDN string + if foundGroupDN, err = globalIAMSys.LDAPConfig.GetValidatedGroupDN(nil, entityName); err != nil { + iamLogIf(ctx, err) + } else if foundGroupDN == "" { + err = errNoSuchGroup + } + entityName = foundGroupDN + } else { + var foundUserDN string + if foundUserDN, err = globalIAMSys.LDAPConfig.GetValidatedDNForUsername(entityName); err != nil { + iamLogIf(ctx, err) + } else if foundUserDN == "" { + err = errNoSuchUser + } + entityName = foundUserDN + } + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } } updatedAt, err := globalIAMSys.PolicyDBSet(ctx, entityName, policyName, userType, isGroup) @@ -2157,12 +2200,12 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { // 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) + writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, gerr, 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) + writeErrorResponseJSON(ctx, w, importError(ctx, gerr, allGroupsFile, group), r.URL) return } } @@ -2209,7 +2252,8 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { return } } - // service account access key cannot have space characters beginning and end of the string. + // 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 @@ -2384,6 +2428,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) { writeErrorResponseJSON(ctx, w, importError(ctx, errIAMActionNotAllowed, stsUserPolicyMappingsFile, u), r.URL) return } + if _, err := globalIAMSys.PolicyDBSet(ctx, u, pm.Policies, stsUser, false); err != nil { writeErrorResponseJSON(ctx, w, importError(ctx, err, stsUserPolicyMappingsFile, u), r.URL) return diff --git a/cmd/iam.go b/cmd/iam.go index bcaba8236..223595394 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -1269,10 +1269,6 @@ func (sys *IAMSys) CreateUser(ctx context.Context, accessKey string, ureq madmin return updatedAt, errServerNotInitialized } - if sys.usersSysType != MinIOUsersSysType { - return updatedAt, errIAMActionNotAllowed - } - if !auth.IsAccessKeyValid(accessKey) { return updatedAt, auth.ErrInvalidAccessKeyLength } @@ -1702,10 +1698,6 @@ func (sys *IAMSys) AddUsersToGroup(ctx context.Context, group string, members [] return updatedAt, errServerNotInitialized } - if sys.usersSysType != MinIOUsersSysType { - return updatedAt, errIAMActionNotAllowed - } - updatedAt, err = sys.store.AddUsersToGroup(ctx, group, members) if err != nil { return updatedAt, err @@ -1777,36 +1769,14 @@ func (sys *IAMSys) ListGroups(ctx context.Context) (r []string, err error) { } } -// PolicyDBSet - sets a policy for a user or group in the PolicyDB - the user doesn't have to exist since sometimes they are virtuals +// PolicyDBSet - sets a policy for a user or group in the PolicyDB. This does +// not validate if the user/group exists - that is the responsibility of the +// caller. func (sys *IAMSys) PolicyDBSet(ctx context.Context, name, policy string, userType IAMUserType, isGroup bool) (updatedAt time.Time, err error) { if !sys.Initialized() { return updatedAt, errServerNotInitialized } - if sys.LDAPConfig.Enabled() { - if isGroup { - var foundGroupDN string - if foundGroupDN, err = sys.LDAPConfig.GetValidatedGroupDN(nil, name); err != nil { - iamLogIf(ctx, err) - return - } else if foundGroupDN == "" { - err = errNoSuchGroup - return - } - name = foundGroupDN - } else { - var foundUserDN string - if foundUserDN, err = sys.LDAPConfig.GetValidatedDNForUsername(name); err != nil { - iamLogIf(ctx, err) - return - } else if foundUserDN == "" { - err = errNoSuchUser - return - } - name = foundUserDN - } - } - updatedAt, err = sys.store.PolicyDBSet(ctx, name, policy, userType, isGroup) if err != nil { return diff --git a/cmd/site-replication.go b/cmd/site-replication.go index 8b96b1914..814898075 100644 --- a/cmd/site-replication.go +++ b/cmd/site-replication.go @@ -1282,7 +1282,13 @@ func (c *SiteReplicationSys) PeerIAMUserChangeHandler(ctx context.Context, chang // only changing the account status. _, err = globalIAMSys.SetUserStatus(ctx, change.AccessKey, userReq.Status) } else { - _, err = globalIAMSys.CreateUser(ctx, change.AccessKey, userReq) + // We don't allow internal user creation with LDAP enabled for now + // (both sites must have LDAP disabled). + if globalIAMSys.LDAPConfig.Enabled() { + err = errIAMActionNotAllowed + } else { + _, err = globalIAMSys.CreateUser(ctx, change.AccessKey, userReq) + } } } if err != nil { @@ -1312,7 +1318,13 @@ func (c *SiteReplicationSys) PeerGroupInfoChangeHandler(ctx context.Context, cha if updReq.Status != "" && len(updReq.Members) == 0 { _, err = globalIAMSys.SetGroupStatus(ctx, updReq.Group, updReq.Status == madmin.GroupEnabled) } else { - _, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) + if globalIAMSys.LDAPConfig.Enabled() { + // We don't allow internal group manipulation in this API when + // LDAP is enabled for now (both sites must have LDAP disabled). + err = errIAMActionNotAllowed + } else { + _, err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members) + } if err == nil && updReq.Status != madmin.GroupEnabled { _, err = globalIAMSys.SetGroupStatus(ctx, updReq.Group, updReq.Status == madmin.GroupEnabled) } @@ -1417,7 +1429,39 @@ func (c *SiteReplicationSys) PeerPolicyMappingHandler(ctx context.Context, mappi } } - _, err := globalIAMSys.PolicyDBSet(ctx, mapping.UserOrGroup, mapping.Policy, IAMUserType(mapping.UserType), mapping.IsGroup) + // When LDAP is enabled, we verify that the user or group exists in LDAP and + // use the normalized form of the entityName (which will be an LDAP DN). + userType := IAMUserType(mapping.UserType) + isGroup := mapping.IsGroup + entityName := mapping.UserOrGroup + if globalIAMSys.GetUsersSysType() == LDAPUsersSysType && userType == stsUser { + + // Validate that the user or group exists in LDAP and use the normalized + // form of the entityName (which will be an LDAP DN). + var err error + if isGroup { + var foundGroupDN string + if foundGroupDN, err = globalIAMSys.LDAPConfig.GetValidatedGroupDN(nil, entityName); err != nil { + iamLogIf(ctx, err) + } else if foundGroupDN == "" { + err = errNoSuchGroup + } + entityName = foundGroupDN + } else { + var foundUserDN string + if foundUserDN, err = globalIAMSys.LDAPConfig.GetValidatedDNForUsername(entityName); err != nil { + iamLogIf(ctx, err) + } else if foundUserDN == "" { + err = errNoSuchUser + } + entityName = foundUserDN + } + if err != nil { + return wrapSRErr(err) + } + } + + _, err := globalIAMSys.PolicyDBSet(ctx, entityName, mapping.Policy, userType, isGroup) if err != nil { return wrapSRErr(err) } diff --git a/cmd/sts-handlers_test.go b/cmd/sts-handlers_test.go index 55bcf9968..e4429cbdd 100644 --- a/cmd/sts-handlers_test.go +++ b/cmd/sts-handlers_test.go @@ -806,8 +806,29 @@ func TestIAMImportAssetWithLDAP(t *testing.T) { exportContentStrings := map[string]string{ allPoliciesFile: `{"consoleAdmin":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["admin:*"]},{"Effect":"Allow","Action":["kms:*"]},{"Effect":"Allow","Action":["s3:*"],"Resource":["arn:aws:s3:::*"]}]},"diagnostics":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["admin:Prometheus","admin:Profiling","admin:ServerTrace","admin:ConsoleLog","admin:ServerInfo","admin:TopLocksInfo","admin:OBDInfo","admin:BandwidthMonitor"],"Resource":["arn:aws:s3:::*"]}]},"readonly":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetBucketLocation","s3:GetObject"],"Resource":["arn:aws:s3:::*"]}]},"readwrite":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:*"],"Resource":["arn:aws:s3:::*"]}]},"writeonly":{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:PutObject"],"Resource":["arn:aws:s3:::*"]}]}}`, - allUsersFile: `{}`, - allGroupsFile: `{}`, + + // Built-in user should be imported without errors even if LDAP is + // enabled. + allUsersFile: `{ + "foo": { + "secretKey": "foobar123", + "status": "enabled" + } +} +`, + // Built-in groups should be imported without errors even if LDAP is + // enabled. + allGroupsFile: `{ + "mygroup": { + "version": 1, + "status": "enabled", + "members": [ + "foo" + ], + "updatedAt": "2024-04-23T21:34:43.587429659Z" + } +} +`, allSvcAcctsFile: `{ "u4ccRswj62HV3Ifwima7": { "parent": "uid=svc.algorithm,OU=swengg,DC=min,DC=io", @@ -828,14 +849,32 @@ func TestIAMImportAssetWithLDAP(t *testing.T) { } } `, - userPolicyMappingsFile: `{}`, - // Contains duplicate mapping with same policy, we should not error out. + // Built-in user-to-policies mapping should be imported without errors + // even if LDAP is enabled. + userPolicyMappingsFile: `{ + "foo": { + "version": 0, + "policy": "readwrite", + "updatedAt": "2024-04-23T21:34:43.815519816Z" + } +} +`, + // Contains: + // + // 1. duplicate mapping with same policy, we should not error out; + // + // 2. non-LDAP group mapping, we should not error out; groupPolicyMappingsFile: `{ "cn=project.c,ou=groups,ou=swengg,DC=min,dc=io": { "version": 0, "policy": "consoleAdmin", "updatedAt": "2024-04-17T23:54:28.442998301Z" }, + "mygroup": { + "version": 0, + "policy": "consoleAdmin", + "updatedAt": "2024-04-23T21:34:43.66922872Z" + }, "cn=project.c,ou=groups,OU=swengg,DC=min,DC=io": { "version": 0, "policy": "consoleAdmin",