mirror of
https://github.com/minio/minio.git
synced 2025-04-11 06:57:49 -04:00
fix: add service account support for AssumeRole/LDAPIdentity creds (#9451)
allow generating service accounts for temporary credentials which have a designated parent, currently OpenID is not yet supported. added checks to ensure that service account cannot generate further service accounts for itself, service accounts can never be a parent to any credential.
This commit is contained in:
parent
a3b266761e
commit
1b122526aa
@ -397,7 +397,12 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newCred, err := globalIAMSys.NewServiceAccount(ctx, cred.AccessKey, createReq.Policy)
|
parentUser := cred.AccessKey
|
||||||
|
if cred.ParentUser != "" {
|
||||||
|
parentUser = cred.ParentUser
|
||||||
|
}
|
||||||
|
|
||||||
|
newCred, err := globalIAMSys.NewServiceAccount(ctx, parentUser, createReq.Policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@ -516,7 +521,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cred.AccessKey != user {
|
if cred.AccessKey != user || cred.ParentUser != user {
|
||||||
// The service account belongs to another user but return not found error to mitigate brute force attacks.
|
// The service account belongs to another user but return not found error to mitigate brute force attacks.
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServiceAccountNotFound), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServiceAccountNotFound), r.URL)
|
||||||
return
|
return
|
||||||
|
77
cmd/iam.go
77
cmd/iam.go
@ -145,8 +145,8 @@ type UserIdentity struct {
|
|||||||
Credentials auth.Credentials `json:"credentials"`
|
Credentials auth.Credentials `json:"credentials"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUserIdentity(creds auth.Credentials) UserIdentity {
|
func newUserIdentity(cred auth.Credentials) UserIdentity {
|
||||||
return UserIdentity{Version: 1, Credentials: creds}
|
return UserIdentity{Version: 1, Credentials: cred}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupInfo contains info about a group
|
// GroupInfo contains info about a group
|
||||||
@ -440,7 +440,7 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
|
|||||||
// This case cannot happen
|
// This case cannot happen
|
||||||
return errNoSuchUser
|
return errNoSuchUser
|
||||||
}
|
}
|
||||||
// User is from STS if the creds are temporary
|
// User is from STS if the cred are temporary
|
||||||
if cr.IsTemp() {
|
if cr.IsTemp() {
|
||||||
usersType = append(usersType, stsUser)
|
usersType = append(usersType, stsUser)
|
||||||
} else {
|
} else {
|
||||||
@ -553,6 +553,16 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
|
|||||||
return errIAMActionNotAllowed
|
return errIAMActionNotAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete any service accounts if any first.
|
||||||
|
for _, u := range sys.iamUsersMap {
|
||||||
|
if u.IsServiceAccount() {
|
||||||
|
if u.ParentUser == accessKey {
|
||||||
|
_ = sys.store.deleteUserIdentity(u.AccessKey, srvAccUser)
|
||||||
|
delete(sys.iamUsersMap, u.AccessKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// It is ok to ignore deletion error on the mapped policy
|
// It is ok to ignore deletion error on the mapped policy
|
||||||
sys.store.deleteMappedPolicy(accessKey, regularUser, false)
|
sys.store.deleteMappedPolicy(accessKey, regularUser, false)
|
||||||
err := sys.store.deleteUserIdentity(accessKey, regularUser)
|
err := sys.store.deleteUserIdentity(accessKey, regularUser)
|
||||||
@ -564,15 +574,6 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
|
|||||||
delete(sys.iamUsersMap, accessKey)
|
delete(sys.iamUsersMap, accessKey)
|
||||||
delete(sys.iamUserPolicyMap, accessKey)
|
delete(sys.iamUserPolicyMap, accessKey)
|
||||||
|
|
||||||
for _, u := range sys.iamUsersMap {
|
|
||||||
if u.IsServiceAccount() {
|
|
||||||
if u.ParentUser == accessKey {
|
|
||||||
_ = sys.store.deleteUserIdentity(u.AccessKey, srvAccUser)
|
|
||||||
delete(sys.iamUsersMap, u.AccessKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,12 +660,12 @@ func (sys *IAMSys) IsTempUser(name string) (bool, error) {
|
|||||||
sys.store.rlock()
|
sys.store.rlock()
|
||||||
defer sys.store.runlock()
|
defer sys.store.runlock()
|
||||||
|
|
||||||
creds, found := sys.iamUsersMap[name]
|
cred, found := sys.iamUsersMap[name]
|
||||||
if !found {
|
if !found {
|
||||||
return false, errNoSuchUser
|
return false, errNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
return creds.IsTemp(), nil
|
return cred.IsTemp(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsServiceAccount - returns if given key is a service account
|
// IsServiceAccount - returns if given key is a service account
|
||||||
@ -677,13 +678,13 @@ func (sys *IAMSys) IsServiceAccount(name string) (bool, string, error) {
|
|||||||
sys.store.rlock()
|
sys.store.rlock()
|
||||||
defer sys.store.runlock()
|
defer sys.store.runlock()
|
||||||
|
|
||||||
creds, found := sys.iamUsersMap[name]
|
cred, found := sys.iamUsersMap[name]
|
||||||
if !found {
|
if !found {
|
||||||
return false, "", errNoSuchUser
|
return false, "", errNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
if creds.IsServiceAccount() {
|
if cred.IsServiceAccount() {
|
||||||
return true, creds.ParentUser, nil
|
return true, cred.ParentUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, "", nil
|
return false, "", nil
|
||||||
@ -713,19 +714,19 @@ func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
creds, found := sys.iamUsersMap[name]
|
cred, found := sys.iamUsersMap[name]
|
||||||
if !found {
|
if !found {
|
||||||
return u, errNoSuchUser
|
return u, errNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
if creds.IsTemp() {
|
if cred.IsTemp() || cred.IsServiceAccount() {
|
||||||
return u, errIAMActionNotAllowed
|
return u, errIAMActionNotAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
u = madmin.UserInfo{
|
u = madmin.UserInfo{
|
||||||
PolicyName: sys.iamUserPolicyMap[name].Policy,
|
PolicyName: sys.iamUserPolicyMap[name].Policy,
|
||||||
Status: func() madmin.AccountStatus {
|
Status: func() madmin.AccountStatus {
|
||||||
if creds.IsValid() {
|
if cred.IsValid() {
|
||||||
return madmin.AccountEnabled
|
return madmin.AccountEnabled
|
||||||
}
|
}
|
||||||
return madmin.AccountDisabled
|
return madmin.AccountDisabled
|
||||||
@ -758,7 +759,7 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
|
|||||||
return errNoSuchUser
|
return errNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
if cred.IsTemp() {
|
if cred.IsTemp() || cred.IsServiceAccount() {
|
||||||
return errIAMActionNotAllowed
|
return errIAMActionNotAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -806,10 +807,6 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, ses
|
|||||||
sys.store.lock()
|
sys.store.lock()
|
||||||
defer sys.store.unlock()
|
defer sys.store.unlock()
|
||||||
|
|
||||||
if sys.usersSysType != MinIOUsersSysType {
|
|
||||||
return auth.Credentials{}, errIAMActionNotAllowed
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentUser == globalActiveCred.AccessKey {
|
if parentUser == globalActiveCred.AccessKey {
|
||||||
return auth.Credentials{}, errIAMActionNotAllowed
|
return auth.Credentials{}, errIAMActionNotAllowed
|
||||||
}
|
}
|
||||||
@ -819,7 +816,16 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, ses
|
|||||||
return auth.Credentials{}, errNoSuchUser
|
return auth.Credentials{}, errNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
if cr.IsTemp() {
|
// Disallow service accounts to further create more service accounts.
|
||||||
|
if cr.IsServiceAccount() {
|
||||||
|
return auth.Credentials{}, errIAMActionNotAllowed
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Disallow temporary users with no parent, this is most
|
||||||
|
// probably with OpenID which we don't support to provide
|
||||||
|
// any parent user, LDAPUsersType and MinIOUsersType are
|
||||||
|
// currently supported.
|
||||||
|
if cr.ParentUser == "" && cr.IsTemp() {
|
||||||
return auth.Credentials{}, errIAMActionNotAllowed
|
return auth.Credentials{}, errIAMActionNotAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -838,8 +844,8 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, ses
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return auth.Credentials{}, err
|
return auth.Credentials{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cred.ParentUser = parentUser
|
cred.ParentUser = parentUser
|
||||||
|
|
||||||
u := newUserIdentity(cred)
|
u := newUserIdentity(cred)
|
||||||
|
|
||||||
if err := sys.store.saveUserIdentity(u.Credentials.AccessKey, srvAccUser, u); err != nil {
|
if err := sys.store.saveUserIdentity(u.Credentials.AccessKey, srvAccUser, u); err != nil {
|
||||||
@ -861,10 +867,6 @@ func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([
|
|||||||
sys.store.rlock()
|
sys.store.rlock()
|
||||||
defer sys.store.runlock()
|
defer sys.store.runlock()
|
||||||
|
|
||||||
if sys.usersSysType != MinIOUsersSysType {
|
|
||||||
return nil, errIAMActionNotAllowed
|
|
||||||
}
|
|
||||||
|
|
||||||
var serviceAccounts []string
|
var serviceAccounts []string
|
||||||
|
|
||||||
for k, v := range sys.iamUsersMap {
|
for k, v := range sys.iamUsersMap {
|
||||||
@ -886,10 +888,6 @@ func (sys *IAMSys) GetServiceAccountParent(ctx context.Context, accessKey string
|
|||||||
sys.store.rlock()
|
sys.store.rlock()
|
||||||
defer sys.store.runlock()
|
defer sys.store.runlock()
|
||||||
|
|
||||||
if sys.usersSysType != MinIOUsersSysType {
|
|
||||||
return "", errIAMActionNotAllowed
|
|
||||||
}
|
|
||||||
|
|
||||||
sa, ok := sys.iamUsersMap[accessKey]
|
sa, ok := sys.iamUsersMap[accessKey]
|
||||||
if !ok || !sa.IsServiceAccount() {
|
if !ok || !sa.IsServiceAccount() {
|
||||||
return "", errNoSuchServiceAccount
|
return "", errNoSuchServiceAccount
|
||||||
@ -908,10 +906,6 @@ func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string) e
|
|||||||
sys.store.lock()
|
sys.store.lock()
|
||||||
defer sys.store.unlock()
|
defer sys.store.unlock()
|
||||||
|
|
||||||
if sys.usersSysType != MinIOUsersSysType {
|
|
||||||
return errIAMActionNotAllowed
|
|
||||||
}
|
|
||||||
|
|
||||||
sa, ok := sys.iamUsersMap[accessKey]
|
sa, ok := sys.iamUsersMap[accessKey]
|
||||||
if !ok || !sa.IsServiceAccount() {
|
if !ok || !sa.IsServiceAccount() {
|
||||||
return errNoSuchServiceAccount
|
return errNoSuchServiceAccount
|
||||||
@ -1009,6 +1003,11 @@ func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
|||||||
defer sys.store.runlock()
|
defer sys.store.runlock()
|
||||||
|
|
||||||
cred, ok = sys.iamUsersMap[accessKey]
|
cred, ok = sys.iamUsersMap[accessKey]
|
||||||
|
if ok && cred.IsValid() {
|
||||||
|
if cred.ParentUser != "" {
|
||||||
|
_, ok = sys.iamUsersMap[cred.ParentUser]
|
||||||
|
}
|
||||||
|
}
|
||||||
return cred, ok && cred.IsValid()
|
return cred, ok && cred.IsValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/cmd/config/identity/openid"
|
"github.com/minio/minio/cmd/config/identity/openid"
|
||||||
@ -135,6 +136,11 @@ func checkAssumeRoleAuth(ctx context.Context, r *http.Request) (user auth.Creden
|
|||||||
return user, ErrSTSAccessDenied
|
return user, ErrSTSAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temporary credentials or Service accounts cannot generate further temporary credentials.
|
||||||
|
if user.IsTemp() || user.IsServiceAccount() {
|
||||||
|
return user, ErrSTSAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
return user, ErrSTSNone
|
return user, ErrSTSNone
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,10 +213,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
policyName := ""
|
policyName := strings.Join(policies, ",")
|
||||||
if len(policies) > 0 {
|
|
||||||
policyName = policies[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// This policy is the policy associated with the user
|
// This policy is the policy associated with the user
|
||||||
// requesting for temporary credentials. The temporary
|
// requesting for temporary credentials. The temporary
|
||||||
@ -228,6 +231,10 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the parent of the temporary access key, this is useful
|
||||||
|
// in obtaining service accounts by this cred.
|
||||||
|
cred.ParentUser = user.AccessKey
|
||||||
|
|
||||||
// Set the newly generated credentials.
|
// Set the newly generated credentials.
|
||||||
if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil {
|
if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil {
|
||||||
writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
|
writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
|
||||||
@ -500,9 +507,14 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
policyName := ""
|
// Set the parent of the temporary access key, this is useful
|
||||||
// Set the newly generated credentials.
|
// in obtaining service accounts by this cred.
|
||||||
if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil {
|
cred.ParentUser = ldapUsername
|
||||||
|
|
||||||
|
// 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 {
|
||||||
writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
|
writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ func (cred Credentials) IsExpired() bool {
|
|||||||
|
|
||||||
// IsTemp - returns whether credential is temporary or not.
|
// IsTemp - returns whether credential is temporary or not.
|
||||||
func (cred Credentials) IsTemp() bool {
|
func (cred Credentials) IsTemp() bool {
|
||||||
return cred.SessionToken != "" && cred.ParentUser == "" && !cred.Expiration.IsZero() && !cred.Expiration.Equal(timeSentinel)
|
return cred.SessionToken != "" && !cred.Expiration.IsZero() && !cred.Expiration.Equal(timeSentinel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsServiceAccount - returns whether credential is a service account or not
|
// IsServiceAccount - returns whether credential is a service account or not
|
||||||
|
Loading…
x
Reference in New Issue
Block a user