From 657d4c6222da07f58a23f48f0bf0c83b8b8463f8 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 7 Mar 2025 00:51:04 +0800 Subject: [PATCH 1/7] feat: listing LDAP users by policy mapping --- cmd/iam.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/cmd/iam.go b/cmd/iam.go index f8d0d60e83963..55937319da67d 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -912,10 +912,17 @@ func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) { return nil, errServerNotInitialized } - if sys.usersSysType != MinIOUsersSysType { + switch sys.usersSysType { + case MinIOUsersSysType: + return sys.listMinIOUsers() + case LDAPUsersSysType: + return sys.listLDAPUsers() + default: return nil, errIAMActionNotAllowed } +} +func (sys *IAMSys) listMinIOUsers() (map[string]madmin.UserInfo, error) { <-sys.configLoaded sys.Lock() @@ -940,6 +947,25 @@ func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) { return users, nil } +func (sys *IAMSys) listLDAPUsers() (map[string]madmin.UserInfo, error) { + <-sys.configLoaded + + policyMap := make(map[string]MappedPolicy) + if err := sys.store.loadMappedPolicies(context.Background(), stsUser, false, policyMap); err != nil { + return nil, err + } + + users := make(map[string]madmin.UserInfo) + for k, v := range policyMap { + users[k] = madmin.UserInfo{ + PolicyName: v.Policies, + Status: madmin.AccountEnabled, + } + } + + return users, nil +} + // IsTempUser - returns if given key is a temporary user. func (sys *IAMSys) IsTempUser(name string) (bool, string, error) { if !sys.Initialized() { From 53d81c4597d6e24891ae2b9fb8752d27606e23d7 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 7 Mar 2025 01:44:05 +0800 Subject: [PATCH 2/7] fix: LDAP users are enabled --- cmd/iam.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/iam.go b/cmd/iam.go index 55937319da67d..5af5c47051b29 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -1034,6 +1034,7 @@ func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) { } return madmin.UserInfo{ PolicyName: mappedPolicy.Policies, + Status: madmin.AccountEnabled, MemberOf: memberships.ToSlice(), }, nil } From c3c085abbd89b69ac9e56837139018ff7cd6ac6a Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 7 Mar 2025 02:43:55 +0800 Subject: [PATCH 3/7] feat: web login (JWT) for LDAP users --- cmd/jwt.go | 84 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 17 deletions(-) diff --git a/cmd/jwt.go b/cmd/jwt.go index ddc8ce024a838..e8880f1faf6ba 100644 --- a/cmd/jwt.go +++ b/cmd/jwt.go @@ -51,34 +51,85 @@ var ( ) func authenticateJWTUsers(accessKey, secretKey string, expiry time.Duration) (string, error) { - passedCredential, err := auth.CreateCredentials(accessKey, secretKey) + expiresAt := UTCNow().Add(expiry) + + cred, secret, err := authenticateUsersForJWT(accessKey, secretKey, expiresAt) if err != nil { return "", err } - expiresAt := UTCNow().Add(expiry) - return authenticateJWTUsersWithCredentials(passedCredential, expiresAt) + + claims := xjwt.NewMapClaims() + claims.SetExpiry(expiresAt) + claims.SetAccessKey(cred.AccessKey) + + jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims) + return jwt.SignedString([]byte(secret)) +} + +func authenticateUsersForJWT(accessKey, secretKey string, expiredAt time.Time) (auth.Credentials, string, error) { + if globalIAMSys.usersSysType == LDAPUsersSysType { + return authenticateLDAPUsersForJWT(accessKey, secretKey, expiredAt) + } + return authenticateMinIOUsersForJWT(accessKey, secretKey) } -func authenticateJWTUsersWithCredentials(credentials auth.Credentials, expiresAt time.Time) (string, error) { +func authenticateLDAPUsersForJWT(username, password string, expiredAt time.Time) (auth.Credentials, string, error) { + ldapUserDN, ldapGroups, err := globalLDAPConfig.Bind(username, password) + if err != nil { + return auth.Credentials{}, "", errAuthentication + } + + // Check if this user or their groups have a policy applied. + ldapPolicies, _ := globalIAMSys.PolicyDBGet(ldapUserDN, false, ldapGroups...) + if len(ldapPolicies) == 0 { + return auth.Credentials{}, "", errInvalidAccessKeyID + } + + m := map[string]interface{}{ + expClaim: expiredAt.Unix(), + ldapUser: ldapUserDN, + } + + secret := globalActiveCred.SecretKey + cred, err := auth.GetNewCredentialsWithMetadata(m, secret) + if err != nil { + return auth.Credentials{}, "", errAuthentication + } + + cred.ParentUser = ldapUserDN + cred.Groups = ldapGroups + + // Set the newly generated credentials, policyName is empty on purpose + // LDAP policies are applied automatically using their ldapUser, ldapGroups + // mapping. + if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, ""); err != nil { + return auth.Credentials{}, "", errAuthentication + } + + // Notify all other MinIO peers to reload temp users + for _, nerr := range globalNotificationSys.LoadUser(cred.AccessKey, true) { + if nerr.Err != nil { + return auth.Credentials{}, "", errAuthentication + } + } + + return cred, secret, nil +} + +func authenticateMinIOUsersForJWT(accessKey, secretKey string) (auth.Credentials, string, error) { serverCred := globalActiveCred - if serverCred.AccessKey != credentials.AccessKey { + if serverCred.AccessKey != accessKey { var ok bool - serverCred, ok = globalIAMSys.GetUser(credentials.AccessKey) + serverCred, ok = globalIAMSys.GetUser(accessKey) if !ok { - return "", errInvalidAccessKeyID + return auth.Credentials{}, "", errInvalidAccessKeyID } } - if !serverCred.Equal(credentials) { - return "", errAuthentication + if serverCred.AccessKey != serverCred.AccessKey && serverCred.SecretKey != secretKey { + return auth.Credentials{}, "", errAuthentication } - - claims := xjwt.NewMapClaims() - claims.SetExpiry(expiresAt) - claims.SetAccessKey(credentials.AccessKey) - - jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims) - return jwt.SignedString([]byte(serverCred.SecretKey)) + return serverCred, serverCred.SecretKey, nil } func authenticateNode(accessKey, secretKey, audience string) (string, error) { @@ -119,7 +170,6 @@ func webTokenCallback(claims *xjwt.MapClaims) ([]byte, error) { return nil, errInvalidAccessKeyID } return []byte(cred.SecretKey), nil - } func isAuthTokenValid(token string) bool { From 3443c5ecfc6359c417f3d61ed7b249710f18ea3c Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 7 Mar 2025 04:22:59 +0800 Subject: [PATCH 4/7] fix: web login permission for LDAP users --- cmd/jwt.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/jwt.go b/cmd/jwt.go index e8880f1faf6ba..44c8016cd90e6 100644 --- a/cmd/jwt.go +++ b/cmd/jwt.go @@ -19,6 +19,7 @@ package cmd import ( "errors" "net/http" + "strings" "time" jwtgo "github.com/dgrijalva/jwt-go" @@ -102,7 +103,7 @@ func authenticateLDAPUsersForJWT(username, password string, expiredAt time.Time) // Set the newly generated credentials, policyName is empty on purpose // LDAP policies are applied automatically using their ldapUser, ldapGroups // mapping. - if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, ""); err != nil { + if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, strings.Join(ldapPolicies, ",")); err != nil { return auth.Credentials{}, "", errAuthentication } From 6b7d61c32531aa2145dfe4d823913e4725ab7657 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 7 Mar 2025 05:50:54 +0800 Subject: [PATCH 5/7] fix: serializing LDAP user in web JWT --- cmd/iam.go | 12 ++++++------ cmd/jwt.go | 7 ++++--- cmd/jwt/parser.go | 8 ++++++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/cmd/iam.go b/cmd/iam.go index 5af5c47051b29..104d4f9317df6 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -2159,13 +2159,13 @@ func (sys *IAMSys) IsAllowedLDAPSTS(args iampolicy.Args, parentUser string) bool } // Check policy for this LDAP user. - ldapPolicies, err := sys.PolicyDBGet(parentUser, false, args.Groups...) - if err != nil { - return false - } - + ldapPolicies, _ := sys.policyDBGet(args.AccountName, false) if len(ldapPolicies) == 0 { - return false + parentPolicies, err := sys.PolicyDBGet(parentUser, false, args.Groups...) + if err != nil { + return false + } + ldapPolicies = parentPolicies } var availablePolicies []iampolicy.Policy diff --git a/cmd/jwt.go b/cmd/jwt.go index 44c8016cd90e6..f38ad82fe4d18 100644 --- a/cmd/jwt.go +++ b/cmd/jwt.go @@ -62,6 +62,9 @@ func authenticateJWTUsers(accessKey, secretKey string, expiry time.Duration) (st claims := xjwt.NewMapClaims() claims.SetExpiry(expiresAt) claims.SetAccessKey(cred.AccessKey) + if cred.ParentUser != "" { + claims.SetLDAPUser(cred.ParentUser) + } jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims) return jwt.SignedString([]byte(secret)) @@ -100,9 +103,7 @@ func authenticateLDAPUsersForJWT(username, password string, expiredAt time.Time) cred.ParentUser = ldapUserDN cred.Groups = ldapGroups - // Set the newly generated credentials, policyName is empty on purpose - // LDAP policies are applied automatically using their ldapUser, ldapGroups - // mapping. + // Set the newly generated credentials, ensure that policies are fixed during the session. if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, strings.Join(ldapPolicies, ",")); err != nil { return auth.Credentials{}, "", errAuthentication } diff --git a/cmd/jwt/parser.go b/cmd/jwt/parser.go index 72c97042c8755..df252d9d1b2c9 100644 --- a/cmd/jwt/parser.go +++ b/cmd/jwt/parser.go @@ -78,6 +78,7 @@ type StandardClaims struct { // MapClaims - implements custom unmarshaller type MapClaims struct { AccessKey string `json:"accessKey,omitempty"` + LDAPUser string `json:"ldapUser,omitempty"` jwtgo.MapClaims } @@ -162,6 +163,12 @@ func (c *MapClaims) SetAccessKey(accessKey string) { c.MapClaims["accessKey"] = accessKey } +// SetLDAPUser sets parent user dn as custom +// "ldapUser" field. +func (c *MapClaims) SetLDAPUser(ldapUser string) { + c.MapClaims["ldapUser"] = ldapUser +} + // Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible // claims interface, additionally validates "accessKey" fields. func (c *MapClaims) Valid() error { @@ -317,6 +324,7 @@ func ParseWithClaims(tokenStr string, claims *MapClaims, fn func(*MapClaims) ([] jwtgo.ValidationErrorClaimsInvalid) } } + claims.LDAPUser, _ = claims.Lookup("ldapUser") // Lookup key from claims, claims may not be valid and may return // invalid key which is okay as the signature verification will fail. From 0d8689f18ce178d9adcd122a72534f035c0fd52f Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 7 Mar 2025 13:00:14 +0800 Subject: [PATCH 6/7] feat: listing LDAP groups by policy mapping --- cmd/iam.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/cmd/iam.go b/cmd/iam.go index 104d4f9317df6..25667e16ac2c4 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -1835,16 +1835,23 @@ func (sys *IAMSys) ListGroups() (r []string, err error) { return r, errServerNotInitialized } - if sys.usersSysType != MinIOUsersSysType { - return nil, errIAMActionNotAllowed + switch sys.usersSysType { + case MinIOUsersSysType: + return sys.listMinIOGroups() + case LDAPUsersSysType: + return sys.listLDAPGroups() + default: + return r, errIAMActionNotAllowed } +} +func (sys *IAMSys) listMinIOGroups() ([]string, error) { <-sys.configLoaded sys.Lock() defer sys.Unlock() - r = make([]string, 0, len(sys.iamGroupsMap)) + r := make([]string, 0, len(sys.iamGroupsMap)) for k := range sys.iamGroupsMap { r = append(r, k) } @@ -1852,6 +1859,22 @@ func (sys *IAMSys) ListGroups() (r []string, err error) { return r, nil } +func (sys *IAMSys) listLDAPGroups() ([]string, error) { + <-sys.configLoaded + + policyMap := make(map[string]MappedPolicy) + if err := sys.store.loadMappedPolicies(context.Background(), stsUser, true, policyMap); err != nil { + return nil, err + } + + r := make([]string, 0, len(policyMap)) + for k := range policyMap { + r = append(r, k) + } + + return r, nil +} + // PolicyDBSet - sets a policy for a user or group in the PolicyDB. func (sys *IAMSys) PolicyDBSet(name, policy string, isGroup bool) error { if !sys.Initialized() { From 9f083ad96a4136fb00280500e5a053a077d92217 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 7 Mar 2025 13:07:05 +0800 Subject: [PATCH 7/7] fix: don't list temp users --- cmd/iam.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/iam.go b/cmd/iam.go index 25667e16ac2c4..080429aaf705b 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -963,6 +963,13 @@ func (sys *IAMSys) listLDAPUsers() (map[string]madmin.UserInfo, error) { } } + // remove temp users created via STS + for k, v := range sys.iamUsersMap { + if v.IsTemp() || v.IsServiceAccount() { + delete(users, k) + } + } + return users, nil }