Use etcd watch to reload IAM users (#7551)

Currently we used to reload users every five minutes,
regardless of etcd is configured or not. But with etcd
configured we can do this more asynchronously to trigger
a refresh by using the watch API

Fixes #7515
This commit is contained in:
Harshavardhana 2019-04-26 06:18:50 -07:00 committed by Nitish Tiwari
parent 27ef1262bf
commit 83ca1a8d64
2 changed files with 108 additions and 40 deletions

View File

@ -21,6 +21,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"time"
etcd "github.com/coreos/etcd/clientv3" etcd "github.com/coreos/etcd/clientv3"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@ -105,12 +106,26 @@ func readConfigEtcd(ctx context.Context, client *etcd.Client, configFile string)
// watchConfigEtcd - watches for changes on `configFile` on etcd and loads them. // watchConfigEtcd - watches for changes on `configFile` on etcd and loads them.
func watchConfigEtcd(objAPI ObjectLayer, configFile string, loadCfgFn func(ObjectLayer) error) { func watchConfigEtcd(objAPI ObjectLayer, configFile string, loadCfgFn func(ObjectLayer) error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) for {
defer cancel() watchCh := globalEtcdClient.Watch(context.Background(), iamConfigPrefix)
for watchResp := range globalEtcdClient.Watch(ctx, configFile) { select {
for _, event := range watchResp.Events { case <-GlobalServiceDoneCh:
if event.IsModify() || event.IsCreate() { return
loadCfgFn(objAPI) case watchResp, ok := <-watchCh:
if !ok {
time.Sleep(1 * time.Second)
continue
}
if err := watchResp.Err(); err != nil {
logger.LogIf(context.Background(), err)
// log and retry.
time.Sleep(1 * time.Second)
continue
}
for _, event := range watchResp.Events {
if event.IsModify() || event.IsCreate() {
loadCfgFn(objAPI)
}
} }
} }
} }

View File

@ -62,6 +62,9 @@ type IAMSys struct {
// 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 {
return sys.refreshEtcd()
}
return sys.refresh(objAPI) return sys.refresh(objAPI)
} }
@ -71,21 +74,52 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
return errInvalidArgument return errInvalidArgument
} }
defer func() { if globalEtcdClient != nil {
// Refresh IAMSys in background. defer func() {
go func() { go func() {
ticker := time.NewTicker(globalRefreshIAMInterval) // Refresh IAMSys with etcd watch.
defer ticker.Stop() for {
for { watchCh := globalEtcdClient.Watch(context.Background(), iamConfigPrefix)
select { select {
case <-GlobalServiceDoneCh: case <-GlobalServiceDoneCh:
return return
case <-ticker.C: case watchResp, ok := <-watchCh:
sys.refresh(objAPI) if !ok {
time.Sleep(1 * time.Second)
continue
}
if err := watchResp.Err(); err != nil {
logger.LogIf(context.Background(), err)
// log and retry.
time.Sleep(1 * time.Second)
continue
}
for _, event := range watchResp.Events {
if event.IsModify() || event.IsCreate() || event.Type == etcd.EventTypeDelete {
sys.refreshEtcd()
}
}
}
} }
} }()
}() }()
}() } else {
defer func() {
// Refresh IAMSys in background.
go func() {
ticker := time.NewTicker(globalRefreshIAMInterval)
defer ticker.Stop()
for {
select {
case <-GlobalServiceDoneCh:
return
case <-ticker.C:
sys.refresh(objAPI)
}
}
}()
}()
}
doneCh := make(chan struct{}) doneCh := make(chan struct{})
defer close(doneCh) defer close(doneCh)
@ -95,6 +129,9 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
// - Read quorum is lost just after the initialization // - Read quorum is lost just after the initialization
// of the object layer. // of the object layer.
for range newRetryTimerSimple(doneCh) { for range newRetryTimerSimple(doneCh) {
if globalEtcdClient != nil {
return sys.refreshEtcd()
}
// Load IAMSys once during boot. // Load IAMSys once during boot.
if err := sys.refresh(objAPI); err != nil { if err := sys.refresh(objAPI); err != nil {
if err == errDiskNotFound || if err == errDiskNotFound ||
@ -697,35 +734,51 @@ func setDefaultCannedPolicies(policies map[string]iampolicy.Policy) {
} }
} }
func (sys *IAMSys) refreshEtcd() error {
iamUsersMap := make(map[string]auth.Credentials)
iamPolicyMap := make(map[string]string)
iamCannedPolicyMap := make(map[string]iampolicy.Policy)
if err := reloadEtcdPolicies(iamConfigPoliciesPrefix, iamCannedPolicyMap); err != nil {
return err
}
if err := reloadEtcdUsers(iamConfigUsersPrefix, iamUsersMap, iamPolicyMap); err != nil {
return err
}
if err := reloadEtcdUsers(iamConfigSTSPrefix, iamUsersMap, iamPolicyMap); err != nil {
return err
}
// Sets default canned policies, if none are set.
setDefaultCannedPolicies(iamCannedPolicyMap)
sys.Lock()
defer sys.Unlock()
sys.iamUsersMap = iamUsersMap
sys.iamPolicyMap = iamPolicyMap
sys.iamCannedPolicyMap = iamCannedPolicyMap
return nil
}
// Refresh IAMSys. // Refresh IAMSys.
func (sys *IAMSys) refresh(objAPI ObjectLayer) error { func (sys *IAMSys) refresh(objAPI ObjectLayer) error {
iamUsersMap := make(map[string]auth.Credentials) iamUsersMap := make(map[string]auth.Credentials)
iamPolicyMap := make(map[string]string) iamPolicyMap := make(map[string]string)
iamCannedPolicyMap := make(map[string]iampolicy.Policy) iamCannedPolicyMap := make(map[string]iampolicy.Policy)
if globalEtcdClient != nil { if err := reloadPolicies(objAPI, iamConfigPoliciesPrefix, iamCannedPolicyMap); err != nil {
if err := reloadEtcdPolicies(iamConfigPoliciesPrefix, iamCannedPolicyMap); err != nil { return err
return err }
} if err := reloadUsers(objAPI, iamConfigUsersPrefix, iamUsersMap, iamPolicyMap); err != nil {
if err := reloadEtcdUsers(iamConfigUsersPrefix, iamUsersMap, iamPolicyMap); err != nil { return err
return err }
} if err := reloadUsers(objAPI, iamConfigSTSPrefix, iamUsersMap, iamPolicyMap); err != nil {
if err := reloadEtcdUsers(iamConfigSTSPrefix, iamUsersMap, iamPolicyMap); err != nil { return err
return err
}
} else {
if err := reloadPolicies(objAPI, iamConfigPoliciesPrefix, iamCannedPolicyMap); err != nil {
return err
}
if err := reloadUsers(objAPI, iamConfigUsersPrefix, iamUsersMap, iamPolicyMap); err != nil {
return err
}
if err := reloadUsers(objAPI, iamConfigSTSPrefix, iamUsersMap, iamPolicyMap); err != nil {
return err
}
} }
// Sets default canned policies, if none set. // Sets default canned policies, if none are set.
setDefaultCannedPolicies(iamCannedPolicyMap) setDefaultCannedPolicies(iamCannedPolicyMap)
sys.Lock() sys.Lock()