Add service account type in IAM (#9029)

master
Anis Elleuch 5 years ago committed by GitHub
parent 8b880a246a
commit 496f4a7dc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 114
      cmd/admin-handlers-users.go
  2. 4
      cmd/admin-router.go
  3. 7
      cmd/api-errors.go
  4. 13
      cmd/common-main.go
  5. 39
      cmd/config-encrypted.go
  6. 3
      cmd/globals.go
  7. 111
      cmd/iam-etcd-store.go
  8. 104
      cmd/iam-object-store.go
  9. 347
      cmd/iam.go
  10. 7
      cmd/peer-rest-server.go
  11. 3
      cmd/sts-handlers.go
  12. 17
      pkg/auth/credentials.go
  13. 52
      pkg/madmin/examples/add-service-account-and-policy.go
  14. 49
      pkg/madmin/examples/get-service-account.go
  15. 98
      pkg/madmin/user-commands.go

@ -378,6 +378,120 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
} }
} }
// AddServiceAccount - PUT /minio/admin/v2/add-service-account?parentUser=<parent_user_accesskey>
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AddServiceAccount")
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.CreateUserAdminAction)
if objectAPI == nil {
return
}
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
// More than maxConfigSize bytes were available
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
return
}
password := cred.SecretKey
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
var createReq madmin.AddServiceAccountReq
if err = json.Unmarshal(configBytes, &createReq); err != nil {
logger.LogIf(ctx, err)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return
}
if createReq.Parent == "" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
return
}
// Disallow creating service accounts for the root user
if createReq.Parent == globalActiveCred.AccessKey {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddServiceAccountInvalidArgument), r.URL)
return
}
creds, err := globalIAMSys.NewServiceAccount(ctx, createReq.Parent, createReq.Policy)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Notify all other Minio peers to reload user
for _, nerr := range globalNotificationSys.LoadUser(creds.AccessKey, false) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
var createResp = madmin.AddServiceAccountResp{
Credentials: creds,
}
data, err := json.Marshal(createResp)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
econfigData, err := madmin.EncryptData(password, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, econfigData)
}
// GetServiceAccount - GET /minio/admin/v2/get-service-account?accessKey=<access_key>
func (a adminAPIHandlers) GetServiceAccount(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetServiceAccount")
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetUserAdminAction)
if objectAPI == nil {
return
}
vars := mux.Vars(r)
accessKey := vars["accessKey"]
creds, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
data, err := json.Marshal(creds)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
password := globalActiveCred.SecretKey
econfigData, err := madmin.EncryptData(password, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, econfigData)
}
// InfoCannedPolicy - GET /minio/admin/v2/info-canned-policy?name={policyName} // InfoCannedPolicy - GET /minio/admin/v2/info-canned-policy?name={policyName}
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "InfoCannedPolicy") ctx := newContext(r, w, "InfoCannedPolicy")

@ -110,6 +110,10 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
adminRouter.Methods(http.MethodPut).Path(adminAPIVersionPrefix+"/set-user-status").HandlerFunc(httpTraceHdrs(adminAPI.SetUserStatus)). adminRouter.Methods(http.MethodPut).Path(adminAPIVersionPrefix+"/set-user-status").HandlerFunc(httpTraceHdrs(adminAPI.SetUserStatus)).
Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}") Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
// Service accounts ops
adminRouter.Methods(http.MethodPut).Path(adminAPIVersionPrefix + "/add-service-account").HandlerFunc(httpTraceHdrs(adminAPI.AddServiceAccount))
adminRouter.Methods(http.MethodGet).Path(adminAPIVersionPrefix+"/get-service-account").HandlerFunc(httpTraceHdrs(adminAPI.GetServiceAccount)).Queries("accessKey", "{accessKey:.*}")
// Info policy IAM // Info policy IAM
adminRouter.Methods(http.MethodGet).Path(adminAPIVersionPrefix+"/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}") adminRouter.Methods(http.MethodGet).Path(adminAPIVersionPrefix+"/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")

@ -332,6 +332,7 @@ const (
ErrAdminProfilerNotEnabled ErrAdminProfilerNotEnabled
ErrInvalidDecompressedSize ErrInvalidDecompressedSize
ErrAddUserInvalidArgument ErrAddUserInvalidArgument
ErrAddServiceAccountInvalidArgument
ErrPostPolicyConditionInvalidFormat ErrPostPolicyConditionInvalidFormat
) )
@ -1573,6 +1574,12 @@ var errorCodes = errorCodeMap{
Description: "User is not allowed to be same as admin access key", Description: "User is not allowed to be same as admin access key",
HTTPStatusCode: http.StatusConflict, HTTPStatusCode: http.StatusConflict,
}, },
ErrAddServiceAccountInvalidArgument: {
Code: "XMinioInvalidArgument",
Description: "New service accounts for admin access key is not allowed",
HTTPStatusCode: http.StatusConflict,
},
ErrPostPolicyConditionInvalidFormat: { ErrPostPolicyConditionInvalidFormat: {
Code: "PostPolicyInvalidKeyName", Code: "PostPolicyInvalidKeyName",
Description: "Invalid according to Policy: Policy Condition failed", Description: "Invalid according to Policy: Policy Condition failed",

@ -22,6 +22,7 @@ import (
"encoding/gob" "encoding/gob"
"errors" "errors"
"net" "net"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -226,10 +227,22 @@ func handleCommonEnvVars() {
globalConfigEncrypted = true globalConfigEncrypted = true
} }
if env.IsSet(config.EnvAccessKeyOld) && env.IsSet(config.EnvSecretKeyOld) {
oldCred, err := auth.CreateCredentials(env.Get(config.EnvAccessKeyOld, ""), env.Get(config.EnvSecretKeyOld, ""))
if err != nil {
logger.Fatal(config.ErrInvalidCredentials(err),
"Unable to validate the old credentials inherited from the shell environment")
}
globalOldCred = oldCred
os.Unsetenv(config.EnvAccessKeyOld)
os.Unsetenv(config.EnvSecretKeyOld)
}
globalWORMEnabled, err = config.LookupWorm() globalWORMEnabled, err = config.LookupWorm()
if err != nil { if err != nil {
logger.Fatal(config.ErrInvalidWormValue(err), "Invalid worm configuration") logger.Fatal(config.ErrInvalidWormValue(err), "Invalid worm configuration")
} }
} }
func logStartupMessage(msg string) { func logStartupMessage(msg string) {

@ -21,7 +21,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
@ -29,7 +28,6 @@ import (
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/env"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
) )
@ -88,11 +86,6 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer, server bool) error {
} }
} }
activeCredOld, err := getOldCreds()
if err != nil {
return err
}
doneCh = make(chan struct{}) doneCh = make(chan struct{})
defer close(doneCh) defer close(doneCh)
@ -106,7 +99,7 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer, server bool) error {
select { select {
case <-retryTimerCh: case <-retryTimerCh:
// Migrate IAM configuration // Migrate IAM configuration
if err = migrateConfigPrefixToEncrypted(objAPI, activeCredOld, encrypted); err != nil { if err = migrateConfigPrefixToEncrypted(objAPI, globalOldCred, encrypted); err != nil {
if err == errDiskNotFound || if err == errDiskNotFound ||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) || strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) { strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
@ -164,21 +157,6 @@ func decryptData(edata []byte, creds ...auth.Credentials) ([]byte, error) {
return data, err return data, err
} }
func getOldCreds() (activeCredOld auth.Credentials, err error) {
accessKeyOld := env.Get(config.EnvAccessKeyOld, "")
secretKeyOld := env.Get(config.EnvSecretKeyOld, "")
if accessKeyOld != "" && secretKeyOld != "" {
activeCredOld, err = auth.CreateCredentials(accessKeyOld, secretKeyOld)
if err != nil {
return activeCredOld, err
}
// Once we have obtained the rotating creds
os.Unsetenv(config.EnvAccessKeyOld)
os.Unsetenv(config.EnvSecretKeyOld)
}
return activeCredOld, nil
}
func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error { func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel() defer cancel()
@ -206,20 +184,15 @@ func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
} }
} }
activeCredOld, err := getOldCreds()
if err != nil {
return err
}
if encrypted { if encrypted {
// No key rotation requested, and backend is // No key rotation requested, and backend is
// already encrypted. We proceed without migration. // already encrypted. We proceed without migration.
if !activeCredOld.IsValid() { if !globalOldCred.IsValid() {
return nil return nil
} }
// No real reason to rotate if old and new creds are same. // No real reason to rotate if old and new creds are same.
if activeCredOld.Equal(globalActiveCred) { if globalOldCred.Equal(globalActiveCred) {
return nil return nil
} }
@ -254,8 +227,8 @@ func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
var data []byte var data []byte
// Is rotating of creds requested? // Is rotating of creds requested?
if activeCredOld.IsValid() { if globalOldCred.IsValid() {
data, err = decryptData(cdata, activeCredOld, globalActiveCred) data, err = decryptData(cdata, globalOldCred, globalActiveCred)
if err != nil { if err != nil {
if err == madmin.ErrMaliciousData { if err == madmin.ErrMaliciousData {
return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil) return config.ErrInvalidRotatingCredentialsBackendEncrypted(nil)
@ -285,7 +258,7 @@ func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
} }
} }
if encrypted && globalActiveCred.IsValid() && activeCredOld.IsValid() { if encrypted && globalActiveCred.IsValid() && globalOldCred.IsValid() {
logger.Info("Rotation complete, please make sure to unset MINIO_ACCESS_KEY_OLD and MINIO_SECRET_KEY_OLD envs") logger.Info("Rotation complete, please make sure to unset MINIO_ACCESS_KEY_OLD and MINIO_SECRET_KEY_OLD envs")
} }

@ -193,6 +193,9 @@ var (
globalActiveCred auth.Credentials globalActiveCred auth.Credentials
// Hold the old server credentials passed by the environment
globalOldCred auth.Credentials
// Indicates if config is to be encrypted // Indicates if config is to be encrypted
globalConfigEncrypted bool globalConfigEncrypted bool

@ -28,6 +28,7 @@ import (
etcd "github.com/coreos/etcd/clientv3" etcd "github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/mvcc/mvccpb" "github.com/coreos/etcd/mvcc/mvccpb"
jwtgo "github.com/dgrijalva/jwt-go"
"github.com/minio/minio-go/v6/pkg/set" "github.com/minio/minio-go/v6/pkg/set"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
@ -174,7 +175,11 @@ func (ies *IAMEtcdStore) migrateUsersConfigToV1(isSTS bool) error {
// 2. copy policy to new loc. // 2. copy policy to new loc.
mp := newMappedPolicy(policyName) mp := newMappedPolicy(policyName)
path := getMappedPolicyPath(user, isSTS, false) userType := regularUser
if isSTS {
userType = stsUser
}
path := getMappedPolicyPath(user, userType, false)
if err := ies.saveIAMConfig(mp, path); err != nil { if err := ies.saveIAMConfig(mp, path); err != nil {
return err return err
} }
@ -300,9 +305,9 @@ func (ies *IAMEtcdStore) loadPolicyDocs(m map[string]iampolicy.Policy) error {
return nil return nil
} }
func (ies *IAMEtcdStore) loadUser(user string, isSTS bool, m map[string]auth.Credentials) error { func (ies *IAMEtcdStore) loadUser(user string, userType IAMUserType, m map[string]auth.Credentials) error {
var u UserIdentity var u UserIdentity
err := ies.loadIAMConfig(&u, getUserIdentityPath(user, isSTS)) err := ies.loadIAMConfig(&u, getUserIdentityPath(user, userType))
if err != nil { if err != nil {
if err == errConfigNotFound { if err == errConfigNotFound {
return errNoSuchUser return errNoSuchUser
@ -313,11 +318,31 @@ func (ies *IAMEtcdStore) loadUser(user string, isSTS bool, m map[string]auth.Cre
if u.Credentials.IsExpired() { if u.Credentials.IsExpired() {
// Delete expired identity. // Delete expired identity.
ctx := ies.getContext() ctx := ies.getContext()
deleteKeyEtcd(ctx, ies.client, getUserIdentityPath(user, isSTS)) deleteKeyEtcd(ctx, ies.client, getUserIdentityPath(user, userType))
deleteKeyEtcd(ctx, ies.client, getMappedPolicyPath(user, isSTS, false)) deleteKeyEtcd(ctx, ies.client, getMappedPolicyPath(user, userType, false))
return nil return nil
} }
// If this is a service account, rotate the session key if we are changing the server creds
if globalOldCred.IsValid() && u.Credentials.IsServiceAccount() {
if !globalOldCred.Equal(globalActiveCred) {
m := jwtgo.MapClaims{}
stsTokenCallback := func(t *jwtgo.Token) (interface{}, error) {
return []byte(globalOldCred.SecretKey), nil
}
if _, err := jwtgo.ParseWithClaims(u.Credentials.SessionToken, m, stsTokenCallback); err == nil {
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m))
if token, err := jwt.SignedString([]byte(globalActiveCred.SecretKey)); err == nil {
u.Credentials.SessionToken = token
err := ies.saveIAMConfig(&u, getUserIdentityPath(user, userType))
if err != nil {
return err
}
}
}
}
}
if u.Credentials.AccessKey == "" { if u.Credentials.AccessKey == "" {
u.Credentials.AccessKey = user u.Credentials.AccessKey = user
} }
@ -326,10 +351,15 @@ func (ies *IAMEtcdStore) loadUser(user string, isSTS bool, m map[string]auth.Cre
} }
func (ies *IAMEtcdStore) loadUsers(isSTS bool, m map[string]auth.Credentials) error { func (ies *IAMEtcdStore) loadUsers(userType IAMUserType, m map[string]auth.Credentials) error {
basePrefix := iamConfigUsersPrefix var basePrefix string
if isSTS { switch userType {
case srvAccUser:
basePrefix = iamConfigServiceAccountsPrefix
case stsUser:
basePrefix = iamConfigSTSPrefix basePrefix = iamConfigSTSPrefix
default:
basePrefix = iamConfigUsersPrefix
} }
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
@ -345,7 +375,7 @@ func (ies *IAMEtcdStore) loadUsers(isSTS bool, m map[string]auth.Credentials) er
// Reload config for all users. // Reload config for all users.
for _, user := range users.ToSlice() { for _, user := range users.ToSlice() {
if err = ies.loadUser(user, isSTS, m); err != nil { if err = ies.loadUser(user, userType, m); err != nil {
return err return err
} }
} }
@ -388,9 +418,9 @@ func (ies *IAMEtcdStore) loadGroups(m map[string]GroupInfo) error {
} }
func (ies *IAMEtcdStore) loadMappedPolicy(name string, isSTS, isGroup bool, m map[string]MappedPolicy) error { func (ies *IAMEtcdStore) loadMappedPolicy(name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
var p MappedPolicy var p MappedPolicy
err := ies.loadIAMConfig(&p, getMappedPolicyPath(name, isSTS, isGroup)) err := ies.loadIAMConfig(&p, getMappedPolicyPath(name, userType, isGroup))
if err != nil { if err != nil {
if err == errConfigNotFound { if err == errConfigNotFound {
return errNoSuchPolicy return errNoSuchPolicy
@ -402,19 +432,23 @@ func (ies *IAMEtcdStore) loadMappedPolicy(name string, isSTS, isGroup bool, m ma
} }
func (ies *IAMEtcdStore) loadMappedPolicies(isSTS, isGroup bool, m map[string]MappedPolicy) error { func (ies *IAMEtcdStore) loadMappedPolicies(userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel() defer cancel()
ies.setContext(ctx) ies.setContext(ctx)
defer ies.clearContext() defer ies.clearContext()
var basePrefix string var basePrefix string
switch { if isGroup {
case isSTS:
basePrefix = iamConfigPolicyDBSTSUsersPrefix
case isGroup:
basePrefix = iamConfigPolicyDBGroupsPrefix basePrefix = iamConfigPolicyDBGroupsPrefix
default: } else {
basePrefix = iamConfigPolicyDBUsersPrefix switch userType {
case srvAccUser:
basePrefix = iamConfigPolicyDBServiceAccountsPrefix
case stsUser:
basePrefix = iamConfigPolicyDBSTSUsersPrefix
default:
basePrefix = iamConfigPolicyDBUsersPrefix
}
} }
r, err := ies.client.Get(ctx, basePrefix, etcd.WithPrefix(), etcd.WithKeysOnly()) r, err := ies.client.Get(ctx, basePrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if err != nil { if err != nil {
@ -425,7 +459,7 @@ func (ies *IAMEtcdStore) loadMappedPolicies(isSTS, isGroup bool, m map[string]Ma
// Reload config and policies for all users. // Reload config and policies for all users.
for _, user := range users.ToSlice() { for _, user := range users.ToSlice() {
if err = ies.loadMappedPolicy(user, isSTS, isGroup, m); err != nil { if err = ies.loadMappedPolicy(user, userType, isGroup, m); err != nil {
return err return err
} }
} }
@ -452,29 +486,32 @@ func (ies *IAMEtcdStore) loadAll(sys *IAMSys, objectAPI ObjectLayer) error {
} }
// load STS temp users // load STS temp users
if err := ies.loadUsers(true, iamUsersMap); err != nil { if err := ies.loadUsers(stsUser, iamUsersMap); err != nil {
return err return err
} }
if isMinIOUsersSys { if isMinIOUsersSys {
// load long term users // load long term users
if err := ies.loadUsers(false, iamUsersMap); err != nil { if err := ies.loadUsers(regularUser, iamUsersMap); err != nil {
return err
}
if err := ies.loadUsers(srvAccUser, iamUsersMap); err != nil {
return err return err
} }
if err := ies.loadGroups(iamGroupsMap); err != nil { if err := ies.loadGroups(iamGroupsMap); err != nil {
return err return err
} }
if err := ies.loadMappedPolicies(false, false, iamUserPolicyMap); err != nil { if err := ies.loadMappedPolicies(regularUser, false, iamUserPolicyMap); err != nil {
return err return err
} }
} }
// load STS policy mappings into the same map // load STS policy mappings into the same map
if err := ies.loadMappedPolicies(true, false, iamUserPolicyMap); err != nil { if err := ies.loadMappedPolicies(stsUser, false, iamUserPolicyMap); err != nil {
return err return err
} }
// load policies mapped to groups // load policies mapped to groups
if err := ies.loadMappedPolicies(false, true, iamGroupPolicyMap); err != nil { if err := ies.loadMappedPolicies(regularUser, true, iamGroupPolicyMap); err != nil {
return err return err
} }
@ -498,12 +535,12 @@ func (ies *IAMEtcdStore) savePolicyDoc(policyName string, p iampolicy.Policy) er
return ies.saveIAMConfig(&p, getPolicyDocPath(policyName)) return ies.saveIAMConfig(&p, getPolicyDocPath(policyName))
} }
func (ies *IAMEtcdStore) saveMappedPolicy(name string, isSTS, isGroup bool, mp MappedPolicy) error { func (ies *IAMEtcdStore) saveMappedPolicy(name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error {
return ies.saveIAMConfig(mp, getMappedPolicyPath(name, isSTS, isGroup)) return ies.saveIAMConfig(mp, getMappedPolicyPath(name, userType, isGroup))
} }
func (ies *IAMEtcdStore) saveUserIdentity(name string, isSTS bool, u UserIdentity) error { func (ies *IAMEtcdStore) saveUserIdentity(name string, userType IAMUserType, u UserIdentity) error {
return ies.saveIAMConfig(u, getUserIdentityPath(name, isSTS)) return ies.saveIAMConfig(u, getUserIdentityPath(name, userType))
} }
func (ies *IAMEtcdStore) saveGroupInfo(name string, gi GroupInfo) error { func (ies *IAMEtcdStore) saveGroupInfo(name string, gi GroupInfo) error {
@ -518,16 +555,16 @@ func (ies *IAMEtcdStore) deletePolicyDoc(name string) error {
return err return err
} }
func (ies *IAMEtcdStore) deleteMappedPolicy(name string, isSTS, isGroup bool) error { func (ies *IAMEtcdStore) deleteMappedPolicy(name string, userType IAMUserType, isGroup bool) error {
err := ies.deleteIAMConfig(getMappedPolicyPath(name, isSTS, isGroup)) err := ies.deleteIAMConfig(getMappedPolicyPath(name, userType, isGroup))
if err == errConfigNotFound { if err == errConfigNotFound {
err = errNoSuchPolicy err = errNoSuchPolicy
} }
return err return err
} }
func (ies *IAMEtcdStore) deleteUserIdentity(name string, isSTS bool) error { func (ies *IAMEtcdStore) deleteUserIdentity(name string, userType IAMUserType) error {
err := ies.deleteIAMConfig(getUserIdentityPath(name, isSTS)) err := ies.deleteIAMConfig(getUserIdentityPath(name, userType))
if err == errConfigNotFound { if err == errConfigNotFound {
err = errNoSuchUser err = errNoSuchUser
} }
@ -599,11 +636,11 @@ func (ies *IAMEtcdStore) reloadFromEvent(sys *IAMSys, event *etcd.Event) {
case usersPrefix: case usersPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key), accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigUsersPrefix)) iamConfigUsersPrefix))
ies.loadUser(accessKey, false, sys.iamUsersMap) ies.loadUser(accessKey, regularUser, sys.iamUsersMap)
case stsPrefix: case stsPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key), accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigSTSPrefix)) iamConfigSTSPrefix))
ies.loadUser(accessKey, true, sys.iamUsersMap) ies.loadUser(accessKey, stsUser, sys.iamUsersMap)
case groupsPrefix: case groupsPrefix:
group := path.Dir(strings.TrimPrefix(string(event.Kv.Key), group := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigGroupsPrefix)) iamConfigGroupsPrefix))
@ -619,17 +656,17 @@ func (ies *IAMEtcdStore) reloadFromEvent(sys *IAMSys, event *etcd.Event) {
policyMapFile := strings.TrimPrefix(string(event.Kv.Key), policyMapFile := strings.TrimPrefix(string(event.Kv.Key),
iamConfigPolicyDBUsersPrefix) iamConfigPolicyDBUsersPrefix)
user := strings.TrimSuffix(policyMapFile, ".json") user := strings.TrimSuffix(policyMapFile, ".json")
ies.loadMappedPolicy(user, false, false, sys.iamUserPolicyMap) ies.loadMappedPolicy(user, regularUser, false, sys.iamUserPolicyMap)
case policyDBSTSUsersPrefix: case policyDBSTSUsersPrefix:
policyMapFile := strings.TrimPrefix(string(event.Kv.Key), policyMapFile := strings.TrimPrefix(string(event.Kv.Key),
iamConfigPolicyDBSTSUsersPrefix) iamConfigPolicyDBSTSUsersPrefix)
user := strings.TrimSuffix(policyMapFile, ".json") user := strings.TrimSuffix(policyMapFile, ".json")
ies.loadMappedPolicy(user, true, false, sys.iamUserPolicyMap) ies.loadMappedPolicy(user, stsUser, false, sys.iamUserPolicyMap)
case policyDBGroupsPrefix: case policyDBGroupsPrefix:
policyMapFile := strings.TrimPrefix(string(event.Kv.Key), policyMapFile := strings.TrimPrefix(string(event.Kv.Key),
iamConfigPolicyDBGroupsPrefix) iamConfigPolicyDBGroupsPrefix)
user := strings.TrimSuffix(policyMapFile, ".json") user := strings.TrimSuffix(policyMapFile, ".json")
ies.loadMappedPolicy(user, false, true, sys.iamGroupPolicyMap) ies.loadMappedPolicy(user, regularUser, true, sys.iamGroupPolicyMap)
} }
case eventDelete: case eventDelete:
switch { switch {

@ -25,6 +25,8 @@ import (
"sync" "sync"
"time" "time"
jwtgo "github.com/dgrijalva/jwt-go"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
iampolicy "github.com/minio/minio/pkg/iam/policy" iampolicy "github.com/minio/minio/pkg/iam/policy"
@ -116,7 +118,11 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(isSTS bool) error {
// 2. copy policy file to new location. // 2. copy policy file to new location.
mp := newMappedPolicy(policyName) mp := newMappedPolicy(policyName)
if err := iamOS.saveMappedPolicy(user, isSTS, false, mp); err != nil { userType := regularUser
if isSTS {
userType = stsUser
}
if err := iamOS.saveMappedPolicy(user, userType, false, mp); err != nil {
return err return err
} }
@ -289,14 +295,14 @@ func (iamOS *IAMObjectStore) loadPolicyDocs(m map[string]iampolicy.Policy) error
return nil return nil
} }
func (iamOS *IAMObjectStore) loadUser(user string, isSTS bool, m map[string]auth.Credentials) error { func (iamOS *IAMObjectStore) loadUser(user string, userType IAMUserType, m map[string]auth.Credentials) error {
objectAPI := iamOS.getObjectAPI() objectAPI := iamOS.getObjectAPI()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
var u UserIdentity var u UserIdentity
err := iamOS.loadIAMConfig(&u, getUserIdentityPath(user, isSTS)) err := iamOS.loadIAMConfig(&u, getUserIdentityPath(user, userType))
if err != nil { if err != nil {
if err == errConfigNotFound { if err == errConfigNotFound {
return errNoSuchUser return errNoSuchUser
@ -306,11 +312,31 @@ func (iamOS *IAMObjectStore) loadUser(user string, isSTS bool, m map[string]auth
if u.Credentials.IsExpired() { if u.Credentials.IsExpired() {
// Delete expired identity - ignoring errors here. // Delete expired identity - ignoring errors here.
iamOS.deleteIAMConfig(getUserIdentityPath(user, isSTS)) iamOS.deleteIAMConfig(getUserIdentityPath(user, userType))
iamOS.deleteIAMConfig(getMappedPolicyPath(user, isSTS, false)) iamOS.deleteIAMConfig(getMappedPolicyPath(user, userType, false))
return nil return nil
} }
// If this is a service account, rotate the session key if needed
if globalOldCred.IsValid() && u.Credentials.IsServiceAccount() {
if !globalOldCred.Equal(globalActiveCred) {
m := jwtgo.MapClaims{}
stsTokenCallback := func(t *jwtgo.Token) (interface{}, error) {
return []byte(globalOldCred.SecretKey), nil
}
if _, err := jwtgo.ParseWithClaims(u.Credentials.SessionToken, m, stsTokenCallback); err == nil {
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m))
if token, err := jwt.SignedString([]byte(globalActiveCred.SecretKey)); err == nil {
u.Credentials.SessionToken = token
err := iamOS.saveIAMConfig(&u, getUserIdentityPath(user, userType))
if err != nil {
return err
}
}
}
}
}
if u.Credentials.AccessKey == "" { if u.Credentials.AccessKey == "" {
u.Credentials.AccessKey = user u.Credentials.AccessKey = user
} }
@ -318,7 +344,7 @@ func (iamOS *IAMObjectStore) loadUser(user string, isSTS bool, m map[string]auth
return nil return nil
} }
func (iamOS *IAMObjectStore) loadUsers(isSTS bool, m map[string]auth.Credentials) error { func (iamOS *IAMObjectStore) loadUsers(userType IAMUserType, m map[string]auth.Credentials) error {
objectAPI := iamOS.getObjectAPI() objectAPI := iamOS.getObjectAPI()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
@ -326,17 +352,24 @@ func (iamOS *IAMObjectStore) loadUsers(isSTS bool, m map[string]auth.Credentials
doneCh := make(chan struct{}) doneCh := make(chan struct{})
defer close(doneCh) defer close(doneCh)
basePrefix := iamConfigUsersPrefix
if isSTS { var basePrefix string
switch userType {
case srvAccUser:
basePrefix = iamConfigServiceAccountsPrefix
case stsUser:
basePrefix = iamConfigSTSPrefix basePrefix = iamConfigSTSPrefix
default:
basePrefix = iamConfigUsersPrefix
} }
for item := range listIAMConfigItems(objectAPI, basePrefix, true, doneCh) { for item := range listIAMConfigItems(objectAPI, basePrefix, true, doneCh) {
if item.Err != nil { if item.Err != nil {
return item.Err return item.Err
} }
userName := item.Item userName := item.Item
err := iamOS.loadUser(userName, isSTS, m) err := iamOS.loadUser(userName, userType, m)
if err != nil { if err != nil {
return err return err
} }
@ -384,7 +417,7 @@ func (iamOS *IAMObjectStore) loadGroups(m map[string]GroupInfo) error {
return nil return nil
} }
func (iamOS *IAMObjectStore) loadMappedPolicy(name string, isSTS, isGroup bool, func (iamOS *IAMObjectStore) loadMappedPolicy(name string, userType IAMUserType, isGroup bool,
m map[string]MappedPolicy) error { m map[string]MappedPolicy) error {
objectAPI := iamOS.getObjectAPI() objectAPI := iamOS.getObjectAPI()
@ -393,7 +426,7 @@ func (iamOS *IAMObjectStore) loadMappedPolicy(name string, isSTS, isGroup bool,
} }
var p MappedPolicy var p MappedPolicy
err := iamOS.loadIAMConfig(&p, getMappedPolicyPath(name, isSTS, isGroup)) err := iamOS.loadIAMConfig(&p, getMappedPolicyPath(name, userType, isGroup))
if err != nil { if err != nil {
if err == errConfigNotFound { if err == errConfigNotFound {
return errNoSuchPolicy return errNoSuchPolicy
@ -404,7 +437,7 @@ func (iamOS *IAMObjectStore) loadMappedPolicy(name string, isSTS, isGroup bool,
return nil return nil
} }
func (iamOS *IAMObjectStore) loadMappedPolicies(isSTS, isGroup bool, m map[string]MappedPolicy) error { func (iamOS *IAMObjectStore) loadMappedPolicies(userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
objectAPI := iamOS.getObjectAPI() objectAPI := iamOS.getObjectAPI()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
@ -413,13 +446,17 @@ func (iamOS *IAMObjectStore) loadMappedPolicies(isSTS, isGroup bool, m map[strin
doneCh := make(chan struct{}) doneCh := make(chan struct{})
defer close(doneCh) defer close(doneCh)
var basePath string var basePath string
switch { if isGroup {
case isSTS:
basePath = iamConfigPolicyDBSTSUsersPrefix
case isGroup:
basePath = iamConfigPolicyDBGroupsPrefix basePath = iamConfigPolicyDBGroupsPrefix
default: } else {
basePath = iamConfigPolicyDBUsersPrefix switch userType {
case srvAccUser:
basePath = iamConfigPolicyDBServiceAccountsPrefix
case stsUser:
basePath = iamConfigPolicyDBSTSUsersPrefix
default:
basePath = iamConfigPolicyDBUsersPrefix
}
} }
for item := range listIAMConfigItems(objectAPI, basePath, false, doneCh) { for item := range listIAMConfigItems(objectAPI, basePath, false, doneCh) {
if item.Err != nil { if item.Err != nil {
@ -428,7 +465,7 @@ func (iamOS *IAMObjectStore) loadMappedPolicies(isSTS, isGroup bool, m map[strin
policyFile := item.Item policyFile := item.Item
userOrGroupName := strings.TrimSuffix(policyFile, ".json") userOrGroupName := strings.TrimSuffix(policyFile, ".json")
err := iamOS.loadMappedPolicy(userOrGroupName, isSTS, isGroup, m) err := iamOS.loadMappedPolicy(userOrGroupName, userType, isGroup, m)
if err != nil { if err != nil {
return err return err
} }
@ -466,27 +503,30 @@ func (iamOS *IAMObjectStore) loadAll(sys *IAMSys, objectAPI ObjectLayer) error {
return err return err
} }
// load STS temp users // load STS temp users
if err := iamOS.loadUsers(true, iamUsersMap); err != nil { if err := iamOS.loadUsers(stsUser, iamUsersMap); err != nil {
return err return err
} }
if isMinIOUsersSys { if isMinIOUsersSys {
if err := iamOS.loadUsers(false, iamUsersMap); err != nil { if err := iamOS.loadUsers(regularUser, iamUsersMap); err != nil {
return err
}
if err := iamOS.loadUsers(srvAccUser, iamUsersMap); err != nil {
return err return err
} }
if err := iamOS.loadGroups(iamGroupsMap); err != nil { if err := iamOS.loadGroups(iamGroupsMap); err != nil {
return err return err
} }
if err := iamOS.loadMappedPolicies(false, false, iamUserPolicyMap); err != nil { if err := iamOS.loadMappedPolicies(regularUser, false, iamUserPolicyMap); err != nil {
return err return err
} }
} }
// load STS policy mappings // load STS policy mappings
if err := iamOS.loadMappedPolicies(true, false, iamUserPolicyMap); err != nil { if err := iamOS.loadMappedPolicies(stsUser, false, iamUserPolicyMap); err != nil {
return err return err
} }
// load policies mapped to groups // load policies mapped to groups
if err := iamOS.loadMappedPolicies(false, true, iamGroupPolicyMap); err != nil { if err := iamOS.loadMappedPolicies(regularUser, true, iamGroupPolicyMap); err != nil {
return err return err
} }
@ -510,12 +550,12 @@ func (iamOS *IAMObjectStore) savePolicyDoc(policyName string, p iampolicy.Policy
return iamOS.saveIAMConfig(&p, getPolicyDocPath(policyName)) return iamOS.saveIAMConfig(&p, getPolicyDocPath(policyName))
} }
func (iamOS *IAMObjectStore) saveMappedPolicy(name string, isSTS, isGroup bool, mp MappedPolicy) error { func (iamOS *IAMObjectStore) saveMappedPolicy(name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error {
return iamOS.saveIAMConfig(mp, getMappedPolicyPath(name, isSTS, isGroup)) return iamOS.saveIAMConfig(mp, getMappedPolicyPath(name, userType, isGroup))
} }
func (iamOS *IAMObjectStore) saveUserIdentity(name string, isSTS bool, u UserIdentity) error { func (iamOS *IAMObjectStore) saveUserIdentity(name string, userType IAMUserType, u UserIdentity) error {
return iamOS.saveIAMConfig(u, getUserIdentityPath(name, isSTS)) return iamOS.saveIAMConfig(u, getUserIdentityPath(name, userType))
} }
func (iamOS *IAMObjectStore) saveGroupInfo(name string, gi GroupInfo) error { func (iamOS *IAMObjectStore) saveGroupInfo(name string, gi GroupInfo) error {
@ -530,16 +570,16 @@ func (iamOS *IAMObjectStore) deletePolicyDoc(name string) error {
return err return err
} }
func (iamOS *IAMObjectStore) deleteMappedPolicy(name string, isSTS, isGroup bool) error { func (iamOS *IAMObjectStore) deleteMappedPolicy(name string, userType IAMUserType, isGroup bool) error {
err := iamOS.deleteIAMConfig(getMappedPolicyPath(name, isSTS, isGroup)) err := iamOS.deleteIAMConfig(getMappedPolicyPath(name, userType, isGroup))
if err == errConfigNotFound { if err == errConfigNotFound {
err = errNoSuchPolicy err = errNoSuchPolicy
} }
return err return err
} }
func (iamOS *IAMObjectStore) deleteUserIdentity(name string, isSTS bool) error { func (iamOS *IAMObjectStore) deleteUserIdentity(name string, userType IAMUserType) error {
err := iamOS.deleteIAMConfig(getUserIdentityPath(name, isSTS)) err := iamOS.deleteIAMConfig(getUserIdentityPath(name, userType))
if err == errConfigNotFound { if err == errConfigNotFound {
err = errNoSuchUser err = errNoSuchUser
} }

@ -19,7 +19,9 @@ package cmd
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt"
"sync" "sync"
"github.com/minio/minio-go/v6/pkg/set" "github.com/minio/minio-go/v6/pkg/set"
@ -52,6 +54,9 @@ const (
// IAM users directory. // IAM users directory.
iamConfigUsersPrefix = iamConfigPrefix + "/users/" iamConfigUsersPrefix = iamConfigPrefix + "/users/"
// IAM service accounts directory.
iamConfigServiceAccountsPrefix = iamConfigPrefix + "/service-accounts/"
// IAM groups directory. // IAM groups directory.
iamConfigGroupsPrefix = iamConfigPrefix + "/groups/" iamConfigGroupsPrefix = iamConfigPrefix + "/groups/"
@ -62,10 +67,11 @@ const (
iamConfigSTSPrefix = iamConfigPrefix + "/sts/" iamConfigSTSPrefix = iamConfigPrefix + "/sts/"
// IAM Policy DB prefixes. // IAM Policy DB prefixes.
iamConfigPolicyDBPrefix = iamConfigPrefix + "/policydb/" iamConfigPolicyDBPrefix = iamConfigPrefix + "/policydb/"
iamConfigPolicyDBUsersPrefix = iamConfigPolicyDBPrefix + "users/" iamConfigPolicyDBUsersPrefix = iamConfigPolicyDBPrefix + "users/"
iamConfigPolicyDBSTSUsersPrefix = iamConfigPolicyDBPrefix + "sts-users/" iamConfigPolicyDBSTSUsersPrefix = iamConfigPolicyDBPrefix + "sts-users/"
iamConfigPolicyDBGroupsPrefix = iamConfigPolicyDBPrefix + "groups/" iamConfigPolicyDBServiceAccountsPrefix = iamConfigPolicyDBPrefix + "service-accounts/"
iamConfigPolicyDBGroupsPrefix = iamConfigPolicyDBPrefix + "groups/"
// IAM identity file which captures identity credentials. // IAM identity file which captures identity credentials.
iamIdentityFile = "identity.json" iamIdentityFile = "identity.json"
@ -99,10 +105,15 @@ func getIAMFormatFilePath() string {
return iamConfigPrefix + SlashSeparator + iamFormatFile return iamConfigPrefix + SlashSeparator + iamFormatFile
} }
func getUserIdentityPath(user string, isSTS bool) string { func getUserIdentityPath(user string, userType IAMUserType) string {
basePath := iamConfigUsersPrefix var basePath string
if isSTS { switch userType {
case srvAccUser:
basePath = iamConfigServiceAccountsPrefix
case stsUser:
basePath = iamConfigSTSPrefix basePath = iamConfigSTSPrefix
default:
basePath = iamConfigUsersPrefix
} }
return pathJoin(basePath, user, iamIdentityFile) return pathJoin(basePath, user, iamIdentityFile)
} }
@ -115,12 +126,15 @@ func getPolicyDocPath(name string) string {
return pathJoin(iamConfigPoliciesPrefix, name, iamPolicyFile) return pathJoin(iamConfigPoliciesPrefix, name, iamPolicyFile)
} }
func getMappedPolicyPath(name string, isSTS, isGroup bool) string { func getMappedPolicyPath(name string, userType IAMUserType, isGroup bool) string {
switch { if isGroup {
case isSTS:
return pathJoin(iamConfigPolicyDBSTSUsersPrefix, name+".json")
case isGroup:
return pathJoin(iamConfigPolicyDBGroupsPrefix, name+".json") return pathJoin(iamConfigPolicyDBGroupsPrefix, name+".json")
}
switch userType {
case srvAccUser:
return pathJoin(iamConfigPolicyDBServiceAccountsPrefix, name+".json")
case stsUser:
return pathJoin(iamConfigPolicyDBSTSUsersPrefix, name+".json")
default: default:
return pathJoin(iamConfigPolicyDBUsersPrefix, name+".json") return pathJoin(iamConfigPolicyDBUsersPrefix, name+".json")
} }
@ -180,6 +194,15 @@ type IAMSys struct {
store IAMStorageAPI store IAMStorageAPI
} }
// IAMUserType represents a user type inside MinIO server
type IAMUserType int
const (
regularUser IAMUserType = iota
stsUser
srvAccUser
)
// IAMStorageAPI defines an interface for the IAM persistence layer // IAMStorageAPI defines an interface for the IAM persistence layer
type IAMStorageAPI interface { type IAMStorageAPI interface {
migrateBackendFormat(ObjectLayer) error migrateBackendFormat(ObjectLayer) error
@ -187,14 +210,14 @@ type IAMStorageAPI interface {
loadPolicyDoc(policy string, m map[string]iampolicy.Policy) error loadPolicyDoc(policy string, m map[string]iampolicy.Policy) error
loadPolicyDocs(m map[string]iampolicy.Policy) error loadPolicyDocs(m map[string]iampolicy.Policy) error
loadUser(user string, isSTS bool, m map[string]auth.Credentials) error loadUser(user string, userType IAMUserType, m map[string]auth.Credentials) error
loadUsers(isSTS bool, m map[string]auth.Credentials) error loadUsers(userType IAMUserType, m map[string]auth.Credentials) error
loadGroup(group string, m map[string]GroupInfo) error loadGroup(group string, m map[string]GroupInfo) error
loadGroups(m map[string]GroupInfo) error loadGroups(m map[string]GroupInfo) error
loadMappedPolicy(name string, isSTS, isGroup bool, m map[string]MappedPolicy) error loadMappedPolicy(name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
loadMappedPolicies(isSTS, isGroup bool, m map[string]MappedPolicy) error loadMappedPolicies(userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
loadAll(*IAMSys, ObjectLayer) error loadAll(*IAMSys, ObjectLayer) error
@ -203,13 +226,13 @@ type IAMStorageAPI interface {
deleteIAMConfig(path string) error deleteIAMConfig(path string) error
savePolicyDoc(policyName string, p iampolicy.Policy) error savePolicyDoc(policyName string, p iampolicy.Policy) error
saveMappedPolicy(name string, isSTS, isGroup bool, mp MappedPolicy) error saveMappedPolicy(name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error
saveUserIdentity(name string, isSTS bool, u UserIdentity) error saveUserIdentity(name string, userType IAMUserType, u UserIdentity) error
saveGroupInfo(group string, gi GroupInfo) error saveGroupInfo(group string, gi GroupInfo) error
deletePolicyDoc(policyName string) error deletePolicyDoc(policyName string) error
deleteMappedPolicy(name string, isSTS, isGroup bool) error deleteMappedPolicy(name string, userType IAMUserType, isGroup bool) error
deleteUserIdentity(name string, isSTS bool) error deleteUserIdentity(name string, userType IAMUserType) error
deleteGroupInfo(name string) error deleteGroupInfo(name string) error
watch(*IAMSys) watch(*IAMSys)
@ -290,9 +313,9 @@ func (sys *IAMSys) LoadPolicyMapping(objAPI ObjectLayer, userOrGroup string, isG
if globalEtcdClient == nil { if globalEtcdClient == nil {
var err error var err error
if isGroup { if isGroup {
err = sys.store.loadMappedPolicy(userOrGroup, false, isGroup, sys.iamGroupPolicyMap) err = sys.store.loadMappedPolicy(userOrGroup, regularUser, isGroup, sys.iamGroupPolicyMap)
} else { } else {
err = sys.store.loadMappedPolicy(userOrGroup, false, isGroup, sys.iamUserPolicyMap) err = sys.store.loadMappedPolicy(userOrGroup, regularUser, isGroup, sys.iamUserPolicyMap)
} }
// Ignore policy not mapped error // Ignore policy not mapped error
@ -305,7 +328,7 @@ func (sys *IAMSys) LoadPolicyMapping(objAPI ObjectLayer, userOrGroup string, isG
} }
// LoadUser - reloads a specific user from backend disks or etcd. // LoadUser - reloads a specific user from backend disks or etcd.
func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, isSTS bool) error { func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, userType IAMUserType) error {
sys.Lock() sys.Lock()
defer sys.Unlock() defer sys.Unlock()
@ -314,11 +337,11 @@ func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, isSTS bool) er
} }
if globalEtcdClient == nil { if globalEtcdClient == nil {
err := sys.store.loadUser(accessKey, isSTS, sys.iamUsersMap) err := sys.store.loadUser(accessKey, userType, sys.iamUsersMap)
if err != nil { if err != nil {
return err return err
} }
err = sys.store.loadMappedPolicy(accessKey, isSTS, false, sys.iamUserPolicyMap) err = sys.store.loadMappedPolicy(accessKey, userType, false, sys.iamUserPolicyMap)
// Ignore policy not mapped error // Ignore policy not mapped error
if err != nil && err != errConfigNotFound { if err != nil && err != errConfigNotFound {
return err return err
@ -360,8 +383,12 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
} }
sys.store.watch(sys) sys.store.watch(sys)
err := sys.store.loadAll(sys, objAPI)
return sys.store.loadAll(sys, objAPI) // Invalidate the old cred after finishing IAM initialization
globalOldCred = auth.Credentials{}
return err
} }
// DeletePolicy - deletes a canned policy from backend or etcd. // DeletePolicy - deletes a canned policy from backend or etcd.
@ -393,7 +420,7 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
// Delete user-policy mappings that will no longer apply // Delete user-policy mappings that will no longer apply
var usersToDel []string var usersToDel []string
var isUserSTS []bool var usersType []IAMUserType
for u, mp := range sys.iamUserPolicyMap { for u, mp := range sys.iamUserPolicyMap {
if mp.Policy == policyName { if mp.Policy == policyName {
usersToDel = append(usersToDel, u) usersToDel = append(usersToDel, u)
@ -403,12 +430,15 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
return errNoSuchUser return errNoSuchUser
} }
// User is from STS if the creds are temporary // User is from STS if the creds are temporary
isSTS := cr.IsTemp() if cr.IsTemp() {
isUserSTS = append(isUserSTS, isSTS) usersType = append(usersType, stsUser)
} else {
usersType = append(usersType, regularUser)
}
} }
} }
for i, u := range usersToDel { for i, u := range usersToDel {
sys.policyDBSet(u, "", isUserSTS[i], false) sys.policyDBSet(u, "", usersType[i], false)
} }
// Delete group-policy mappings that will no longer apply // Delete group-policy mappings that will no longer apply
@ -419,7 +449,7 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
} }
} }
for _, g := range groupsToDel { for _, g := range groupsToDel {
sys.policyDBSet(g, "", false, true) sys.policyDBSet(g, "", regularUser, true)
} }
return err return err
@ -523,8 +553,8 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
} }
// 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, false, false) sys.store.deleteMappedPolicy(accessKey, regularUser, false)
err := sys.store.deleteUserIdentity(accessKey, false) err := sys.store.deleteUserIdentity(accessKey, regularUser)
switch err.(type) { switch err.(type) {
case ObjectNotFound: case ObjectNotFound:
// ignore if user is already deleted. // ignore if user is already deleted.
@ -534,6 +564,15 @@ 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
} }
@ -565,7 +604,7 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
} }
mp := newMappedPolicy(policyName) mp := newMappedPolicy(policyName)
if err := sys.store.saveMappedPolicy(accessKey, true, false, mp); err != nil { if err := sys.store.saveMappedPolicy(accessKey, stsUser, false, mp); err != nil {
return err return err
} }
@ -577,7 +616,7 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
} }
u := newUserIdentity(cred) u := newUserIdentity(cred)
if err := sys.store.saveUserIdentity(accessKey, true, u); err != nil { if err := sys.store.saveUserIdentity(accessKey, stsUser, u); err != nil {
return err return err
} }
@ -602,7 +641,7 @@ func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
} }
for k, v := range sys.iamUsersMap { for k, v := range sys.iamUsersMap {
if !v.IsTemp() { if !v.IsTemp() && !v.IsServiceAccount() {
users[k] = madmin.UserInfo{ users[k] = madmin.UserInfo{
PolicyName: sys.iamUserPolicyMap[k].Policy, PolicyName: sys.iamUserPolicyMap[k].Policy,
Status: func() madmin.AccountStatus { Status: func() madmin.AccountStatus {
@ -636,6 +675,28 @@ func (sys *IAMSys) IsTempUser(name string) (bool, error) {
return creds.IsTemp(), nil return creds.IsTemp(), nil
} }
// IsServiceAccount - returns if given key is a service account
func (sys *IAMSys) IsServiceAccount(name string) (bool, string, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil {
return false, "", errServerNotInitialized
}
sys.RLock()
defer sys.RUnlock()
creds, found := sys.iamUsersMap[name]
if !found {
return false, "", errNoSuchUser
}
if creds.IsServiceAccount() {
return true, creds.ParentUser, nil
}
return false, "", nil
}
// GetUserInfo - get info on a user. // GetUserInfo - get info on a user.
func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) { func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
objectAPI := newObjectLayerWithoutSafeModeFn() objectAPI := newObjectLayerWithoutSafeModeFn()
@ -717,7 +778,7 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
return errServerNotInitialized return errServerNotInitialized
} }
if err := sys.store.saveUserIdentity(accessKey, false, uinfo); err != nil { if err := sys.store.saveUserIdentity(accessKey, regularUser, uinfo); err != nil {
return err return err
} }
@ -725,6 +786,104 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
return nil return nil
} }
// NewServiceAccount - create a new service account
func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser, sessionPolicy string) (auth.Credentials, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil {
return auth.Credentials{}, errServerNotInitialized
}
if len(sessionPolicy) > 16*1024 {
return auth.Credentials{}, fmt.Errorf("Session policy should not exceed 16*1024 characters")
}
policy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicy)))
if err != nil {
return auth.Credentials{}, err
}
// Version in policy must not be empty
if policy.Version == "" {
return auth.Credentials{}, fmt.Errorf("Invalid session policy version")
}
sys.Lock()
defer sys.Unlock()
if sys.usersSysType != MinIOUsersSysType {
return auth.Credentials{}, errIAMActionNotAllowed
}
if sys.store == nil {
return auth.Credentials{}, errServerNotInitialized
}
if parentUser == globalActiveCred.AccessKey {
return auth.Credentials{}, errIAMActionNotAllowed
}
cr, ok := sys.iamUsersMap[parentUser]
if !ok {
return auth.Credentials{}, errNoSuchUser
}
if cr.IsTemp() {
return auth.Credentials{}, errIAMActionNotAllowed
}
m := make(map[string]interface{})
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicy))
m[parentClaim] = parentUser
m[iamPolicyClaimName()] = "embedded-policy"
secret := globalActiveCred.SecretKey
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
if err != nil {
return auth.Credentials{}, err
}
cred.ParentUser = parentUser
u := newUserIdentity(cred)
if err := sys.store.saveUserIdentity(u.Credentials.AccessKey, srvAccUser, u); err != nil {
return auth.Credentials{}, err
}
sys.iamUsersMap[u.Credentials.AccessKey] = u.Credentials
return cred, nil
}
// GetServiceAccount - returns the credentials of the given service account
func (sys *IAMSys) GetServiceAccount(ctx context.Context, serviceAccountAccessKey string) (auth.Credentials, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil {
return auth.Credentials{}, errServerNotInitialized
}
sys.Lock()
defer sys.Unlock()
if sys.usersSysType != MinIOUsersSysType {
return auth.Credentials{}, errIAMActionNotAllowed
}
if sys.store == nil {
return auth.Credentials{}, errServerNotInitialized
}
cr, ok := sys.iamUsersMap[serviceAccountAccessKey]
if !ok {
return auth.Credentials{}, errNoSuchUser
}
if !cr.IsServiceAccount() {
return auth.Credentials{}, errIAMActionNotAllowed
}
return cr, nil
}
// SetUser - set user credentials and policy. // SetUser - set user credentials and policy.
func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error { func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
objectAPI := newObjectLayerWithoutSafeModeFn() objectAPI := newObjectLayerWithoutSafeModeFn()
@ -754,7 +913,7 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
return errIAMActionNotAllowed return errIAMActionNotAllowed
} }
if err := sys.store.saveUserIdentity(accessKey, false, u); err != nil { if err := sys.store.saveUserIdentity(accessKey, regularUser, u); err != nil {
return err return err
} }
@ -762,7 +921,7 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
// Set policy if specified. // Set policy if specified.
if uinfo.PolicyName != "" { if uinfo.PolicyName != "" {
return sys.policyDBSet(accessKey, uinfo.PolicyName, false, false) return sys.policyDBSet(accessKey, uinfo.PolicyName, regularUser, false)
} }
return nil return nil
} }
@ -792,7 +951,7 @@ func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error {
cred.SecretKey = secretKey cred.SecretKey = secretKey
u := newUserIdentity(cred) u := newUserIdentity(cred)
if err := sys.store.saveUserIdentity(accessKey, false, u); err != nil { if err := sys.store.saveUserIdentity(accessKey, regularUser, u); err != nil {
return err return err
} }
@ -923,7 +1082,7 @@ func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
// Remove the group from storage. First delete the // Remove the group from storage. First delete the
// mapped policy. // mapped policy.
err := sys.store.deleteMappedPolicy(group, false, true) err := sys.store.deleteMappedPolicy(group, regularUser, true)
// No-mapped-policy case is ignored. // No-mapped-policy case is ignored.
if err != nil && err != errConfigNotFound { if err != nil && err != errConfigNotFound {
return err return err
@ -1068,12 +1227,12 @@ func (sys *IAMSys) PolicyDBSet(name, policy string, isGroup bool) error {
// isSTS is always false when called via PolicyDBSet as policy // isSTS is always false when called via PolicyDBSet as policy
// is never set by an external API call for STS users. // is never set by an external API call for STS users.
return sys.policyDBSet(name, policy, false, isGroup) return sys.policyDBSet(name, policy, regularUser, isGroup)
} }
// policyDBSet - sets a policy for user in the policy db. Assumes that caller // policyDBSet - sets a policy for user in the policy db. Assumes that caller
// has sys.Lock(). If policy == "", then policy mapping is removed. // has sys.Lock(). If policy == "", then policy mapping is removed.
func (sys *IAMSys) policyDBSet(name, policy string, isSTS, isGroup bool) error { func (sys *IAMSys) policyDBSet(name, policy string, userType IAMUserType, isGroup bool) error {
if sys.store == nil { if sys.store == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@ -1099,7 +1258,7 @@ func (sys *IAMSys) policyDBSet(name, policy string, isSTS, isGroup bool) error {
// Handle policy mapping removal // Handle policy mapping removal
if policy == "" { if policy == "" {
if err := sys.store.deleteMappedPolicy(name, isSTS, isGroup); err != nil { if err := sys.store.deleteMappedPolicy(name, userType, isGroup); err != nil {
return err return err
} }
if !isGroup { if !isGroup {
@ -1112,7 +1271,7 @@ func (sys *IAMSys) policyDBSet(name, policy string, isSTS, isGroup bool) error {
// Handle policy mapping set/update // Handle policy mapping set/update
mp := newMappedPolicy(policy) mp := newMappedPolicy(policy)
if err := sys.store.saveMappedPolicy(name, isSTS, isGroup, mp); err != nil { if err := sys.store.saveMappedPolicy(name, userType, isGroup, mp); err != nil {
return err return err
} }
if !isGroup { if !isGroup {
@ -1294,6 +1453,93 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
return result, nil return result, nil
} }
// IsAllowedServiceAccount - checks if the given service account is allowed to perform
// actions. The permission of the parent user is checked first
func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parent string) bool {
// Now check if we have a subject claim
p, ok := args.Claims[parentClaim]
if ok {
parentInClaim, ok := p.(string)
if !ok {
// Reject malformed/malicious requests.
return false
}
// The parent claim in the session token should be equal
// to the parent detected in the backend
if parentInClaim != parent {
return false
}
}
// Check if the parent is allowed to perform this action, reject if not
parentUserPolicies, err := sys.PolicyDBGet(parent, false)
if err != nil {
logger.LogIf(context.Background(), err)
return false
}
if len(parentUserPolicies) == 0 {
return false
}
var availablePolicies []iampolicy.Policy
// Policies were found, evaluate all of them.
sys.RLock()
for _, pname := range parentUserPolicies {
p, found := sys.iamPolicyDocsMap[pname]
if found {
availablePolicies = append(availablePolicies, p)
}
}
sys.RUnlock()
if len(availablePolicies) == 0 {
return false
}
combinedPolicy := availablePolicies[0]
for i := 1; i < len(availablePolicies); i++ {
combinedPolicy.Statements = append(combinedPolicy.Statements,
availablePolicies[i].Statements...)
}
serviceAcc := args.AccountName
args.AccountName = parent
if !combinedPolicy.IsAllowed(args) {
return false
}
args.AccountName = serviceAcc
// Now check if we have a sessionPolicy.
spolicy, ok := args.Claims[iampolicy.SessionPolicyName]
if ok {
spolicyStr, ok := spolicy.(string)
if !ok {
// Sub policy if set, should be a string reject
// malformed/malicious requests.
return false
}
// Check if policy is parseable.
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
if err != nil {
// Log any error in input session policy config.
logger.LogIf(context.Background(), err)
return false
}
// Policy without Version string value reject it.
if subPolicy.Version == "" {
return false
}
return subPolicy.IsAllowed(args)
}
return false
}
// IsAllowedSTS is meant for STS based temporary credentials, // IsAllowedSTS is meant for STS based temporary credentials,
// which implements claims validation and verification other than // which implements claims validation and verification other than
// applying policies. // applying policies.
@ -1444,6 +1690,17 @@ func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
return sys.IsAllowedSTS(args) return sys.IsAllowedSTS(args)
} }
// If the credential is for a service account, perform related check
ok, parentUser, err := sys.IsServiceAccount(args.AccountName)
if err != nil {
logger.LogIf(context.Background(), err)
return false
}
if ok {
return sys.IsAllowedServiceAccount(args, parentUser)
}
// Continue with the assumption of a regular user
policies, err := sys.PolicyDBGet(args.AccountName, false) policies, err := sys.PolicyDBGet(args.AccountName, false)
if err != nil { if err != nil {
logger.LogIf(context.Background(), err) logger.LogIf(context.Background(), err)

@ -325,7 +325,12 @@ func (s *peerRESTServer) LoadUserHandler(w http.ResponseWriter, r *http.Request)
return return
} }
if err = globalIAMSys.LoadUser(objAPI, accessKey, temp); err != nil { var userType = regularUser
if temp {
userType = stsUser
}
if err = globalIAMSys.LoadUser(objAPI, accessKey, userType); err != nil {
s.writeErrorResponse(w, err) s.writeErrorResponse(w, err)
return return
} }

@ -58,6 +58,9 @@ const (
expClaim = "exp" expClaim = "exp"
subClaim = "sub" subClaim = "sub"
// JWT claim to check the parent user
parentClaim = "parent"
// LDAP claim keys // LDAP claim keys
ldapUser = "ldapUser" ldapUser = "ldapUser"
ldapGroups = "ldapGroups" ldapGroups = "ldapGroups"

@ -90,6 +90,7 @@ type Credentials struct {
Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"` Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"`
SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"` SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"`
Status string `xml:"-" json:"status,omitempty"` Status string `xml:"-" json:"status,omitempty"`
ParentUser string `xml:"-" json:"parentUser,omitempty"`
} }
func (cred Credentials) String() string { func (cred Credentials) String() string {
@ -119,7 +120,12 @@ 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 != "" return cred.SessionToken != "" && cred.ParentUser == ""
}
// IsServiceAccount - returns whether credential is a service account or not
func (cred Credentials) IsServiceAccount() bool {
return cred.ParentUser != ""
} }
// IsValid - returns whether credential is valid or not. // IsValid - returns whether credential is valid or not.
@ -207,14 +213,15 @@ func GetNewCredentialsWithMetadata(m map[string]interface{}, tokenSecret string)
"/", "+", -1) "/", "+", -1)
cred.Status = "on" cred.Status = "on"
if tokenSecret == "" {
cred.Expiration = timeSentinel
return cred, nil
}
expiry, err := ExpToInt64(m["exp"]) expiry, err := ExpToInt64(m["exp"])
if err != nil { if err != nil {
return cred, err return cred, err
} }
if expiry == 0 {
cred.Expiration = timeSentinel
return cred, nil
}
m["accessKey"] = cred.AccessKey m["accessKey"] = cred.AccessKey
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m)) jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m))

@ -0,0 +1,52 @@
// +build ignore
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package main
import (
"fmt"
"log"
"github.com/minio/minio/pkg/madmin"
)
func main() {
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
// dummy values, please replace them with original values.
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
// dummy values, please replace them with original values.
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
// New returns an MinIO Admin client object.
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
if err != nil {
log.Fatalln(err)
}
// Create policy
policy := `{"Version": "2012-10-17","Statement": [{"Action": ["s3:GetObject"],"Effect": "Allow","Resource": ["arn:aws:s3:::testbucket/*"],"Sid": ""}]}`
creds, err := madmClnt.AddServiceAccount("parentuser", policy)
if err != nil {
log.Fatalln(err)
}
fmt.Println(creds)
}

@ -0,0 +1,49 @@
// +build ignore
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package main
import (
"fmt"
"log"
"github.com/minio/minio/pkg/madmin"
)
func main() {
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
// dummy values, please replace them with original values.
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
// dummy values, please replace them with original values.
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
// New returns an MinIO Admin client object.
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
if err != nil {
log.Fatalln(err)
}
creds, err := madmClnt.GetServiceAccount("service-account-access-key")
if err != nil {
log.Fatalln(err)
}
fmt.Println(creds)
}

@ -210,3 +210,101 @@ func (adm *AdminClient) SetUserStatus(accessKey string, status AccountStatus) er
return nil return nil
} }
// AddServiceAccountReq is the request body of the add service account admin call
type AddServiceAccountReq struct {
Parent string `json:"parent"`
Policy string `json:"policy"`
}
// AddServiceAccountResp is the response body of the add service account admin call
type AddServiceAccountResp struct {
Credentials auth.Credentials `json:"credentials"`
}
// AddServiceAccount - creates a new service account belonging to the given parent user
// while restricting the service account permission by the given policy document.
func (adm *AdminClient) AddServiceAccount(parentUser string, policy string) (auth.Credentials, error) {
if !auth.IsAccessKeyValid(parentUser) {
return auth.Credentials{}, auth.ErrInvalidAccessKeyLength
}
data, err := json.Marshal(AddServiceAccountReq{
Parent: parentUser,
Policy: policy,
})
if err != nil {
return auth.Credentials{}, err
}
econfigBytes, err := EncryptData(adm.secretAccessKey, data)
if err != nil {
return auth.Credentials{}, err
}
reqData := requestData{
relPath: adminAPIPrefix + "/add-service-account",
content: econfigBytes,
}
// Execute PUT on /minio/admin/v2/add-service-account to set a user.
resp, err := adm.executeMethod("PUT", reqData)
defer closeResponse(resp)
if err != nil {
return auth.Credentials{}, err
}
if resp.StatusCode != http.StatusOK {
return auth.Credentials{}, httpRespToErrorResponse(resp)
}
data, err = DecryptData(adm.secretAccessKey, resp.Body)
if err != nil {
return auth.Credentials{}, err
}
var serviceAccountResp AddServiceAccountResp
if err = json.Unmarshal(data, &serviceAccountResp); err != nil {
return auth.Credentials{}, err
}
return serviceAccountResp.Credentials, nil
}
// GetServiceAccount returns the credential of the service account
func (adm *AdminClient) GetServiceAccount(serviceAccountAccessKey string) (auth.Credentials, error) {
if !auth.IsAccessKeyValid(serviceAccountAccessKey) {
return auth.Credentials{}, auth.ErrInvalidAccessKeyLength
}
queryValues := url.Values{}
queryValues.Set("accessKey", serviceAccountAccessKey)
reqData := requestData{
relPath: adminAPIPrefix + "/get-service-account",
queryValues: queryValues,
}
// Execute GET on /minio/admin/v2/get-service-account to set a user.
resp, err := adm.executeMethod("GET", reqData)
defer closeResponse(resp)
if err != nil {
return auth.Credentials{}, err
}
if resp.StatusCode != http.StatusOK {
return auth.Credentials{}, httpRespToErrorResponse(resp)
}
data, err := DecryptData(adm.secretAccessKey, resp.Body)
if err != nil {
return auth.Credentials{}, err
}
var creds auth.Credentials
if err = json.Unmarshal(data, &creds); err != nil {
return auth.Credentials{}, err
}
return creds, nil
}

Loading…
Cancel
Save