mirror of
https://github.com/minio/minio.git
synced 2025-01-11 23:13:23 -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}
|
// 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
|
||||||
}
|
}
|
||||||
|
347
cmd/iam.go
347
cmd/iam.go
@ -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))
|
||||||
|
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
|
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