mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
Add service account type in IAM (#9029)
This commit is contained in:
parent
8b880a246a
commit
496f4a7dc7
@ -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}
|
||||
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
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)).
|
||||
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
|
||||
adminRouter.Methods(http.MethodGet).Path(adminAPIVersionPrefix+"/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")
|
||||
|
||||
|
@ -332,6 +332,7 @@ const (
|
||||
ErrAdminProfilerNotEnabled
|
||||
ErrInvalidDecompressedSize
|
||||
ErrAddUserInvalidArgument
|
||||
ErrAddServiceAccountInvalidArgument
|
||||
ErrPostPolicyConditionInvalidFormat
|
||||
)
|
||||
|
||||
@ -1573,6 +1574,12 @@ var errorCodes = errorCodeMap{
|
||||
Description: "User is not allowed to be same as admin access key",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
ErrAddServiceAccountInvalidArgument: {
|
||||
Code: "XMinioInvalidArgument",
|
||||
Description: "New service accounts for admin access key is not allowed",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
|
||||
ErrPostPolicyConditionInvalidFormat: {
|
||||
Code: "PostPolicyInvalidKeyName",
|
||||
Description: "Invalid according to Policy: Policy Condition failed",
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@ -226,10 +227,22 @@ func handleCommonEnvVars() {
|
||||
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()
|
||||
if err != nil {
|
||||
logger.Fatal(config.ErrInvalidWormValue(err), "Invalid worm configuration")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func logStartupMessage(msg string) {
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
@ -29,7 +28,6 @@ import (
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
"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{})
|
||||
defer close(doneCh)
|
||||
|
||||
@ -106,7 +99,7 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer, server bool) error {
|
||||
select {
|
||||
case <-retryTimerCh:
|
||||
// Migrate IAM configuration
|
||||
if err = migrateConfigPrefixToEncrypted(objAPI, activeCredOld, encrypted); err != nil {
|
||||
if err = migrateConfigPrefixToEncrypted(objAPI, globalOldCred, encrypted); err != nil {
|
||||
if err == errDiskNotFound ||
|
||||
strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) ||
|
||||
strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) {
|
||||
@ -164,21 +157,6 @@ func decryptData(edata []byte, creds ...auth.Credentials) ([]byte, error) {
|
||||
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 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||
defer cancel()
|
||||
@ -206,20 +184,15 @@ func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
|
||||
}
|
||||
}
|
||||
|
||||
activeCredOld, err := getOldCreds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if encrypted {
|
||||
// No key rotation requested, and backend is
|
||||
// already encrypted. We proceed without migration.
|
||||
if !activeCredOld.IsValid() {
|
||||
if !globalOldCred.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// No real reason to rotate if old and new creds are same.
|
||||
if activeCredOld.Equal(globalActiveCred) {
|
||||
if globalOldCred.Equal(globalActiveCred) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -254,8 +227,8 @@ func migrateIAMConfigsEtcdToEncrypted(client *etcd.Client) error {
|
||||
|
||||
var data []byte
|
||||
// Is rotating of creds requested?
|
||||
if activeCredOld.IsValid() {
|
||||
data, err = decryptData(cdata, activeCredOld, globalActiveCred)
|
||||
if globalOldCred.IsValid() {
|
||||
data, err = decryptData(cdata, globalOldCred, globalActiveCred)
|
||||
if err != nil {
|
||||
if err == madmin.ErrMaliciousData {
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -193,6 +193,9 @@ var (
|
||||
|
||||
globalActiveCred auth.Credentials
|
||||
|
||||
// Hold the old server credentials passed by the environment
|
||||
globalOldCred auth.Credentials
|
||||
|
||||
// Indicates if config is to be encrypted
|
||||
globalConfigEncrypted bool
|
||||
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
|
||||
etcd "github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
@ -174,7 +175,11 @@ func (ies *IAMEtcdStore) migrateUsersConfigToV1(isSTS bool) error {
|
||||
|
||||
// 2. copy policy to new loc.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -300,9 +305,9 @@ func (ies *IAMEtcdStore) loadPolicyDocs(m map[string]iampolicy.Policy) error {
|
||||
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
|
||||
err := ies.loadIAMConfig(&u, getUserIdentityPath(user, isSTS))
|
||||
err := ies.loadIAMConfig(&u, getUserIdentityPath(user, userType))
|
||||
if err != nil {
|
||||
if err == errConfigNotFound {
|
||||
return errNoSuchUser
|
||||
@ -313,11 +318,31 @@ func (ies *IAMEtcdStore) loadUser(user string, isSTS bool, m map[string]auth.Cre
|
||||
if u.Credentials.IsExpired() {
|
||||
// Delete expired identity.
|
||||
ctx := ies.getContext()
|
||||
deleteKeyEtcd(ctx, ies.client, getUserIdentityPath(user, isSTS))
|
||||
deleteKeyEtcd(ctx, ies.client, getMappedPolicyPath(user, isSTS, false))
|
||||
deleteKeyEtcd(ctx, ies.client, getUserIdentityPath(user, userType))
|
||||
deleteKeyEtcd(ctx, ies.client, getMappedPolicyPath(user, userType, false))
|
||||
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 == "" {
|
||||
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 {
|
||||
basePrefix := iamConfigUsersPrefix
|
||||
if isSTS {
|
||||
func (ies *IAMEtcdStore) loadUsers(userType IAMUserType, m map[string]auth.Credentials) error {
|
||||
var basePrefix string
|
||||
switch userType {
|
||||
case srvAccUser:
|
||||
basePrefix = iamConfigServiceAccountsPrefix
|
||||
case stsUser:
|
||||
basePrefix = iamConfigSTSPrefix
|
||||
default:
|
||||
basePrefix = iamConfigUsersPrefix
|
||||
}
|
||||
|
||||
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.
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
err := ies.loadIAMConfig(&p, getMappedPolicyPath(name, isSTS, isGroup))
|
||||
err := ies.loadIAMConfig(&p, getMappedPolicyPath(name, userType, isGroup))
|
||||
if err != nil {
|
||||
if err == errConfigNotFound {
|
||||
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)
|
||||
defer cancel()
|
||||
ies.setContext(ctx)
|
||||
defer ies.clearContext()
|
||||
var basePrefix string
|
||||
switch {
|
||||
case isSTS:
|
||||
basePrefix = iamConfigPolicyDBSTSUsersPrefix
|
||||
case isGroup:
|
||||
if isGroup {
|
||||
basePrefix = iamConfigPolicyDBGroupsPrefix
|
||||
default:
|
||||
basePrefix = iamConfigPolicyDBUsersPrefix
|
||||
} else {
|
||||
switch userType {
|
||||
case srvAccUser:
|
||||
basePrefix = iamConfigPolicyDBServiceAccountsPrefix
|
||||
case stsUser:
|
||||
basePrefix = iamConfigPolicyDBSTSUsersPrefix
|
||||
default:
|
||||
basePrefix = iamConfigPolicyDBUsersPrefix
|
||||
}
|
||||
}
|
||||
r, err := ies.client.Get(ctx, basePrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
|
||||
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.
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -452,29 +486,32 @@ func (ies *IAMEtcdStore) loadAll(sys *IAMSys, objectAPI ObjectLayer) error {
|
||||
}
|
||||
|
||||
// load STS temp users
|
||||
if err := ies.loadUsers(true, iamUsersMap); err != nil {
|
||||
if err := ies.loadUsers(stsUser, iamUsersMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isMinIOUsersSys {
|
||||
// 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
|
||||
}
|
||||
if err := ies.loadGroups(iamGroupsMap); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ies.loadMappedPolicies(false, false, iamUserPolicyMap); err != nil {
|
||||
if err := ies.loadMappedPolicies(regularUser, false, iamUserPolicyMap); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -498,12 +535,12 @@ func (ies *IAMEtcdStore) savePolicyDoc(policyName string, p iampolicy.Policy) er
|
||||
return ies.saveIAMConfig(&p, getPolicyDocPath(policyName))
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) saveMappedPolicy(name string, isSTS, isGroup bool, mp MappedPolicy) error {
|
||||
return ies.saveIAMConfig(mp, getMappedPolicyPath(name, isSTS, isGroup))
|
||||
func (ies *IAMEtcdStore) saveMappedPolicy(name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error {
|
||||
return ies.saveIAMConfig(mp, getMappedPolicyPath(name, userType, isGroup))
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) saveUserIdentity(name string, isSTS bool, u UserIdentity) error {
|
||||
return ies.saveIAMConfig(u, getUserIdentityPath(name, isSTS))
|
||||
func (ies *IAMEtcdStore) saveUserIdentity(name string, userType IAMUserType, u UserIdentity) error {
|
||||
return ies.saveIAMConfig(u, getUserIdentityPath(name, userType))
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) saveGroupInfo(name string, gi GroupInfo) error {
|
||||
@ -518,16 +555,16 @@ func (ies *IAMEtcdStore) deletePolicyDoc(name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) deleteMappedPolicy(name string, isSTS, isGroup bool) error {
|
||||
err := ies.deleteIAMConfig(getMappedPolicyPath(name, isSTS, isGroup))
|
||||
func (ies *IAMEtcdStore) deleteMappedPolicy(name string, userType IAMUserType, isGroup bool) error {
|
||||
err := ies.deleteIAMConfig(getMappedPolicyPath(name, userType, isGroup))
|
||||
if err == errConfigNotFound {
|
||||
err = errNoSuchPolicy
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) deleteUserIdentity(name string, isSTS bool) error {
|
||||
err := ies.deleteIAMConfig(getUserIdentityPath(name, isSTS))
|
||||
func (ies *IAMEtcdStore) deleteUserIdentity(name string, userType IAMUserType) error {
|
||||
err := ies.deleteIAMConfig(getUserIdentityPath(name, userType))
|
||||
if err == errConfigNotFound {
|
||||
err = errNoSuchUser
|
||||
}
|
||||
@ -599,11 +636,11 @@ func (ies *IAMEtcdStore) reloadFromEvent(sys *IAMSys, event *etcd.Event) {
|
||||
case usersPrefix:
|
||||
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||
iamConfigUsersPrefix))
|
||||
ies.loadUser(accessKey, false, sys.iamUsersMap)
|
||||
ies.loadUser(accessKey, regularUser, sys.iamUsersMap)
|
||||
case stsPrefix:
|
||||
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||
iamConfigSTSPrefix))
|
||||
ies.loadUser(accessKey, true, sys.iamUsersMap)
|
||||
ies.loadUser(accessKey, stsUser, sys.iamUsersMap)
|
||||
case groupsPrefix:
|
||||
group := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||
iamConfigGroupsPrefix))
|
||||
@ -619,17 +656,17 @@ func (ies *IAMEtcdStore) reloadFromEvent(sys *IAMSys, event *etcd.Event) {
|
||||
policyMapFile := strings.TrimPrefix(string(event.Kv.Key),
|
||||
iamConfigPolicyDBUsersPrefix)
|
||||
user := strings.TrimSuffix(policyMapFile, ".json")
|
||||
ies.loadMappedPolicy(user, false, false, sys.iamUserPolicyMap)
|
||||
ies.loadMappedPolicy(user, regularUser, false, sys.iamUserPolicyMap)
|
||||
case policyDBSTSUsersPrefix:
|
||||
policyMapFile := strings.TrimPrefix(string(event.Kv.Key),
|
||||
iamConfigPolicyDBSTSUsersPrefix)
|
||||
user := strings.TrimSuffix(policyMapFile, ".json")
|
||||
ies.loadMappedPolicy(user, true, false, sys.iamUserPolicyMap)
|
||||
ies.loadMappedPolicy(user, stsUser, false, sys.iamUserPolicyMap)
|
||||
case policyDBGroupsPrefix:
|
||||
policyMapFile := strings.TrimPrefix(string(event.Kv.Key),
|
||||
iamConfigPolicyDBGroupsPrefix)
|
||||
user := strings.TrimSuffix(policyMapFile, ".json")
|
||||
ies.loadMappedPolicy(user, false, true, sys.iamGroupPolicyMap)
|
||||
ies.loadMappedPolicy(user, regularUser, true, sys.iamGroupPolicyMap)
|
||||
}
|
||||
case eventDelete:
|
||||
switch {
|
||||
|
@ -25,6 +25,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
||||
@ -289,14 +295,14 @@ func (iamOS *IAMObjectStore) loadPolicyDocs(m map[string]iampolicy.Policy) error
|
||||
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()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
var u UserIdentity
|
||||
err := iamOS.loadIAMConfig(&u, getUserIdentityPath(user, isSTS))
|
||||
err := iamOS.loadIAMConfig(&u, getUserIdentityPath(user, userType))
|
||||
if err != nil {
|
||||
if err == errConfigNotFound {
|
||||
return errNoSuchUser
|
||||
@ -306,11 +312,31 @@ func (iamOS *IAMObjectStore) loadUser(user string, isSTS bool, m map[string]auth
|
||||
|
||||
if u.Credentials.IsExpired() {
|
||||
// Delete expired identity - ignoring errors here.
|
||||
iamOS.deleteIAMConfig(getUserIdentityPath(user, isSTS))
|
||||
iamOS.deleteIAMConfig(getMappedPolicyPath(user, isSTS, false))
|
||||
iamOS.deleteIAMConfig(getUserIdentityPath(user, userType))
|
||||
iamOS.deleteIAMConfig(getMappedPolicyPath(user, userType, false))
|
||||
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 == "" {
|
||||
u.Credentials.AccessKey = user
|
||||
}
|
||||
@ -318,7 +344,7 @@ func (iamOS *IAMObjectStore) loadUser(user string, isSTS bool, m map[string]auth
|
||||
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()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
@ -326,17 +352,24 @@ func (iamOS *IAMObjectStore) loadUsers(isSTS bool, m map[string]auth.Credentials
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
basePrefix := iamConfigUsersPrefix
|
||||
if isSTS {
|
||||
|
||||
var basePrefix string
|
||||
switch userType {
|
||||
case srvAccUser:
|
||||
basePrefix = iamConfigServiceAccountsPrefix
|
||||
case stsUser:
|
||||
basePrefix = iamConfigSTSPrefix
|
||||
default:
|
||||
basePrefix = iamConfigUsersPrefix
|
||||
}
|
||||
|
||||
for item := range listIAMConfigItems(objectAPI, basePrefix, true, doneCh) {
|
||||
if item.Err != nil {
|
||||
return item.Err
|
||||
}
|
||||
|
||||
userName := item.Item
|
||||
err := iamOS.loadUser(userName, isSTS, m)
|
||||
err := iamOS.loadUser(userName, userType, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -384,7 +417,7 @@ func (iamOS *IAMObjectStore) loadGroups(m map[string]GroupInfo) error {
|
||||
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 {
|
||||
|
||||
objectAPI := iamOS.getObjectAPI()
|
||||
@ -393,7 +426,7 @@ func (iamOS *IAMObjectStore) loadMappedPolicy(name string, isSTS, isGroup bool,
|
||||
}
|
||||
|
||||
var p MappedPolicy
|
||||
err := iamOS.loadIAMConfig(&p, getMappedPolicyPath(name, isSTS, isGroup))
|
||||
err := iamOS.loadIAMConfig(&p, getMappedPolicyPath(name, userType, isGroup))
|
||||
if err != nil {
|
||||
if err == errConfigNotFound {
|
||||
return errNoSuchPolicy
|
||||
@ -404,7 +437,7 @@ func (iamOS *IAMObjectStore) loadMappedPolicy(name string, isSTS, isGroup bool,
|
||||
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()
|
||||
if objectAPI == nil {
|
||||
return errServerNotInitialized
|
||||
@ -413,13 +446,17 @@ func (iamOS *IAMObjectStore) loadMappedPolicies(isSTS, isGroup bool, m map[strin
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
var basePath string
|
||||
switch {
|
||||
case isSTS:
|
||||
basePath = iamConfigPolicyDBSTSUsersPrefix
|
||||
case isGroup:
|
||||
if isGroup {
|
||||
basePath = iamConfigPolicyDBGroupsPrefix
|
||||
default:
|
||||
basePath = iamConfigPolicyDBUsersPrefix
|
||||
} else {
|
||||
switch userType {
|
||||
case srvAccUser:
|
||||
basePath = iamConfigPolicyDBServiceAccountsPrefix
|
||||
case stsUser:
|
||||
basePath = iamConfigPolicyDBSTSUsersPrefix
|
||||
default:
|
||||
basePath = iamConfigPolicyDBUsersPrefix
|
||||
}
|
||||
}
|
||||
for item := range listIAMConfigItems(objectAPI, basePath, false, doneCh) {
|
||||
if item.Err != nil {
|
||||
@ -428,7 +465,7 @@ func (iamOS *IAMObjectStore) loadMappedPolicies(isSTS, isGroup bool, m map[strin
|
||||
|
||||
policyFile := item.Item
|
||||
userOrGroupName := strings.TrimSuffix(policyFile, ".json")
|
||||
err := iamOS.loadMappedPolicy(userOrGroupName, isSTS, isGroup, m)
|
||||
err := iamOS.loadMappedPolicy(userOrGroupName, userType, isGroup, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -466,27 +503,30 @@ func (iamOS *IAMObjectStore) loadAll(sys *IAMSys, objectAPI ObjectLayer) error {
|
||||
return err
|
||||
}
|
||||
// load STS temp users
|
||||
if err := iamOS.loadUsers(true, iamUsersMap); err != nil {
|
||||
if err := iamOS.loadUsers(stsUser, iamUsersMap); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
if err := iamOS.loadGroups(iamGroupsMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := iamOS.loadMappedPolicies(false, false, iamUserPolicyMap); err != nil {
|
||||
if err := iamOS.loadMappedPolicies(regularUser, false, iamUserPolicyMap); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// load STS policy mappings
|
||||
if err := iamOS.loadMappedPolicies(true, false, iamUserPolicyMap); err != nil {
|
||||
if err := iamOS.loadMappedPolicies(stsUser, false, iamUserPolicyMap); err != nil {
|
||||
return err
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -510,12 +550,12 @@ func (iamOS *IAMObjectStore) savePolicyDoc(policyName string, p iampolicy.Policy
|
||||
return iamOS.saveIAMConfig(&p, getPolicyDocPath(policyName))
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) saveMappedPolicy(name string, isSTS, isGroup bool, mp MappedPolicy) error {
|
||||
return iamOS.saveIAMConfig(mp, getMappedPolicyPath(name, isSTS, isGroup))
|
||||
func (iamOS *IAMObjectStore) saveMappedPolicy(name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error {
|
||||
return iamOS.saveIAMConfig(mp, getMappedPolicyPath(name, userType, isGroup))
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) saveUserIdentity(name string, isSTS bool, u UserIdentity) error {
|
||||
return iamOS.saveIAMConfig(u, getUserIdentityPath(name, isSTS))
|
||||
func (iamOS *IAMObjectStore) saveUserIdentity(name string, userType IAMUserType, u UserIdentity) error {
|
||||
return iamOS.saveIAMConfig(u, getUserIdentityPath(name, userType))
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) saveGroupInfo(name string, gi GroupInfo) error {
|
||||
@ -530,16 +570,16 @@ func (iamOS *IAMObjectStore) deletePolicyDoc(name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) deleteMappedPolicy(name string, isSTS, isGroup bool) error {
|
||||
err := iamOS.deleteIAMConfig(getMappedPolicyPath(name, isSTS, isGroup))
|
||||
func (iamOS *IAMObjectStore) deleteMappedPolicy(name string, userType IAMUserType, isGroup bool) error {
|
||||
err := iamOS.deleteIAMConfig(getMappedPolicyPath(name, userType, isGroup))
|
||||
if err == errConfigNotFound {
|
||||
err = errNoSuchPolicy
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) deleteUserIdentity(name string, isSTS bool) error {
|
||||
err := iamOS.deleteIAMConfig(getUserIdentityPath(name, isSTS))
|
||||
func (iamOS *IAMObjectStore) deleteUserIdentity(name string, userType IAMUserType) error {
|
||||
err := iamOS.deleteIAMConfig(getUserIdentityPath(name, userType))
|
||||
if err == errConfigNotFound {
|
||||
err = errNoSuchUser
|
||||
}
|
||||
|
347
cmd/iam.go
347
cmd/iam.go
@ -19,7 +19,9 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
@ -52,6 +54,9 @@ const (
|
||||
// IAM users directory.
|
||||
iamConfigUsersPrefix = iamConfigPrefix + "/users/"
|
||||
|
||||
// IAM service accounts directory.
|
||||
iamConfigServiceAccountsPrefix = iamConfigPrefix + "/service-accounts/"
|
||||
|
||||
// IAM groups directory.
|
||||
iamConfigGroupsPrefix = iamConfigPrefix + "/groups/"
|
||||
|
||||
@ -62,10 +67,11 @@ const (
|
||||
iamConfigSTSPrefix = iamConfigPrefix + "/sts/"
|
||||
|
||||
// IAM Policy DB prefixes.
|
||||
iamConfigPolicyDBPrefix = iamConfigPrefix + "/policydb/"
|
||||
iamConfigPolicyDBUsersPrefix = iamConfigPolicyDBPrefix + "users/"
|
||||
iamConfigPolicyDBSTSUsersPrefix = iamConfigPolicyDBPrefix + "sts-users/"
|
||||
iamConfigPolicyDBGroupsPrefix = iamConfigPolicyDBPrefix + "groups/"
|
||||
iamConfigPolicyDBPrefix = iamConfigPrefix + "/policydb/"
|
||||
iamConfigPolicyDBUsersPrefix = iamConfigPolicyDBPrefix + "users/"
|
||||
iamConfigPolicyDBSTSUsersPrefix = iamConfigPolicyDBPrefix + "sts-users/"
|
||||
iamConfigPolicyDBServiceAccountsPrefix = iamConfigPolicyDBPrefix + "service-accounts/"
|
||||
iamConfigPolicyDBGroupsPrefix = iamConfigPolicyDBPrefix + "groups/"
|
||||
|
||||
// IAM identity file which captures identity credentials.
|
||||
iamIdentityFile = "identity.json"
|
||||
@ -99,10 +105,15 @@ func getIAMFormatFilePath() string {
|
||||
return iamConfigPrefix + SlashSeparator + iamFormatFile
|
||||
}
|
||||
|
||||
func getUserIdentityPath(user string, isSTS bool) string {
|
||||
basePath := iamConfigUsersPrefix
|
||||
if isSTS {
|
||||
func getUserIdentityPath(user string, userType IAMUserType) string {
|
||||
var basePath string
|
||||
switch userType {
|
||||
case srvAccUser:
|
||||
basePath = iamConfigServiceAccountsPrefix
|
||||
case stsUser:
|
||||
basePath = iamConfigSTSPrefix
|
||||
default:
|
||||
basePath = iamConfigUsersPrefix
|
||||
}
|
||||
return pathJoin(basePath, user, iamIdentityFile)
|
||||
}
|
||||
@ -115,12 +126,15 @@ func getPolicyDocPath(name string) string {
|
||||
return pathJoin(iamConfigPoliciesPrefix, name, iamPolicyFile)
|
||||
}
|
||||
|
||||
func getMappedPolicyPath(name string, isSTS, isGroup bool) string {
|
||||
switch {
|
||||
case isSTS:
|
||||
return pathJoin(iamConfigPolicyDBSTSUsersPrefix, name+".json")
|
||||
case isGroup:
|
||||
func getMappedPolicyPath(name string, userType IAMUserType, isGroup bool) string {
|
||||
if isGroup {
|
||||
return pathJoin(iamConfigPolicyDBGroupsPrefix, name+".json")
|
||||
}
|
||||
switch userType {
|
||||
case srvAccUser:
|
||||
return pathJoin(iamConfigPolicyDBServiceAccountsPrefix, name+".json")
|
||||
case stsUser:
|
||||
return pathJoin(iamConfigPolicyDBSTSUsersPrefix, name+".json")
|
||||
default:
|
||||
return pathJoin(iamConfigPolicyDBUsersPrefix, name+".json")
|
||||
}
|
||||
@ -180,6 +194,15 @@ type IAMSys struct {
|
||||
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
|
||||
type IAMStorageAPI interface {
|
||||
migrateBackendFormat(ObjectLayer) error
|
||||
@ -187,14 +210,14 @@ type IAMStorageAPI interface {
|
||||
loadPolicyDoc(policy string, m map[string]iampolicy.Policy) error
|
||||
loadPolicyDocs(m map[string]iampolicy.Policy) error
|
||||
|
||||
loadUser(user string, isSTS bool, m map[string]auth.Credentials) error
|
||||
loadUsers(isSTS bool, m map[string]auth.Credentials) error
|
||||
loadUser(user string, userType IAMUserType, m map[string]auth.Credentials) error
|
||||
loadUsers(userType IAMUserType, m map[string]auth.Credentials) error
|
||||
|
||||
loadGroup(group string, m map[string]GroupInfo) error
|
||||
loadGroups(m map[string]GroupInfo) error
|
||||
|
||||
loadMappedPolicy(name string, isSTS, isGroup bool, m map[string]MappedPolicy) error
|
||||
loadMappedPolicies(isSTS, isGroup bool, m map[string]MappedPolicy) error
|
||||
loadMappedPolicy(name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
|
||||
loadMappedPolicies(userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
|
||||
|
||||
loadAll(*IAMSys, ObjectLayer) error
|
||||
|
||||
@ -203,13 +226,13 @@ type IAMStorageAPI interface {
|
||||
deleteIAMConfig(path string) error
|
||||
|
||||
savePolicyDoc(policyName string, p iampolicy.Policy) error
|
||||
saveMappedPolicy(name string, isSTS, isGroup bool, mp MappedPolicy) error
|
||||
saveUserIdentity(name string, isSTS bool, u UserIdentity) error
|
||||
saveMappedPolicy(name string, userType IAMUserType, isGroup bool, mp MappedPolicy) error
|
||||
saveUserIdentity(name string, userType IAMUserType, u UserIdentity) error
|
||||
saveGroupInfo(group string, gi GroupInfo) error
|
||||
|
||||
deletePolicyDoc(policyName string) error
|
||||
deleteMappedPolicy(name string, isSTS, isGroup bool) error
|
||||
deleteUserIdentity(name string, isSTS bool) error
|
||||
deleteMappedPolicy(name string, userType IAMUserType, isGroup bool) error
|
||||
deleteUserIdentity(name string, userType IAMUserType) error
|
||||
deleteGroupInfo(name string) error
|
||||
|
||||
watch(*IAMSys)
|
||||
@ -290,9 +313,9 @@ func (sys *IAMSys) LoadPolicyMapping(objAPI ObjectLayer, userOrGroup string, isG
|
||||
if globalEtcdClient == nil {
|
||||
var err error
|
||||
if isGroup {
|
||||
err = sys.store.loadMappedPolicy(userOrGroup, false, isGroup, sys.iamGroupPolicyMap)
|
||||
err = sys.store.loadMappedPolicy(userOrGroup, regularUser, isGroup, sys.iamGroupPolicyMap)
|
||||
} else {
|
||||
err = sys.store.loadMappedPolicy(userOrGroup, false, isGroup, sys.iamUserPolicyMap)
|
||||
err = sys.store.loadMappedPolicy(userOrGroup, regularUser, isGroup, sys.iamUserPolicyMap)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, isSTS bool) error {
|
||||
func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, userType IAMUserType) error {
|
||||
sys.Lock()
|
||||
defer sys.Unlock()
|
||||
|
||||
@ -314,11 +337,11 @@ func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, isSTS bool) er
|
||||
}
|
||||
|
||||
if globalEtcdClient == nil {
|
||||
err := sys.store.loadUser(accessKey, isSTS, sys.iamUsersMap)
|
||||
err := sys.store.loadUser(accessKey, userType, sys.iamUsersMap)
|
||||
if err != nil {
|
||||
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
|
||||
if err != nil && err != errConfigNotFound {
|
||||
return err
|
||||
@ -360,8 +383,12 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
|
||||
}
|
||||
|
||||
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.
|
||||
@ -393,7 +420,7 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
|
||||
|
||||
// Delete user-policy mappings that will no longer apply
|
||||
var usersToDel []string
|
||||
var isUserSTS []bool
|
||||
var usersType []IAMUserType
|
||||
for u, mp := range sys.iamUserPolicyMap {
|
||||
if mp.Policy == policyName {
|
||||
usersToDel = append(usersToDel, u)
|
||||
@ -403,12 +430,15 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
|
||||
return errNoSuchUser
|
||||
}
|
||||
// User is from STS if the creds are temporary
|
||||
isSTS := cr.IsTemp()
|
||||
isUserSTS = append(isUserSTS, isSTS)
|
||||
if cr.IsTemp() {
|
||||
usersType = append(usersType, stsUser)
|
||||
} else {
|
||||
usersType = append(usersType, regularUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
@ -419,7 +449,7 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
|
||||
}
|
||||
}
|
||||
for _, g := range groupsToDel {
|
||||
sys.policyDBSet(g, "", false, true)
|
||||
sys.policyDBSet(g, "", regularUser, true)
|
||||
}
|
||||
|
||||
return err
|
||||
@ -523,8 +553,8 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
|
||||
}
|
||||
|
||||
// It is ok to ignore deletion error on the mapped policy
|
||||
sys.store.deleteMappedPolicy(accessKey, false, false)
|
||||
err := sys.store.deleteUserIdentity(accessKey, false)
|
||||
sys.store.deleteMappedPolicy(accessKey, regularUser, false)
|
||||
err := sys.store.deleteUserIdentity(accessKey, regularUser)
|
||||
switch err.(type) {
|
||||
case ObjectNotFound:
|
||||
// ignore if user is already deleted.
|
||||
@ -534,6 +564,15 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
|
||||
delete(sys.iamUsersMap, 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
|
||||
}
|
||||
|
||||
@ -565,7 +604,7 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -577,7 +616,7 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -602,7 +641,7 @@ func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
|
||||
}
|
||||
|
||||
for k, v := range sys.iamUsersMap {
|
||||
if !v.IsTemp() {
|
||||
if !v.IsTemp() && !v.IsServiceAccount() {
|
||||
users[k] = madmin.UserInfo{
|
||||
PolicyName: sys.iamUserPolicyMap[k].Policy,
|
||||
Status: func() madmin.AccountStatus {
|
||||
@ -636,6 +675,28 @@ func (sys *IAMSys) IsTempUser(name string) (bool, error) {
|
||||
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.
|
||||
func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
|
||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||
@ -717,7 +778,7 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
|
||||
return errServerNotInitialized
|
||||
}
|
||||
|
||||
if err := sys.store.saveUserIdentity(accessKey, false, uinfo); err != nil {
|
||||
if err := sys.store.saveUserIdentity(accessKey, regularUser, uinfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -725,6 +786,104 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
|
||||
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.
|
||||
func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
|
||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||
@ -754,7 +913,7 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
|
||||
return errIAMActionNotAllowed
|
||||
}
|
||||
|
||||
if err := sys.store.saveUserIdentity(accessKey, false, u); err != nil {
|
||||
if err := sys.store.saveUserIdentity(accessKey, regularUser, u); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -762,7 +921,7 @@ func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
|
||||
|
||||
// Set policy if specified.
|
||||
if uinfo.PolicyName != "" {
|
||||
return sys.policyDBSet(accessKey, uinfo.PolicyName, false, false)
|
||||
return sys.policyDBSet(accessKey, uinfo.PolicyName, regularUser, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -792,7 +951,7 @@ func (sys *IAMSys) SetUserSecretKey(accessKey string, secretKey string) error {
|
||||
|
||||
cred.SecretKey = secretKey
|
||||
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
|
||||
}
|
||||
|
||||
@ -923,7 +1082,7 @@ func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
|
||||
|
||||
// Remove the group from storage. First delete the
|
||||
// mapped policy.
|
||||
err := sys.store.deleteMappedPolicy(group, false, true)
|
||||
err := sys.store.deleteMappedPolicy(group, regularUser, true)
|
||||
// No-mapped-policy case is ignored.
|
||||
if err != nil && err != errConfigNotFound {
|
||||
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
|
||||
// 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
|
||||
// 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 {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
@ -1099,7 +1258,7 @@ func (sys *IAMSys) policyDBSet(name, policy string, isSTS, isGroup bool) error {
|
||||
|
||||
// Handle policy mapping removal
|
||||
if policy == "" {
|
||||
if err := sys.store.deleteMappedPolicy(name, isSTS, isGroup); err != nil {
|
||||
if err := sys.store.deleteMappedPolicy(name, userType, isGroup); err != nil {
|
||||
return err
|
||||
}
|
||||
if !isGroup {
|
||||
@ -1112,7 +1271,7 @@ func (sys *IAMSys) policyDBSet(name, policy string, isSTS, isGroup bool) error {
|
||||
|
||||
// Handle policy mapping set/update
|
||||
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
|
||||
}
|
||||
if !isGroup {
|
||||
@ -1294,6 +1453,93 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
|
||||
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,
|
||||
// which implements claims validation and verification other than
|
||||
// applying policies.
|
||||
@ -1444,6 +1690,17 @@ func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
|
||||
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)
|
||||
if err != nil {
|
||||
logger.LogIf(context.Background(), err)
|
||||
|
@ -325,7 +325,12 @@ func (s *peerRESTServer) LoadUserHandler(w http.ResponseWriter, r *http.Request)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
@ -58,6 +58,9 @@ const (
|
||||
expClaim = "exp"
|
||||
subClaim = "sub"
|
||||
|
||||
// JWT claim to check the parent user
|
||||
parentClaim = "parent"
|
||||
|
||||
// LDAP claim keys
|
||||
ldapUser = "ldapUser"
|
||||
ldapGroups = "ldapGroups"
|
||||
|
@ -90,6 +90,7 @@ type Credentials struct {
|
||||
Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"`
|
||||
SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"`
|
||||
Status string `xml:"-" json:"status,omitempty"`
|
||||
ParentUser string `xml:"-" json:"parentUser,omitempty"`
|
||||
}
|
||||
|
||||
func (cred Credentials) String() string {
|
||||
@ -119,7 +120,12 @@ func (cred Credentials) IsExpired() bool {
|
||||
|
||||
// IsTemp - returns whether credential is temporary or not.
|
||||
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.
|
||||
@ -207,14 +213,15 @@ func GetNewCredentialsWithMetadata(m map[string]interface{}, tokenSecret string)
|
||||
"/", "+", -1)
|
||||
cred.Status = "on"
|
||||
|
||||
if tokenSecret == "" {
|
||||
cred.Expiration = timeSentinel
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
expiry, err := ExpToInt64(m["exp"])
|
||||
if err != nil {
|
||||
return cred, err
|
||||
}
|
||||
if expiry == 0 {
|
||||
cred.Expiration = timeSentinel
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
m["accessKey"] = cred.AccessKey
|
||||
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(m))
|
||||
|
52
pkg/madmin/examples/add-service-account-and-policy.go
Normal file
52
pkg/madmin/examples/add-service-account-and-policy.go
Normal file
@ -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)
|
||||
}
|
49
pkg/madmin/examples/get-service-account.go
Normal file
49
pkg/madmin/examples/get-service-account.go
Normal file
@ -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
|
||||
}
|
||||
|
||||
// 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…
Reference in New Issue
Block a user