diff --git a/cmd/iam.go b/cmd/iam.go index f8d0d60e83963..080429aaf705b 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,32 @@ 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, + } + } + + // remove temp users created via STS + for k, v := range sys.iamUsersMap { + if v.IsTemp() || v.IsServiceAccount() { + delete(users, k) + } + } + + return users, nil +} + // IsTempUser - returns if given key is a temporary user. func (sys *IAMSys) IsTempUser(name string) (bool, string, error) { if !sys.Initialized() { @@ -1008,6 +1041,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 } @@ -1808,16 +1842,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) } @@ -1825,6 +1866,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() { @@ -2132,13 +2189,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 ddc8ce024a838..f38ad82fe4d18 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" @@ -51,34 +52,86 @@ 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) + if cred.ParentUser != "" { + claims.SetLDAPUser(cred.ParentUser) + } + + 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 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, ensure that policies are fixed during the session. + if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, strings.Join(ldapPolicies, ",")); 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 authenticateJWTUsersWithCredentials(credentials auth.Credentials, expiresAt time.Time) (string, error) { +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 +172,6 @@ func webTokenCallback(claims *xjwt.MapClaims) ([]byte, error) { return nil, errInvalidAccessKeyID } return []byte(cred.SecretKey), nil - } func isAuthTokenValid(token string) bool { 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.