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.
master
Harshavardhana 4 years ago committed by GitHub
parent a3b266761e
commit 1b122526aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      cmd/admin-handlers-users.go
  2. 77
      cmd/iam.go
  3. 26
      cmd/sts-handlers.go
  4. 2
      pkg/auth/credentials.go

@ -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

@ -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…
Cancel
Save