Reload a specific user or policy on peers (#7705)

Fixes #7587
This commit is contained in:
Harshavardhana 2019-06-06 17:46:22 -07:00 committed by kannappanr
parent 975237cbf8
commit 6d89435356
7 changed files with 565 additions and 187 deletions

View File

@ -940,8 +940,12 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
accessKey := vars["accessKey"] accessKey := vars["accessKey"]
if err := globalIAMSys.DeleteUser(accessKey); err != nil { // Notify all other MinIO peers to delete user.
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) for _, nerr := range globalNotificationSys.DeleteUser(accessKey) {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
} }
} }
@ -1006,8 +1010,8 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
return return
} }
// Notify all other MinIO peers to reload users // Notify all other MinIO peers to reload user.
for _, nerr := range globalNotificationSys.LoadUsers() { for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil { if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err) logger.LogIf(ctx, nerr.Err)
@ -1065,8 +1069,8 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
return return
} }
// Notify all other MinIO peers to reload users // Notify all other Minio peers to reload user
for _, nerr := range globalNotificationSys.LoadUsers() { for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil { if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err) logger.LogIf(ctx, nerr.Err)
@ -1083,7 +1087,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
return return
} }
policies, err := globalIAMSys.ListCannedPolicies() policies, err := globalIAMSys.ListPolicies()
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
@ -1115,13 +1119,13 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
return return
} }
if err := globalIAMSys.DeleteCannedPolicy(policyName); err != nil { if err := globalIAMSys.DeletePolicy(policyName); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
// Notify all other MinIO peers to reload users // Notify all other MinIO peers to delete policy
for _, nerr := range globalNotificationSys.LoadUsers() { for _, nerr := range globalNotificationSys.DeletePolicy(policyName) {
if nerr.Err != nil { if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err) logger.LogIf(ctx, nerr.Err)
@ -1171,13 +1175,13 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
return return
} }
if err = globalIAMSys.SetCannedPolicy(policyName, *iamPolicy); err != nil { if err = globalIAMSys.SetPolicy(policyName, *iamPolicy); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
// Notify all other MinIO peers to reload users // Notify all other MinIO peers to reload policy
for _, nerr := range globalNotificationSys.LoadUsers() { for _, nerr := range globalNotificationSys.LoadPolicy(policyName) {
if nerr.Err != nil { if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err) logger.LogIf(ctx, nerr.Err)
@ -1214,8 +1218,8 @@ func (a adminAPIHandlers) SetUserPolicy(w http.ResponseWriter, r *http.Request)
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
} }
// Notify all other MinIO peers to reload users // Notify all other Minio peers to reload user
for _, nerr := range globalNotificationSys.LoadUsers() { for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
if nerr.Err != nil { if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err) logger.LogIf(ctx, nerr.Err)

View File

@ -25,6 +25,7 @@ import (
"time" "time"
etcd "github.com/coreos/etcd/clientv3" etcd "github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/mvcc/mvccpb"
"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"
@ -60,6 +61,45 @@ type IAMSys struct {
iamCannedPolicyMap map[string]iampolicy.Policy iamCannedPolicyMap map[string]iampolicy.Policy
} }
// LoadPolicy - reloads a specific canned policy from backend disks or etcd.
func (sys *IAMSys) LoadPolicy(objAPI ObjectLayer, policyName string) error {
if objAPI == nil {
return errInvalidArgument
}
sys.Lock()
defer sys.Unlock()
prefix := iamConfigPoliciesPrefix
if globalEtcdClient == nil {
return reloadPolicy(context.Background(), objAPI, prefix, policyName, sys.iamCannedPolicyMap)
}
// When etcd is set, we use watch APIs so this code is not needed.
return nil
}
// LoadUser - reloads a specific user from backend disks or etcd.
func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, temp bool) error {
if objAPI == nil {
return errInvalidArgument
}
sys.Lock()
defer sys.Unlock()
prefix := iamConfigUsersPrefix
if temp {
prefix = iamConfigSTSPrefix
}
if globalEtcdClient == nil {
return reloadUser(context.Background(), objAPI, prefix, accessKey, sys.iamUsersMap, sys.iamPolicyMap)
}
// When etcd is set, we use watch APIs so this code is not needed.
return nil
}
// Load - loads iam subsystem // Load - loads iam subsystem
func (sys *IAMSys) Load(objAPI ObjectLayer) error { func (sys *IAMSys) Load(objAPI ObjectLayer) error {
if globalEtcdClient != nil { if globalEtcdClient != nil {
@ -68,19 +108,63 @@ func (sys *IAMSys) Load(objAPI ObjectLayer) error {
return sys.refresh(objAPI) return sys.refresh(objAPI)
} }
// Init - initializes config system from iam.json func (sys *IAMSys) reloadFromEvent(event *etcd.Event) {
func (sys *IAMSys) Init(objAPI ObjectLayer) error { eventCreate := event.IsModify() || event.IsCreate()
if objAPI == nil { eventDelete := event.Type == etcd.EventTypeDelete
return errInvalidArgument usersPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigUsersPrefix)
stsPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigSTSPrefix)
policyPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigPoliciesPrefix)
ctx, cancel := context.WithTimeout(context.Background(),
defaultContextTimeout)
defer cancel()
switch {
case eventCreate:
switch {
case usersPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigUsersPrefix))
reloadEtcdUser(ctx, iamConfigUsersPrefix, accessKey,
sys.iamUsersMap, sys.iamPolicyMap)
case stsPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigSTSPrefix))
reloadEtcdUser(ctx, iamConfigSTSPrefix, accessKey,
sys.iamUsersMap, sys.iamPolicyMap)
case policyPrefix:
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigPoliciesPrefix))
reloadEtcdPolicy(ctx, iamConfigPoliciesPrefix,
policyName, sys.iamCannedPolicyMap)
}
case eventDelete:
switch {
case usersPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigUsersPrefix))
delete(sys.iamUsersMap, accessKey)
delete(sys.iamPolicyMap, accessKey)
case stsPrefix:
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigSTSPrefix))
delete(sys.iamUsersMap, accessKey)
delete(sys.iamPolicyMap, accessKey)
case policyPrefix:
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
iamConfigPoliciesPrefix))
delete(sys.iamCannedPolicyMap, policyName)
}
}
} }
if globalEtcdClient != nil { // Watch etcd entries for IAM
defer func() { func (sys *IAMSys) watchIAMEtcd() {
go func() { watchEtcd := func() {
// Refresh IAMSys with etcd watch. // Refresh IAMSys with etcd watch.
for { for {
watchCh := globalEtcdClient.Watch(context.Background(), watchCh := globalEtcdClient.Watch(context.Background(),
iamConfigPrefix, etcd.WithPrefix()) iamConfigPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
select { select {
case <-GlobalServiceDoneCh: case <-GlobalServiceDoneCh:
return return
@ -96,18 +180,18 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
continue continue
} }
for _, event := range watchResp.Events { for _, event := range watchResp.Events {
if event.IsModify() || event.IsCreate() || event.Type == etcd.EventTypeDelete { sys.Lock()
sys.refreshEtcd() sys.reloadFromEvent(event)
sys.Unlock()
} }
} }
} }
} }
}() go watchEtcd()
}() }
} else {
defer func() { func (sys *IAMSys) watchIAMDisk(objAPI ObjectLayer) {
// Refresh IAMSys in background. watchDisk := func() {
go func() {
ticker := time.NewTicker(globalRefreshIAMInterval) ticker := time.NewTicker(globalRefreshIAMInterval)
defer ticker.Stop() defer ticker.Stop()
for { for {
@ -118,8 +202,21 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
sys.refresh(objAPI) sys.refresh(objAPI)
} }
} }
}() }
}() // Refresh IAMSys in background.
go watchDisk()
}
// Init - initializes config system from iam.json
func (sys *IAMSys) Init(objAPI ObjectLayer) error {
if objAPI == nil {
return errInvalidArgument
}
if globalEtcdClient != nil {
defer sys.watchIAMEtcd()
} else {
defer sys.watchIAMDisk(objAPI)
} }
doneCh := make(chan struct{}) doneCh := make(chan struct{})
@ -148,8 +245,8 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
return nil return nil
} }
// DeleteCannedPolicy - deletes a canned policy. // DeletePolicy - deletes a canned policy from backend or etcd.
func (sys *IAMSys) DeleteCannedPolicy(policyName string) error { func (sys *IAMSys) DeletePolicy(policyName string) error {
objectAPI := newObjectLayerFn() objectAPI := newObjectLayerFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
@ -160,11 +257,17 @@ func (sys *IAMSys) DeleteCannedPolicy(policyName string) error {
} }
var err error var err error
configFile := pathJoin(iamConfigPoliciesPrefix, policyName, iamPolicyFile) pFile := pathJoin(iamConfigPoliciesPrefix, policyName, iamPolicyFile)
if globalEtcdClient != nil { if globalEtcdClient != nil {
err = deleteConfigEtcd(context.Background(), globalEtcdClient, configFile) err = deleteConfigEtcd(context.Background(), globalEtcdClient, pFile)
} else { } else {
err = deleteConfig(context.Background(), objectAPI, configFile) err = deleteConfig(context.Background(), objectAPI, pFile)
}
switch err.(type) {
case ObjectNotFound:
// Ignore error if policy is already deleted.
err = nil
} }
sys.Lock() sys.Lock()
@ -174,8 +277,8 @@ func (sys *IAMSys) DeleteCannedPolicy(policyName string) error {
return err return err
} }
// ListCannedPolicies - lists all canned policies. // ListPolicies - lists all canned policies.
func (sys *IAMSys) ListCannedPolicies() (map[string][]byte, error) { func (sys *IAMSys) ListPolicies() (map[string][]byte, error) {
objectAPI := newObjectLayerFn() objectAPI := newObjectLayerFn()
if objectAPI == nil { if objectAPI == nil {
return nil, errServerNotInitialized return nil, errServerNotInitialized
@ -197,8 +300,8 @@ func (sys *IAMSys) ListCannedPolicies() (map[string][]byte, error) {
return cannedPolicyMap, nil return cannedPolicyMap, nil
} }
// SetCannedPolicy - sets a new canned policy. // SetPolicy - sets a new canned policy.
func (sys *IAMSys) SetCannedPolicy(policyName string, p iampolicy.Policy) error { func (sys *IAMSys) SetPolicy(policyName string, p iampolicy.Policy) error {
objectAPI := newObjectLayerFn() objectAPI := newObjectLayerFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
@ -279,11 +382,11 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
pFile := pathJoin(iamConfigUsersPrefix, accessKey, iamPolicyFile) pFile := pathJoin(iamConfigUsersPrefix, accessKey, iamPolicyFile)
iFile := pathJoin(iamConfigUsersPrefix, accessKey, iamIdentityFile) iFile := pathJoin(iamConfigUsersPrefix, accessKey, iamIdentityFile)
if globalEtcdClient != nil { if globalEtcdClient != nil {
// It is okay to ingnore errors when deleting policy.json for the user. // It is okay to ignore errors when deleting policy.json for the user.
_ = deleteConfigEtcd(context.Background(), globalEtcdClient, pFile) deleteConfigEtcd(context.Background(), globalEtcdClient, pFile)
err = deleteConfigEtcd(context.Background(), globalEtcdClient, iFile) err = deleteConfigEtcd(context.Background(), globalEtcdClient, iFile)
} else { } else {
// It is okay to ingnore errors when deleting policy.json for the user. // It is okay to ignore errors when deleting policy.json for the user.
_ = deleteConfig(context.Background(), objectAPI, pFile) _ = deleteConfig(context.Background(), objectAPI, pFile)
err = deleteConfig(context.Background(), objectAPI, iFile) err = deleteConfig(context.Background(), objectAPI, iFile)
} }
@ -291,7 +394,8 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
// //
switch err.(type) { switch err.(type) {
case ObjectNotFound: case ObjectNotFound:
err = errNoSuchUser // ignore if user is already deleted.
err = nil
} }
sys.Lock() sys.Lock()
@ -567,21 +671,9 @@ func (sys *IAMSys) IsAllowed(args iampolicy.Args) bool {
var defaultContextTimeout = 30 * time.Second var defaultContextTimeout = 30 * time.Second
// Similar to reloadUsers but updates users, policies maps from etcd server, func etcdKvsToSet(prefix string, kvs []*mvccpb.KeyValue) set.StringSet {
func reloadEtcdUsers(prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
r, err := globalEtcdClient.Get(ctx, prefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if err != nil {
return err
}
// No users are created yet.
if r.Count == 0 {
return nil
}
users := set.NewStringSet() users := set.NewStringSet()
for _, kv := range r.Kvs { for _, kv := range kvs {
// Extract user by stripping off the `prefix` value as suffix, // Extract user by stripping off the `prefix` value as suffix,
// then strip off the remaining basename to obtain the prefix // then strip off the remaining basename to obtain the prefix
// value, usually in the following form. // value, usually in the following form.
@ -595,43 +687,45 @@ func reloadEtcdUsers(prefix string, usersMap map[string]auth.Credentials, policy
users.Add(user) users.Add(user)
} }
} }
return users
}
// Similar to reloadUsers but updates users, policies maps from etcd server,
func reloadEtcdUsers(prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
r, err := globalEtcdClient.Get(ctx, prefix, etcd.WithPrefix(), etcd.WithKeysOnly())
if err != nil {
return err
}
// No users are created yet.
if r.Count == 0 {
return nil
}
users := etcdKvsToSet(prefix, r.Kvs)
// Reload config and policies for all users. // Reload config and policies for all users.
for _, user := range users.ToSlice() { for _, user := range users.ToSlice() {
idFile := pathJoin(prefix, user, iamIdentityFile) if err = reloadEtcdUser(ctx, prefix, user, usersMap, policyMap); err != nil {
pFile := pathJoin(prefix, user, iamPolicyFile)
cdata, cerr := readConfigEtcd(ctx, globalEtcdClient, idFile)
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile)
if cerr != nil && cerr != errConfigNotFound {
return cerr
}
if perr != nil && perr != errConfigNotFound {
return perr
}
if cerr == errConfigNotFound && perr == errConfigNotFound {
continue
}
if cerr == nil {
var cred auth.Credentials
if err = json.Unmarshal(cdata, &cred); err != nil {
return err return err
} }
cred.AccessKey = user
if cred.IsExpired() {
deleteConfigEtcd(ctx, globalEtcdClient, idFile)
deleteConfigEtcd(ctx, globalEtcdClient, pFile)
continue
} }
usersMap[cred.AccessKey] = cred return nil
} }
if perr == nil {
var policyName string func reloadEtcdPolicy(ctx context.Context, prefix string, policyName string,
if err = json.Unmarshal(pdata, &policyName); err != nil { cannedPolicyMap map[string]iampolicy.Policy) error {
pFile := pathJoin(prefix, policyName, iamPolicyFile)
pdata, err := readConfigEtcd(ctx, globalEtcdClient, pFile)
if err != nil {
return err return err
} }
policyMap[user] = policyName var p iampolicy.Policy
} if err = json.Unmarshal(pdata, &p); err != nil {
return err
} }
cannedPolicyMap[policyName] = p
return nil return nil
} }
@ -647,35 +741,29 @@ func reloadEtcdPolicies(prefix string, cannedPolicyMap map[string]iampolicy.Poli
return nil return nil
} }
policies := set.NewStringSet() policies := etcdKvsToSet(prefix, r.Kvs)
for _, kv := range r.Kvs {
// Extract policy by stripping off the `prefix` value as suffix,
// then strip off the remaining basename to obtain the prefix
// value, usually in the following form.
//
// key := "config/iam/policies/newpolicy/identity.json"
// prefix := "config/iam/policies/"
// v := trim(trim(key, prefix), base(key)) == "newpolicy"
//
policyName := path.Clean(strings.TrimSuffix(strings.TrimPrefix(string(kv.Key), prefix), path.Base(string(kv.Key))))
if !policies.Contains(policyName) {
policies.Add(policyName)
}
}
// Reload config and policies for all policys. // Reload config and policies for all policys.
for _, policyName := range policies.ToSlice() { for _, policyName := range policies.ToSlice() {
if err = reloadEtcdPolicy(ctx, prefix, policyName, cannedPolicyMap); err != nil {
return err
}
}
return nil
}
func reloadPolicy(ctx context.Context, objectAPI ObjectLayer, prefix string,
policyName string, cannedPolicyMap map[string]iampolicy.Policy) error {
pFile := pathJoin(prefix, policyName, iamPolicyFile) pFile := pathJoin(prefix, policyName, iamPolicyFile)
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile) pdata, err := readConfig(context.Background(), objectAPI, pFile)
if perr != nil { if err != nil {
return perr return err
} }
var p iampolicy.Policy var p iampolicy.Policy
if err = json.Unmarshal(pdata, &p); err != nil { if err = json.Unmarshal(pdata, &p); err != nil {
return err return err
} }
cannedPolicyMap[policyName] = p cannedPolicyMap[path.Base(prefix)] = p
}
return nil return nil
} }
@ -690,16 +778,9 @@ func reloadPolicies(objectAPI ObjectLayer, prefix string, cannedPolicyMap map[st
} }
marker = lo.NextMarker marker = lo.NextMarker
for _, prefix := range lo.Prefixes { for _, prefix := range lo.Prefixes {
pFile := pathJoin(prefix, iamPolicyFile) if err = reloadPolicy(context.Background(), objectAPI, "", prefix, cannedPolicyMap); err != nil {
pdata, perr := readConfig(context.Background(), objectAPI, pFile)
if perr != nil {
return perr
}
var p iampolicy.Policy
if err = json.Unmarshal(pdata, &p); err != nil {
return err return err
} }
cannedPolicyMap[path.Base(prefix)] = p
} }
if !lo.IsTruncated { if !lo.IsTruncated {
break break
@ -709,6 +790,86 @@ func reloadPolicies(objectAPI ObjectLayer, prefix string, cannedPolicyMap map[st
} }
func reloadEtcdUser(ctx context.Context, prefix string, accessKey string,
usersMap map[string]auth.Credentials, policyMap map[string]string) error {
idFile := pathJoin(prefix, accessKey, iamIdentityFile)
pFile := pathJoin(prefix, accessKey, iamPolicyFile)
cdata, cerr := readConfigEtcd(ctx, globalEtcdClient, idFile)
pdata, perr := readConfigEtcd(ctx, globalEtcdClient, pFile)
if cerr != nil && cerr != errConfigNotFound {
return cerr
}
if perr != nil && perr != errConfigNotFound {
return perr
}
if cerr == errConfigNotFound && perr == errConfigNotFound {
return nil
}
if cerr == nil {
var cred auth.Credentials
if err := json.Unmarshal(cdata, &cred); err != nil {
return err
}
cred.AccessKey = path.Base(accessKey)
if cred.IsExpired() {
// Delete expired identity.
deleteConfigEtcd(ctx, globalEtcdClient, idFile)
// Delete expired identity policy.
deleteConfigEtcd(ctx, globalEtcdClient, pFile)
return nil
}
usersMap[cred.AccessKey] = cred
}
if perr == nil {
var policyName string
if err := json.Unmarshal(pdata, &policyName); err != nil {
return err
}
policyMap[path.Base(accessKey)] = policyName
}
return nil
}
func reloadUser(ctx context.Context, objectAPI ObjectLayer, prefix string, accessKey string,
usersMap map[string]auth.Credentials, policyMap map[string]string) error {
idFile := pathJoin(prefix, accessKey, iamIdentityFile)
pFile := pathJoin(prefix, accessKey, iamPolicyFile)
cdata, cerr := readConfig(ctx, objectAPI, idFile)
pdata, perr := readConfig(ctx, objectAPI, pFile)
if cerr != nil && cerr != errConfigNotFound {
return cerr
}
if perr != nil && perr != errConfigNotFound {
return perr
}
if cerr == errConfigNotFound && perr == errConfigNotFound {
return nil
}
if cerr == nil {
var cred auth.Credentials
if err := json.Unmarshal(cdata, &cred); err != nil {
return err
}
cred.AccessKey = path.Base(accessKey)
if cred.IsExpired() {
// Delete expired identity.
objectAPI.DeleteObject(context.Background(), minioMetaBucket, idFile)
// Delete expired identity policy.
objectAPI.DeleteObject(context.Background(), minioMetaBucket, pFile)
return nil
}
usersMap[cred.AccessKey] = cred
}
if perr == nil {
var policyName string
if err := json.Unmarshal(pdata, &policyName); err != nil {
return err
}
policyMap[path.Base(accessKey)] = policyName
}
return nil
}
// reloadUsers reads an updates users, policies from object layer into user and policy maps. // reloadUsers reads an updates users, policies from object layer into user and policy maps.
func reloadUsers(objectAPI ObjectLayer, prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error { func reloadUsers(objectAPI ObjectLayer, prefix string, usersMap map[string]auth.Credentials, policyMap map[string]string) error {
marker := "" marker := ""
@ -721,41 +882,10 @@ func reloadUsers(objectAPI ObjectLayer, prefix string, usersMap map[string]auth.
} }
marker = lo.NextMarker marker = lo.NextMarker
for _, prefix := range lo.Prefixes { for _, prefix := range lo.Prefixes {
idFile := pathJoin(prefix, iamIdentityFile) // Prefix is empty because prefix is already part of the List output.
pFile := pathJoin(prefix, iamPolicyFile) if err = reloadUser(context.Background(), objectAPI, "", prefix, usersMap, policyMap); err != nil {
cdata, cerr := readConfig(context.Background(), objectAPI, idFile)
pdata, perr := readConfig(context.Background(), objectAPI, pFile)
if cerr != nil && cerr != errConfigNotFound {
return cerr
}
if perr != nil && perr != errConfigNotFound {
return perr
}
if cerr == errConfigNotFound && perr == errConfigNotFound {
continue
}
if cerr == nil {
var cred auth.Credentials
if err = json.Unmarshal(cdata, &cred); err != nil {
return err return err
} }
cred.AccessKey = path.Base(prefix)
if cred.IsExpired() {
// Delete expired identity.
objectAPI.DeleteObject(context.Background(), minioMetaBucket, idFile)
// Delete expired identity policy.
objectAPI.DeleteObject(context.Background(), minioMetaBucket, pFile)
continue
}
usersMap[cred.AccessKey] = cred
}
if perr == nil {
var policyName string
if err = json.Unmarshal(pdata, &policyName); err != nil {
return err
}
policyMap[path.Base(prefix)] = policyName
}
} }
if !lo.IsTruncated { if !lo.IsTruncated {
break break

View File

@ -157,6 +157,66 @@ func (sys *NotificationSys) ReloadFormat(dryRun bool) []NotificationPeerErr {
return ng.Wait() return ng.Wait()
} }
// DeletePolicy - deletes policy across all peers.
func (sys *NotificationSys) DeletePolicy(policyName string) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(context.Background(), func() error {
return client.DeletePolicy(policyName)
}, idx, *client.host)
}
return ng.Wait()
}
// LoadPolicy - reloads a specific modified policy across all peers
func (sys *NotificationSys) LoadPolicy(policyName string) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(context.Background(), func() error {
return client.LoadPolicy(policyName)
}, idx, *client.host)
}
return ng.Wait()
}
// DeleteUser - deletes a specific user across all peers
func (sys *NotificationSys) DeleteUser(accessKey string) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(context.Background(), func() error {
return client.DeleteUser(accessKey)
}, idx, *client.host)
}
return ng.Wait()
}
// LoadUser - reloads a specific user across all peers
func (sys *NotificationSys) LoadUser(accessKey string, temp bool) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(context.Background(), func() error {
return client.LoadUser(accessKey, temp)
}, idx, *client.host)
}
return ng.Wait()
}
// LoadUsers - calls LoadUsers RPC call on all peers. // LoadUsers - calls LoadUsers RPC call on all peers.
func (sys *NotificationSys) LoadUsers() []NotificationPeerErr { func (sys *NotificationSys) LoadUsers() []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients)) ng := WithNPeers(len(sys.peerClients))

View File

@ -23,6 +23,7 @@ import (
"encoding/gob" "encoding/gob"
"io" "io"
"net/url" "net/url"
"strconv"
"github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@ -337,6 +338,59 @@ func (client *peerRESTClient) PutBucketNotification(bucket string, rulesMap even
return nil return nil
} }
// DeletePolicy - delete a specific canned policy.
func (client *peerRESTClient) DeletePolicy(policyName string) (err error) {
values := make(url.Values)
values.Set(peerRESTPolicy, policyName)
respBody, err := client.call(peerRESTMethodDeletePolicy, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// LoadPolicy - reload a specific canned policy.
func (client *peerRESTClient) LoadPolicy(policyName string) (err error) {
values := make(url.Values)
values.Set(peerRESTPolicy, policyName)
respBody, err := client.call(peerRESTMethodLoadPolicy, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// DeleteUser - delete a specific user.
func (client *peerRESTClient) DeleteUser(accessKey string) (err error) {
values := make(url.Values)
values.Set(peerRESTUser, accessKey)
respBody, err := client.call(peerRESTMethodDeleteUser, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// LoadUser - reload a specific user.
func (client *peerRESTClient) LoadUser(accessKey string, temp bool) (err error) {
values := make(url.Values)
values.Set(peerRESTUser, accessKey)
values.Set(peerRESTUserTemp, strconv.FormatBool(temp))
respBody, err := client.call(peerRESTMethodLoadUser, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// LoadUsers - send load users command to peer nodes. // LoadUsers - send load users command to peer nodes.
func (client *peerRESTClient) LoadUsers() (err error) { func (client *peerRESTClient) LoadUsers() (err error) {
respBody, err := client.call(peerRESTMethodLoadUsers, nil, nil, -1) respBody, err := client.call(peerRESTMethodLoadUsers, nil, nil, -1)

View File

@ -16,7 +16,7 @@
package cmd package cmd
const peerRESTVersion = "v1" const peerRESTVersion = "v2"
const peerRESTPath = minioReservedBucketPath + "/peer/" + peerRESTVersion const peerRESTPath = minioReservedBucketPath + "/peer/" + peerRESTVersion
const ( const (
@ -28,6 +28,10 @@ const (
peerRESTMethodSignalService = "signalservice" peerRESTMethodSignalService = "signalservice"
peerRESTMethodGetLocks = "getlocks" peerRESTMethodGetLocks = "getlocks"
peerRESTMethodBucketPolicyRemove = "removebucketpolicy" peerRESTMethodBucketPolicyRemove = "removebucketpolicy"
peerRESTMethodLoadUser = "loaduser"
peerRESTMethodDeleteUser = "deleteuser"
peerRESTMethodLoadPolicy = "loadpolicy"
peerRESTMethodDeletePolicy = "deletepolicy"
peerRESTMethodLoadUsers = "loadusers" peerRESTMethodLoadUsers = "loadusers"
peerRESTMethodStartProfiling = "startprofiling" peerRESTMethodStartProfiling = "startprofiling"
peerRESTMethodDownloadProfilingData = "downloadprofilingdata" peerRESTMethodDownloadProfilingData = "downloadprofilingdata"
@ -41,6 +45,9 @@ const (
const ( const (
peerRESTBucket = "bucket" peerRESTBucket = "bucket"
peerRESTUser = "user"
peerRESTUserTemp = "user-temp"
peerRESTPolicy = "policy"
peerRESTSignal = "signal" peerRESTSignal = "signal"
peerRESTProfiler = "profiler" peerRESTProfiler = "profiler"
peerRESTDryRun = "dry-run" peerRESTDryRun = "dry-run"

View File

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
@ -117,7 +118,125 @@ func (s *peerRESTServer) GetLocksHandler(w http.ResponseWriter, r *http.Request)
} }
// LoadUsersHandler - returns server info. // DeletePolicyHandler - deletes a policy on the server.
func (s *peerRESTServer) DeletePolicyHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
policyName := vars[peerRESTPolicy]
if policyName == "" {
s.writeErrorResponse(w, errors.New("policyName is missing"))
return
}
if err := globalIAMSys.DeletePolicy(policyName); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// LoadPolicyHandler - reloads a policy on the server.
func (s *peerRESTServer) LoadPolicyHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
policyName := vars[peerRESTPolicy]
if policyName == "" {
s.writeErrorResponse(w, errors.New("policyName is missing"))
return
}
if err := globalIAMSys.LoadPolicy(objAPI, policyName); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// DeleteUserHandler - deletes a user on the server.
func (s *peerRESTServer) DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
accessKey := vars[peerRESTUser]
if accessKey == "" {
s.writeErrorResponse(w, errors.New("username is missing"))
return
}
if err := globalIAMSys.DeleteUser(accessKey); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// LoadUserHandler - reloads a user on the server.
func (s *peerRESTServer) LoadUserHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
accessKey := vars[peerRESTUser]
if accessKey == "" {
s.writeErrorResponse(w, errors.New("username is missing"))
return
}
temp, err := strconv.ParseBool(vars[peerRESTUserTemp])
if err != nil {
s.writeErrorResponse(w, err)
return
}
if err = globalIAMSys.LoadUser(objAPI, accessKey, temp); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// LoadUsersHandler - reloads all users and canned policies.
func (s *peerRESTServer) LoadUsersHandler(w http.ResponseWriter, r *http.Request) { func (s *peerRESTServer) LoadUsersHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) { if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request")) s.writeErrorResponse(w, errors.New("Invalid request"))
@ -576,6 +695,10 @@ func registerPeerRESTHandlers(router *mux.Router) {
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicyRemove).HandlerFunc(httpTraceAll(server.RemoveBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...) subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicyRemove).HandlerFunc(httpTraceAll(server.RemoveBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicySet).HandlerFunc(httpTraceHdrs(server.SetBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...) subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodBucketPolicySet).HandlerFunc(httpTraceHdrs(server.SetBucketPolicyHandler)).Queries(restQueries(peerRESTBucket)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodDeletePolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadPolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodDeleteUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser, peerRESTUserTemp)...)
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadUsers).HandlerFunc(httpTraceAll(server.LoadUsersHandler)) subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodLoadUsers).HandlerFunc(httpTraceAll(server.LoadUsersHandler))
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodStartProfiling).HandlerFunc(httpTraceAll(server.StartProfilingHandler)).Queries(restQueries(peerRESTProfiler)...) subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodStartProfiling).HandlerFunc(httpTraceAll(server.StartProfilingHandler)).Queries(restQueries(peerRESTProfiler)...)

View File

@ -189,7 +189,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
} }
// Notify all other MinIO peers to reload temp users // Notify all other MinIO peers to reload temp users
for _, nerr := range globalNotificationSys.LoadUsers() { for _, nerr := range globalNotificationSys.LoadUser(cred.AccessKey, true) {
if nerr.Err != nil { if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err) logger.LogIf(ctx, nerr.Err)
@ -306,7 +306,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
} }
// Notify all other MinIO peers to reload temp users // Notify all other MinIO peers to reload temp users
for _, nerr := range globalNotificationSys.LoadUsers() { for _, nerr := range globalNotificationSys.LoadUser(cred.AccessKey, true) {
if nerr.Err != nil { if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err) logger.LogIf(ctx, nerr.Err)