Support adding service accounts with expiration (#16430)

Co-authored-by: Harshavardhana <harsha@minio.io>
This commit is contained in:
Praveen raj Mani
2023-02-27 23:40:22 +05:30
committed by GitHub
parent 4d7c8e3bb8
commit 4d708cebe9
10 changed files with 134 additions and 40 deletions

View File

@@ -33,6 +33,7 @@ import (
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config/identity/openid"
"github.com/minio/minio/internal/jwt"
"github.com/minio/minio/internal/logger"
iampolicy "github.com/minio/pkg/iam/policy"
)
@@ -76,8 +77,13 @@ const (
iamFormatFile = "format.json"
iamFormatVersion1 = 1
minServiceAccountExpiry time.Duration = 15 * time.Minute
maxServiceAccountExpiry time.Duration = 365 * 24 * time.Hour
)
var errInvalidSvcAcctExpiration = errors.New("invalid service account expiration")
type iamFormat struct {
Version int `json:"version"`
}
@@ -394,6 +400,19 @@ func (c *iamCache) policyDBGet(mode UsersSysType, name string, isGroup bool) ([]
return policies, mp.UpdatedAt, nil
}
func (c *iamCache) updateUserWithClaims(key string, u UserIdentity) error {
if u.Credentials.SessionToken != "" {
jwtClaims, err := extractJWTClaims(u)
if err != nil {
return err
}
u.Credentials.Claims = jwtClaims.Map()
}
c.iamUsersMap[key] = u
c.updatedAt = time.Now()
return nil
}
// IAMStorageAPI defines an interface for the IAM persistence layer
type IAMStorageAPI interface {
// The role of the read-write lock is to prevent go routines from
@@ -1749,14 +1768,15 @@ func (store *IAMStoreSys) DeleteUser(ctx context.Context, accessKey string, user
if userType == regUser {
for _, ui := range cache.iamUsersMap {
u := ui.Credentials
if u.IsServiceAccount() && u.ParentUser == accessKey {
_ = store.deleteUserIdentity(ctx, u.AccessKey, svcUser)
delete(cache.iamUsersMap, u.AccessKey)
}
// Delete any associated STS users.
if u.IsTemp() && u.ParentUser == accessKey {
_ = store.deleteUserIdentity(ctx, u.AccessKey, stsUser)
delete(cache.iamUsersMap, u.AccessKey)
if u.ParentUser == accessKey {
switch {
case u.IsServiceAccount():
_ = store.deleteUserIdentity(ctx, u.AccessKey, svcUser)
delete(cache.iamUsersMap, u.AccessKey)
case u.IsTemp():
_ = store.deleteUserIdentity(ctx, u.AccessKey, stsUser)
delete(cache.iamUsersMap, u.AccessKey)
}
}
}
}
@@ -2106,8 +2126,9 @@ func (store *IAMStoreSys) SetUserStatus(ctx context.Context, accessKey string, s
return updatedAt, err
}
cache.iamUsersMap[accessKey] = uinfo
cache.updatedAt = time.Now()
if err := cache.updateUserWithClaims(accessKey, uinfo); err != nil {
return updatedAt, err
}
return uinfo.UpdatedAt, nil
}
@@ -2142,8 +2163,7 @@ func (store *IAMStoreSys) AddServiceAccount(ctx context.Context, cred auth.Crede
return updatedAt, err
}
cache.iamUsersMap[u.Credentials.AccessKey] = u
cache.updatedAt = time.Now()
cache.updateUserWithClaims(u.Credentials.AccessKey, u)
return u.UpdatedAt, nil
}
@@ -2170,6 +2190,14 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st
cr.Comment = opts.comment
}
if opts.expiration != nil {
expirationInUTC := opts.expiration.UTC()
if err := validateSvcExpirationInUTC(expirationInUTC); err != nil {
return updatedAt, err
}
cr.Expiration = expirationInUTC
}
switch opts.status {
// The caller did not ask to update status account, do nothing
case "":
@@ -2229,8 +2257,9 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st
return updatedAt, err
}
cache.iamUsersMap[u.Credentials.AccessKey] = u
cache.updatedAt = time.Now()
if err := cache.updateUserWithClaims(u.Credentials.AccessKey, u); err != nil {
return updatedAt, err
}
return u.UpdatedAt, nil
}
@@ -2331,8 +2360,9 @@ func (store *IAMStoreSys) AddUser(ctx context.Context, accessKey string, ureq ma
if err := store.saveUserIdentity(ctx, accessKey, regUser, u); err != nil {
return updatedAt, err
}
cache.iamUsersMap[accessKey] = u
if err := cache.updateUserWithClaims(accessKey, u); err != nil {
return updatedAt, err
}
return u.UpdatedAt, nil
}
@@ -2355,8 +2385,7 @@ func (store *IAMStoreSys) UpdateUserSecretKey(ctx context.Context, accessKey, se
return err
}
cache.iamUsersMap[accessKey] = u
return nil
return cache.updateUserWithClaims(accessKey, u)
}
// GetSTSAndServiceAccounts - returns all STS and Service account credentials.
@@ -2393,8 +2422,8 @@ func (store *IAMStoreSys) UpdateUserIdentity(ctx context.Context, cred auth.Cred
if err := store.saveUserIdentity(ctx, cred.AccessKey, userType, ui); err != nil {
return err
}
cache.iamUsersMap[cred.AccessKey] = ui
return nil
return cache.updateUserWithClaims(cred.AccessKey, ui)
}
// LoadUser - attempts to load user info from storage and updates cache.
@@ -2437,3 +2466,25 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) {
}
}
}
func extractJWTClaims(u UserIdentity) (*jwt.MapClaims, error) {
jwtClaims, err := auth.ExtractClaims(u.Credentials.SessionToken, u.Credentials.SecretKey)
if err != nil {
// Session tokens for STS creds will be generated with root secret
jwtClaims, err = auth.ExtractClaims(u.Credentials.SessionToken, globalActiveCred.SecretKey)
if err != nil {
return nil, err
}
}
return jwtClaims, nil
}
func validateSvcExpirationInUTC(expirationInUTC time.Time) error {
currentTime := time.Now().UTC()
minExpiration := currentTime.Add(minServiceAccountExpiry)
maxExpiration := currentTime.Add(maxServiceAccountExpiry)
if expirationInUTC.Before(minExpiration) || expirationInUTC.After(maxExpiration) {
return errInvalidSvcAcctExpiration
}
return nil
}