mirror of
https://github.com/minio/minio.git
synced 2025-01-11 23:13:23 -05:00
Add IAM groups support (#7981)
This change adds admin APIs and IAM subsystem APIs to: - add or remove members to a group (group addition and deletion is implicit on add and remove) - enable/disable a group - list and fetch group info
This commit is contained in:
parent
5cd9f10a02
commit
414a7eca83
@ -23,6 +23,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
@ -1019,6 +1020,130 @@ func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeSuccessResponseJSON(w, econfigData)
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateGroupMembers - PUT /minio/admin/v1/update-group-members
|
||||||
|
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "UpdateGroupMembers")
|
||||||
|
|
||||||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
data, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var updReq madmin.GroupAddRemove
|
||||||
|
err = json.Unmarshal(data, &updReq)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if updReq.IsRemove {
|
||||||
|
err = globalIAMSys.RemoveUsersFromGroup(updReq.Group, updReq.Members)
|
||||||
|
} else {
|
||||||
|
err = globalIAMSys.AddUsersToGroup(updReq.Group, updReq.Members)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to load group.
|
||||||
|
for _, nerr := range globalNotificationSys.LoadGroup(updReq.Group) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroup - /minio/admin/v1/group?group=mygroup1
|
||||||
|
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "GetGroup")
|
||||||
|
|
||||||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
group := vars["group"]
|
||||||
|
|
||||||
|
gdesc, err := globalIAMSys.GetGroupDescription(group)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(gdesc)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListGroups - GET /minio/admin/v1/groups
|
||||||
|
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "ListGroups")
|
||||||
|
|
||||||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := globalIAMSys.ListGroups()
|
||||||
|
body, err := json.Marshal(groups)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGroupStatus - PUT /minio/admin/v1/set-group-status?group=mygroup1&status=enabled
|
||||||
|
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SetGroupStatus")
|
||||||
|
|
||||||
|
objectAPI := validateAdminReq(ctx, w, r)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
group := vars["group"]
|
||||||
|
status := vars["status"]
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if status == statusEnabled {
|
||||||
|
err = globalIAMSys.SetGroupStatus(group, true)
|
||||||
|
} else if status == statusDisabled {
|
||||||
|
err = globalIAMSys.SetGroupStatus(group, false)
|
||||||
|
} else {
|
||||||
|
err = errInvalidArgument
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all other MinIO peers to reload user.
|
||||||
|
for _, nerr := range globalNotificationSys.LoadGroup(group) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetUserStatus - PUT /minio/admin/v1/set-user-status?accessKey=<access_key>&status=[enabled|disabled]
|
// SetUserStatus - PUT /minio/admin/v1/set-user-status?accessKey=<access_key>&status=[enabled|disabled]
|
||||||
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetUserStatus")
|
ctx := newContext(r, w, "SetUserStatus")
|
||||||
@ -1253,7 +1378,7 @@ func (a adminAPIHandlers) SetUserPolicy(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := globalIAMSys.PolicyDBSet(accessKey, policyName); err != nil {
|
if err := globalIAMSys.PolicyDBSet(accessKey, policyName, false); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +110,18 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
// List users
|
// List users
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/list-users").HandlerFunc(httpTraceHdrs(adminAPI.ListUsers))
|
adminV1Router.Methods(http.MethodGet).Path("/list-users").HandlerFunc(httpTraceHdrs(adminAPI.ListUsers))
|
||||||
|
|
||||||
|
// Add/Remove members from group
|
||||||
|
adminV1Router.Methods(http.MethodPut).Path("/update-group-members").HandlerFunc(httpTraceHdrs(adminAPI.UpdateGroupMembers))
|
||||||
|
|
||||||
|
// Get Group
|
||||||
|
adminV1Router.Methods(http.MethodGet).Path("/group").HandlerFunc(httpTraceHdrs(adminAPI.GetGroup)).Queries("group", "{group:.*}")
|
||||||
|
|
||||||
|
// List Groups
|
||||||
|
adminV1Router.Methods(http.MethodGet).Path("/groups").HandlerFunc(httpTraceHdrs(adminAPI.ListGroups))
|
||||||
|
|
||||||
|
// Set Group Status
|
||||||
|
adminV1Router.Methods(http.MethodPut).Path("/set-group-status").HandlerFunc(httpTraceHdrs(adminAPI.SetGroupStatus)).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
||||||
|
|
||||||
// List policies
|
// List policies
|
||||||
adminV1Router.Methods(http.MethodGet).Path("/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListCannedPolicies))
|
adminV1Router.Methods(http.MethodGet).Path("/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListCannedPolicies))
|
||||||
}
|
}
|
||||||
|
@ -203,6 +203,8 @@ const (
|
|||||||
|
|
||||||
ErrMalformedJSON
|
ErrMalformedJSON
|
||||||
ErrAdminNoSuchUser
|
ErrAdminNoSuchUser
|
||||||
|
ErrAdminNoSuchGroup
|
||||||
|
ErrAdminGroupNotEmpty
|
||||||
ErrAdminNoSuchPolicy
|
ErrAdminNoSuchPolicy
|
||||||
ErrAdminInvalidArgument
|
ErrAdminInvalidArgument
|
||||||
ErrAdminInvalidAccessKey
|
ErrAdminInvalidAccessKey
|
||||||
@ -923,6 +925,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The specified user does not exist.",
|
Description: "The specified user does not exist.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
ErrAdminNoSuchGroup: {
|
||||||
|
Code: "XMinioAdminNoSuchGroup",
|
||||||
|
Description: "The specified group does not exist.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
ErrAdminGroupNotEmpty: {
|
||||||
|
Code: "XMinioAdminGroupNotEmpty",
|
||||||
|
Description: "The specified group is not empty - cannot remove it.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminNoSuchPolicy: {
|
ErrAdminNoSuchPolicy: {
|
||||||
Code: "XMinioAdminNoSuchPolicy",
|
Code: "XMinioAdminNoSuchPolicy",
|
||||||
Description: "The canned policy does not exist.",
|
Description: "The canned policy does not exist.",
|
||||||
@ -1500,6 +1512,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrAdminInvalidArgument
|
apiErr = ErrAdminInvalidArgument
|
||||||
case errNoSuchUser:
|
case errNoSuchUser:
|
||||||
apiErr = ErrAdminNoSuchUser
|
apiErr = ErrAdminNoSuchUser
|
||||||
|
case errNoSuchGroup:
|
||||||
|
apiErr = ErrAdminNoSuchGroup
|
||||||
|
case errGroupNotEmpty:
|
||||||
|
apiErr = ErrAdminGroupNotEmpty
|
||||||
case errNoSuchPolicy:
|
case errNoSuchPolicy:
|
||||||
apiErr = ErrAdminNoSuchPolicy
|
apiErr = ErrAdminNoSuchPolicy
|
||||||
case errSignatureMismatch:
|
case errSignatureMismatch:
|
||||||
|
593
cmd/iam.go
593
cmd/iam.go
@ -42,6 +42,9 @@ const (
|
|||||||
// IAM users directory.
|
// IAM users directory.
|
||||||
iamConfigUsersPrefix = iamConfigPrefix + "/users/"
|
iamConfigUsersPrefix = iamConfigPrefix + "/users/"
|
||||||
|
|
||||||
|
// IAM groups directory.
|
||||||
|
iamConfigGroupsPrefix = iamConfigPrefix + "/groups/"
|
||||||
|
|
||||||
// IAM policies directory.
|
// IAM policies directory.
|
||||||
iamConfigPoliciesPrefix = iamConfigPrefix + "/policies/"
|
iamConfigPoliciesPrefix = iamConfigPrefix + "/policies/"
|
||||||
|
|
||||||
@ -52,6 +55,7 @@ const (
|
|||||||
iamConfigPolicyDBPrefix = iamConfigPrefix + "/policydb/"
|
iamConfigPolicyDBPrefix = iamConfigPrefix + "/policydb/"
|
||||||
iamConfigPolicyDBUsersPrefix = iamConfigPolicyDBPrefix + "users/"
|
iamConfigPolicyDBUsersPrefix = iamConfigPolicyDBPrefix + "users/"
|
||||||
iamConfigPolicyDBSTSUsersPrefix = iamConfigPolicyDBPrefix + "sts-users/"
|
iamConfigPolicyDBSTSUsersPrefix = iamConfigPolicyDBPrefix + "sts-users/"
|
||||||
|
iamConfigPolicyDBGroupsPrefix = iamConfigPolicyDBPrefix + "groups/"
|
||||||
|
|
||||||
// IAM identity file which captures identity credentials.
|
// IAM identity file which captures identity credentials.
|
||||||
iamIdentityFile = "identity.json"
|
iamIdentityFile = "identity.json"
|
||||||
@ -59,12 +63,20 @@ const (
|
|||||||
// IAM policy file which provides policies for each users.
|
// IAM policy file which provides policies for each users.
|
||||||
iamPolicyFile = "policy.json"
|
iamPolicyFile = "policy.json"
|
||||||
|
|
||||||
|
// IAM group members file
|
||||||
|
iamGroupMembersFile = "members.json"
|
||||||
|
|
||||||
// IAM format file
|
// IAM format file
|
||||||
iamFormatFile = "format.json"
|
iamFormatFile = "format.json"
|
||||||
|
|
||||||
iamFormatVersion1 = 1
|
iamFormatVersion1 = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
statusEnabled = "enabled"
|
||||||
|
statusDisabled = "disabled"
|
||||||
|
)
|
||||||
|
|
||||||
type iamFormat struct {
|
type iamFormat struct {
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
}
|
}
|
||||||
@ -85,25 +97,23 @@ func getUserIdentityPath(user string, isSTS bool) string {
|
|||||||
return pathJoin(basePath, user, iamIdentityFile)
|
return pathJoin(basePath, user, iamIdentityFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getGroupInfoPath(group string) string {
|
||||||
|
return pathJoin(iamConfigGroupsPrefix, group, iamGroupMembersFile)
|
||||||
|
}
|
||||||
|
|
||||||
func getPolicyDocPath(name string) string {
|
func getPolicyDocPath(name string) string {
|
||||||
return pathJoin(iamConfigPoliciesPrefix, name, iamPolicyFile)
|
return pathJoin(iamConfigPoliciesPrefix, name, iamPolicyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMappedPolicyPath(name string, isSTS bool) string {
|
func getMappedPolicyPath(name string, isSTS, isGroup bool) string {
|
||||||
if isSTS {
|
switch {
|
||||||
|
case isSTS:
|
||||||
return pathJoin(iamConfigPolicyDBSTSUsersPrefix, name+".json")
|
return pathJoin(iamConfigPolicyDBSTSUsersPrefix, name+".json")
|
||||||
|
case isGroup:
|
||||||
|
return pathJoin(iamConfigPolicyDBGroupsPrefix, name+".json")
|
||||||
|
default:
|
||||||
|
return pathJoin(iamConfigPolicyDBUsersPrefix, name+".json")
|
||||||
}
|
}
|
||||||
return pathJoin(iamConfigPolicyDBUsersPrefix, name+".json")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MappedPolicy represents a policy name mapped to a user or group
|
|
||||||
type MappedPolicy struct {
|
|
||||||
Version int `json:"version"`
|
|
||||||
Policy string `json:"policy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMappedPolicy(policy string) MappedPolicy {
|
|
||||||
return MappedPolicy{Version: 1, Policy: policy}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserIdentity represents a user's secret key and their status
|
// UserIdentity represents a user's secret key and their status
|
||||||
@ -116,6 +126,27 @@ func newUserIdentity(creds auth.Credentials) UserIdentity {
|
|||||||
return UserIdentity{Version: 1, Credentials: creds}
|
return UserIdentity{Version: 1, Credentials: creds}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GroupInfo contains info about a group
|
||||||
|
type GroupInfo struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Members []string `json:"members"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGroupInfo(members []string) GroupInfo {
|
||||||
|
return GroupInfo{Version: 1, Status: statusEnabled, Members: members}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MappedPolicy represents a policy name mapped to a user or group
|
||||||
|
type MappedPolicy struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
Policy string `json:"policy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMappedPolicy(policy string) MappedPolicy {
|
||||||
|
return MappedPolicy{Version: 1, Policy: policy}
|
||||||
|
}
|
||||||
|
|
||||||
func loadIAMConfigItem(objectAPI ObjectLayer, item interface{}, path string) error {
|
func loadIAMConfigItem(objectAPI ObjectLayer, item interface{}, path string) error {
|
||||||
data, err := readConfig(context.Background(), objectAPI, path)
|
data, err := readConfig(context.Background(), objectAPI, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -212,12 +243,18 @@ func saveIAMConfigItemEtcd(ctx context.Context, item interface{}, path string) e
|
|||||||
// IAMSys - config system.
|
// IAMSys - config system.
|
||||||
type IAMSys struct {
|
type IAMSys struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
// map of usernames to credentials
|
|
||||||
iamUsersMap map[string]auth.Credentials
|
|
||||||
// map of policy names to policy definitions
|
// map of policy names to policy definitions
|
||||||
iamPolicyDocsMap map[string]iampolicy.Policy
|
iamPolicyDocsMap map[string]iampolicy.Policy
|
||||||
|
// map of usernames to credentials
|
||||||
|
iamUsersMap map[string]auth.Credentials
|
||||||
|
// map of group names to group info
|
||||||
|
iamGroupsMap map[string]GroupInfo
|
||||||
|
// map of user names to groups they are a member of
|
||||||
|
iamUserGroupMemberships map[string]set.StringSet
|
||||||
// map of usernames/temporary access keys to policy names
|
// map of usernames/temporary access keys to policy names
|
||||||
iamUserPolicyMap map[string]MappedPolicy
|
iamUserPolicyMap map[string]MappedPolicy
|
||||||
|
// map of group names to policy names
|
||||||
|
iamGroupPolicyMap map[string]MappedPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPolicyDoc(objectAPI ObjectLayer, policy string, m map[string]iampolicy.Policy) error {
|
func loadPolicyDoc(objectAPI ObjectLayer, policy string, m map[string]iampolicy.Policy) error {
|
||||||
@ -260,7 +297,7 @@ func loadUser(objectAPI ObjectLayer, user string, isSTS bool,
|
|||||||
idFile := getUserIdentityPath(user, isSTS)
|
idFile := getUserIdentityPath(user, isSTS)
|
||||||
// Delete expired identity - ignoring errors here.
|
// Delete expired identity - ignoring errors here.
|
||||||
deleteConfig(context.Background(), objectAPI, idFile)
|
deleteConfig(context.Background(), objectAPI, idFile)
|
||||||
deleteConfig(context.Background(), objectAPI, getMappedPolicyPath(user, isSTS))
|
deleteConfig(context.Background(), objectAPI, getMappedPolicyPath(user, isSTS, false))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,11 +328,38 @@ func loadUsers(objectAPI ObjectLayer, isSTS bool, m map[string]auth.Credentials)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadMappedPolicy(objectAPI ObjectLayer, name string, isSTS bool,
|
func loadGroup(objectAPI ObjectLayer, group string, m map[string]GroupInfo) error {
|
||||||
|
var g GroupInfo
|
||||||
|
err := loadIAMConfigItem(objectAPI, &g, getGroupInfoPath(group))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m[group] = g
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadGroups(objectAPI ObjectLayer, m map[string]GroupInfo) error {
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
defer close(doneCh)
|
||||||
|
for item := range listIAMConfigItems(objectAPI, iamConfigGroupsPrefix, true, doneCh) {
|
||||||
|
if item.Err != nil {
|
||||||
|
return item.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
group := item.Item
|
||||||
|
err := loadGroup(objectAPI, group, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMappedPolicy(objectAPI ObjectLayer, name string, isSTS, isGroup bool,
|
||||||
m map[string]MappedPolicy) error {
|
m map[string]MappedPolicy) error {
|
||||||
|
|
||||||
var p MappedPolicy
|
var p MappedPolicy
|
||||||
err := loadIAMConfigItem(objectAPI, &p, getMappedPolicyPath(name, isSTS))
|
err := loadIAMConfigItem(objectAPI, &p, getMappedPolicyPath(name, isSTS, isGroup))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -303,7 +367,7 @@ func loadMappedPolicy(objectAPI ObjectLayer, name string, isSTS bool,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadMappedPolicies(objectAPI ObjectLayer, isSTS bool, m map[string]MappedPolicy) error {
|
func loadMappedPolicies(objectAPI ObjectLayer, isSTS, isGroup bool, m map[string]MappedPolicy) error {
|
||||||
doneCh := make(chan struct{})
|
doneCh := make(chan struct{})
|
||||||
defer close(doneCh)
|
defer close(doneCh)
|
||||||
basePath := iamConfigPolicyDBUsersPrefix
|
basePath := iamConfigPolicyDBUsersPrefix
|
||||||
@ -317,7 +381,7 @@ func loadMappedPolicies(objectAPI ObjectLayer, isSTS bool, m map[string]MappedPo
|
|||||||
|
|
||||||
policyFile := item.Item
|
policyFile := item.Item
|
||||||
userOrGroupName := strings.TrimSuffix(policyFile, ".json")
|
userOrGroupName := strings.TrimSuffix(policyFile, ".json")
|
||||||
err := loadMappedPolicy(objectAPI, userOrGroupName, isSTS, m)
|
err := loadMappedPolicy(objectAPI, userOrGroupName, isSTS, isGroup, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -325,6 +389,51 @@ func loadMappedPolicies(objectAPI ObjectLayer, isSTS bool, m map[string]MappedPo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadGroup - loads a specific group from storage, and updates the
|
||||||
|
// memberships cache. If the specified group does not exist in
|
||||||
|
// storage, it is removed from in-memory maps as well - this
|
||||||
|
// simplifies the implementation for group removal. This is called
|
||||||
|
// only via IAM notifications.
|
||||||
|
func (sys *IAMSys) LoadGroup(objAPI ObjectLayer, group string) error {
|
||||||
|
if objAPI == nil {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
defer sys.Unlock()
|
||||||
|
|
||||||
|
if globalEtcdClient != nil {
|
||||||
|
// Watch APIs cover this case, so nothing to do.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := loadGroup(objAPI, group, sys.iamGroupsMap)
|
||||||
|
if err != nil && err != errConfigNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == errConfigNotFound {
|
||||||
|
// group does not exist - so remove from memory.
|
||||||
|
sys.removeGroupFromMembershipsMap(group)
|
||||||
|
delete(sys.iamGroupsMap, group)
|
||||||
|
delete(sys.iamGroupPolicyMap, group)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gi := sys.iamGroupsMap[group]
|
||||||
|
|
||||||
|
// Updating the group memberships cache happens in two steps:
|
||||||
|
//
|
||||||
|
// 1. Remove the group from each user's list of memberships.
|
||||||
|
// 2. Add the group to each member's list of memberships.
|
||||||
|
//
|
||||||
|
// This ensures that regardless of members being added or
|
||||||
|
// removed, the cache stays current.
|
||||||
|
sys.removeGroupFromMembershipsMap(group)
|
||||||
|
sys.updateGroupMembershipsMap(group, &gi)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadPolicy - reloads a specific canned policy from backend disks or etcd.
|
// LoadPolicy - reloads a specific canned policy from backend disks or etcd.
|
||||||
func (sys *IAMSys) LoadPolicy(objAPI ObjectLayer, policyName string) error {
|
func (sys *IAMSys) LoadPolicy(objAPI ObjectLayer, policyName string) error {
|
||||||
if objAPI == nil {
|
if objAPI == nil {
|
||||||
@ -356,7 +465,7 @@ func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, isSTS bool) er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = loadMappedPolicy(objAPI, accessKey, isSTS, sys.iamUserPolicyMap)
|
err = loadMappedPolicy(objAPI, accessKey, isSTS, false, sys.iamUserPolicyMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -378,6 +487,7 @@ func (sys *IAMSys) reloadFromEvent(event *etcd.Event) {
|
|||||||
eventCreate := event.IsModify() || event.IsCreate()
|
eventCreate := event.IsModify() || event.IsCreate()
|
||||||
eventDelete := event.Type == etcd.EventTypeDelete
|
eventDelete := event.Type == etcd.EventTypeDelete
|
||||||
usersPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigUsersPrefix)
|
usersPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigUsersPrefix)
|
||||||
|
groupsPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigGroupsPrefix)
|
||||||
stsPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigSTSPrefix)
|
stsPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigSTSPrefix)
|
||||||
policyPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigPoliciesPrefix)
|
policyPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigPoliciesPrefix)
|
||||||
policyDBUsersPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigPolicyDBUsersPrefix)
|
policyDBUsersPrefix := strings.HasPrefix(string(event.Kv.Key), iamConfigPolicyDBUsersPrefix)
|
||||||
@ -398,6 +508,13 @@ func (sys *IAMSys) reloadFromEvent(event *etcd.Event) {
|
|||||||
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
iamConfigSTSPrefix))
|
iamConfigSTSPrefix))
|
||||||
loadEtcdUser(ctx, accessKey, true, sys.iamUsersMap)
|
loadEtcdUser(ctx, accessKey, true, sys.iamUsersMap)
|
||||||
|
case groupsPrefix:
|
||||||
|
group := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
|
iamConfigGroupsPrefix))
|
||||||
|
loadEtcdGroup(ctx, group, sys.iamGroupsMap)
|
||||||
|
gi := sys.iamGroupsMap[group]
|
||||||
|
sys.removeGroupFromMembershipsMap(group)
|
||||||
|
sys.updateGroupMembershipsMap(group, &gi)
|
||||||
case policyPrefix:
|
case policyPrefix:
|
||||||
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
iamConfigPoliciesPrefix))
|
iamConfigPoliciesPrefix))
|
||||||
@ -406,12 +523,12 @@ func (sys *IAMSys) reloadFromEvent(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")
|
||||||
loadEtcdMappedPolicy(ctx, user, false, sys.iamUserPolicyMap)
|
loadEtcdMappedPolicy(ctx, user, false, 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")
|
||||||
loadEtcdMappedPolicy(ctx, user, true, sys.iamUserPolicyMap)
|
loadEtcdMappedPolicy(ctx, user, true, false, sys.iamUserPolicyMap)
|
||||||
}
|
}
|
||||||
case eventDelete:
|
case eventDelete:
|
||||||
switch {
|
switch {
|
||||||
@ -423,6 +540,12 @@ func (sys *IAMSys) reloadFromEvent(event *etcd.Event) {
|
|||||||
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
accessKey := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
iamConfigSTSPrefix))
|
iamConfigSTSPrefix))
|
||||||
delete(sys.iamUsersMap, accessKey)
|
delete(sys.iamUsersMap, accessKey)
|
||||||
|
case groupsPrefix:
|
||||||
|
group := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
|
iamConfigGroupsPrefix))
|
||||||
|
sys.removeGroupFromMembershipsMap(group)
|
||||||
|
delete(sys.iamGroupsMap, group)
|
||||||
|
delete(sys.iamGroupPolicyMap, group)
|
||||||
case policyPrefix:
|
case policyPrefix:
|
||||||
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
policyName := path.Dir(strings.TrimPrefix(string(event.Kv.Key),
|
||||||
iamConfigPoliciesPrefix))
|
iamConfigPoliciesPrefix))
|
||||||
@ -541,7 +664,7 @@ func migrateUsersConfigToV1(objAPI ObjectLayer, isSTS bool) error {
|
|||||||
|
|
||||||
// 2. copy policy file to new location.
|
// 2. copy policy file to new location.
|
||||||
mp := newMappedPolicy(policyName)
|
mp := newMappedPolicy(policyName)
|
||||||
path := getMappedPolicyPath(user, isSTS)
|
path := getMappedPolicyPath(user, isSTS, false)
|
||||||
if err := saveIAMConfigItem(objAPI, mp, path); err != nil {
|
if err := saveIAMConfigItem(objAPI, mp, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -621,7 +744,7 @@ func migrateUsersConfigEtcdToV1(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)
|
path := getMappedPolicyPath(user, isSTS, false)
|
||||||
if err := saveIAMConfigItemEtcd(ctx, mp, path); err != nil {
|
if err := saveIAMConfigItemEtcd(ctx, mp, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -898,7 +1021,7 @@ func (sys *IAMSys) DeleteUser(accessKey string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
mappingPath := getMappedPolicyPath(accessKey, false)
|
mappingPath := getMappedPolicyPath(accessKey, false, false)
|
||||||
idPath := getUserIdentityPath(accessKey, false)
|
idPath := getUserIdentityPath(accessKey, false)
|
||||||
if globalEtcdClient != nil {
|
if globalEtcdClient != nil {
|
||||||
// It is okay to ignore errors when deleting policy.json for the user.
|
// It is okay to ignore errors when deleting policy.json for the user.
|
||||||
@ -950,7 +1073,7 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
|
|||||||
|
|
||||||
mp := newMappedPolicy(policyName)
|
mp := newMappedPolicy(policyName)
|
||||||
|
|
||||||
mappingPath := getMappedPolicyPath(accessKey, true)
|
mappingPath := getMappedPolicyPath(accessKey, true, false)
|
||||||
var err error
|
var err error
|
||||||
if globalEtcdClient != nil {
|
if globalEtcdClient != nil {
|
||||||
err = saveIAMConfigItemEtcd(context.Background(), mp, mappingPath)
|
err = saveIAMConfigItemEtcd(context.Background(), mp, mappingPath)
|
||||||
@ -1072,7 +1195,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(objectAPI, accessKey, uinfo.PolicyName, false)
|
return sys.policyDBSet(objectAPI, accessKey, uinfo.PolicyName, false, false)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1119,10 +1242,161 @@ func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
|||||||
return cred, ok && cred.IsValid()
|
return cred, ok && cred.IsValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
// PolicyDBSet - sets a policy for a user or group in the
|
// AddUsersToGroup - adds users to a group, creating the group if
|
||||||
// PolicyDB. This function applies only long-term users. For STS
|
// needed. No error if user(s) already are in the group.
|
||||||
// users, policy is set directly by called sys.policyDBSet().
|
func (sys *IAMSys) AddUsersToGroup(group string, members []string) error {
|
||||||
func (sys *IAMSys) PolicyDBSet(name, policy string) error {
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
return errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
if group == "" {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
defer sys.Unlock()
|
||||||
|
|
||||||
|
// Validate that all members exist.
|
||||||
|
for _, member := range members {
|
||||||
|
_, ok := sys.iamUsersMap[member]
|
||||||
|
if !ok {
|
||||||
|
return errNoSuchUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gi, ok := sys.iamGroupsMap[group]
|
||||||
|
if !ok {
|
||||||
|
// Set group as enabled by default when it doesn't
|
||||||
|
// exist.
|
||||||
|
gi = newGroupInfo(members)
|
||||||
|
} else {
|
||||||
|
mergedMembers := append(gi.Members, members...)
|
||||||
|
uniqMembers := set.CreateStringSet(mergedMembers...).ToSlice()
|
||||||
|
gi.Members = uniqMembers
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if globalEtcdClient == nil {
|
||||||
|
err = saveIAMConfigItem(objectAPI, gi, getGroupInfoPath(group))
|
||||||
|
} else {
|
||||||
|
err = saveIAMConfigItemEtcd(context.Background(), gi, getGroupInfoPath(group))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.iamGroupsMap[group] = gi
|
||||||
|
|
||||||
|
// update user-group membership map
|
||||||
|
for _, member := range members {
|
||||||
|
gset := sys.iamUserGroupMemberships[member]
|
||||||
|
if gset == nil {
|
||||||
|
gset = set.CreateStringSet(group)
|
||||||
|
} else {
|
||||||
|
gset.Add(group)
|
||||||
|
}
|
||||||
|
sys.iamUserGroupMemberships[member] = gset
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUsersFromGroup - remove users from group. If no users are
|
||||||
|
// given, and the group is empty, deletes the group as well.
|
||||||
|
func (sys *IAMSys) RemoveUsersFromGroup(group string, members []string) error {
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
return errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
if group == "" {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
defer sys.Unlock()
|
||||||
|
|
||||||
|
// Validate that all members exist.
|
||||||
|
for _, member := range members {
|
||||||
|
_, ok := sys.iamUsersMap[member]
|
||||||
|
if !ok {
|
||||||
|
return errNoSuchUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gi, ok := sys.iamGroupsMap[group]
|
||||||
|
if !ok {
|
||||||
|
return errNoSuchGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if attempting to delete a non-empty group.
|
||||||
|
if len(members) == 0 && len(gi.Members) != 0 {
|
||||||
|
return errGroupNotEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(members) == 0 {
|
||||||
|
// len(gi.Members) == 0 here.
|
||||||
|
|
||||||
|
// Remove the group from storage.
|
||||||
|
giPath := getGroupInfoPath(group)
|
||||||
|
giPolicyPath := getMappedPolicyPath(group, false, true)
|
||||||
|
if globalEtcdClient == nil {
|
||||||
|
err := deleteConfig(context.Background(), objectAPI, giPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove mapped policy from storage.
|
||||||
|
err = deleteConfig(context.Background(), objectAPI,
|
||||||
|
getMappedPolicyPath(group, false, true))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := deleteKeyEtcd(context.Background(), globalEtcdClient, giPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = deleteKeyEtcd(context.Background(), globalEtcdClient, giPolicyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete from server memory
|
||||||
|
delete(sys.iamGroupsMap, group)
|
||||||
|
delete(sys.iamGroupPolicyMap, group)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only removing members.
|
||||||
|
s := set.CreateStringSet(gi.Members...)
|
||||||
|
d := set.CreateStringSet(members...)
|
||||||
|
gi.Members = s.Difference(d).ToSlice()
|
||||||
|
|
||||||
|
err := saveIAMConfigItem(objectAPI, gi, getGroupInfoPath(group))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sys.iamGroupsMap[group] = gi
|
||||||
|
|
||||||
|
// update user-group membership map
|
||||||
|
for _, member := range members {
|
||||||
|
gset := sys.iamUserGroupMemberships[member]
|
||||||
|
if gset == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gset.Remove(group)
|
||||||
|
sys.iamUserGroupMemberships[member] = gset
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGroupStatus - enable/disabled a group
|
||||||
|
func (sys *IAMSys) SetGroupStatus(group string, enabled bool) error {
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
@ -1131,12 +1405,96 @@ func (sys *IAMSys) PolicyDBSet(name, policy string) error {
|
|||||||
sys.Lock()
|
sys.Lock()
|
||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
|
|
||||||
return sys.policyDBSet(objectAPI, name, policy, false)
|
if group == "" {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
gi, ok := sys.iamGroupsMap[group]
|
||||||
|
if !ok {
|
||||||
|
return errNoSuchGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabled {
|
||||||
|
gi.Status = statusEnabled
|
||||||
|
} else {
|
||||||
|
gi.Status = statusDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if globalEtcdClient == nil {
|
||||||
|
err = saveIAMConfigItem(objectAPI, gi, getGroupInfoPath(group))
|
||||||
|
} else {
|
||||||
|
err = saveIAMConfigItemEtcd(context.Background(), gi, getGroupInfoPath(group))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.iamGroupsMap[group] = gi
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroupDescription - builds up group description
|
||||||
|
func (sys *IAMSys) GetGroupDescription(group string) (gd madmin.GroupDesc, err error) {
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
|
|
||||||
|
gi, ok := sys.iamGroupsMap[group]
|
||||||
|
if !ok {
|
||||||
|
return gd, errNoSuchGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
var p []string
|
||||||
|
p, err = sys.policyDBGet(group, true)
|
||||||
|
if err != nil {
|
||||||
|
return gd, err
|
||||||
|
}
|
||||||
|
|
||||||
|
policy := ""
|
||||||
|
if len(p) > 0 {
|
||||||
|
policy = p[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return madmin.GroupDesc{
|
||||||
|
Name: group,
|
||||||
|
Status: gi.Status,
|
||||||
|
Members: gi.Members,
|
||||||
|
Policy: policy,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListGroups - lists groups
|
||||||
|
func (sys *IAMSys) ListGroups() (r []string) {
|
||||||
|
sys.RLock()
|
||||||
|
defer sys.RUnlock()
|
||||||
|
|
||||||
|
for k := range sys.iamGroupsMap {
|
||||||
|
r = append(r, k)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// PolicyDBSet - sets a policy for a user or group in the
|
||||||
|
// PolicyDB. This function applies only long-term users. For STS
|
||||||
|
// users, policy is set directly by called sys.policyDBSet().
|
||||||
|
func (sys *IAMSys) PolicyDBSet(name, policy string, isGroup bool) error {
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil {
|
||||||
|
return errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
defer sys.Unlock()
|
||||||
|
|
||||||
|
// isSTS is always false when called via PolicyDBSet as policy
|
||||||
|
// is never set by an external API call for STS users.
|
||||||
|
return sys.policyDBSet(objectAPI, name, policy, false, isGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
// policyDBSet - sets a policy for user in the policy db. Assumes that
|
// policyDBSet - sets a policy for user in the policy db. Assumes that
|
||||||
// caller has sys.Lock().
|
// caller has sys.Lock().
|
||||||
func (sys *IAMSys) policyDBSet(objectAPI ObjectLayer, name, policy string, isSTS bool) error {
|
func (sys *IAMSys) policyDBSet(objectAPI ObjectLayer, name, policy string, isSTS, isGroup bool) error {
|
||||||
if name == "" || policy == "" {
|
if name == "" || policy == "" {
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
@ -1156,7 +1514,7 @@ func (sys *IAMSys) policyDBSet(objectAPI ObjectLayer, name, policy string, isSTS
|
|||||||
return errNoSuchUser
|
return errNoSuchUser
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
mappingPath := getMappedPolicyPath(name, isSTS)
|
mappingPath := getMappedPolicyPath(name, isSTS, isGroup)
|
||||||
if globalEtcdClient != nil {
|
if globalEtcdClient != nil {
|
||||||
err = saveIAMConfigItemEtcd(context.Background(), mp, mappingPath)
|
err = saveIAMConfigItemEtcd(context.Background(), mp, mappingPath)
|
||||||
} else {
|
} else {
|
||||||
@ -1169,27 +1527,57 @@ func (sys *IAMSys) policyDBSet(objectAPI ObjectLayer, name, policy string, isSTS
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PolicyDBGet - gets policy set on a user
|
// PolicyDBGet - gets policy set on a user or group. Since a user may
|
||||||
func (sys *IAMSys) PolicyDBGet(name string) (string, error) {
|
// be a member of multiple groups, this function returns an array of
|
||||||
|
// applicable policies (each group is mapped to at most one policy).
|
||||||
|
func (sys *IAMSys) PolicyDBGet(name string, isGroup bool) ([]string, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return "", errInvalidArgument
|
return nil, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return "", errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.RLock()
|
sys.RLock()
|
||||||
defer sys.RUnlock()
|
defer sys.RUnlock()
|
||||||
|
|
||||||
if _, ok := sys.iamUsersMap[name]; !ok {
|
return sys.policyDBGet(name, isGroup)
|
||||||
return "", errNoSuchUser
|
}
|
||||||
|
|
||||||
|
// This call assumes that caller has the sys.RLock()
|
||||||
|
func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
|
||||||
|
if isGroup {
|
||||||
|
if _, ok := sys.iamGroupsMap[name]; !ok {
|
||||||
|
return nil, errNoSuchGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
policy := sys.iamGroupPolicyMap[name]
|
||||||
|
// returned policy could be empty
|
||||||
|
if policy.Policy == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return []string{policy.Policy}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := sys.iamUsersMap[name]; !ok {
|
||||||
|
return nil, errNoSuchUser
|
||||||
|
}
|
||||||
|
|
||||||
|
result := []string{}
|
||||||
policy := sys.iamUserPolicyMap[name]
|
policy := sys.iamUserPolicyMap[name]
|
||||||
// returned policy could be empty
|
// returned policy could be empty
|
||||||
return policy.Policy, nil
|
if policy.Policy != "" {
|
||||||
|
result = append(result, policy.Policy)
|
||||||
|
}
|
||||||
|
for _, group := range sys.iamUserGroupMemberships[name].ToSlice() {
|
||||||
|
p, ok := sys.iamGroupPolicyMap[group]
|
||||||
|
if ok && p.Policy != "" {
|
||||||
|
result = append(result, p.Policy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllowedSTS is meant for STS based temporary credentials,
|
// IsAllowedSTS is meant for STS based temporary credentials,
|
||||||
@ -1326,11 +1714,11 @@ func etcdKvsToSetPolicyDB(prefix string, kvs []*mvccpb.KeyValue) set.StringSet {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadEtcdMappedPolicy(ctx context.Context, name string, isSTS bool,
|
func loadEtcdMappedPolicy(ctx context.Context, name string, isSTS, isGroup bool,
|
||||||
m map[string]MappedPolicy) error {
|
m map[string]MappedPolicy) error {
|
||||||
|
|
||||||
var p MappedPolicy
|
var p MappedPolicy
|
||||||
err := loadIAMConfigItemEtcd(ctx, &p, getMappedPolicyPath(name, isSTS))
|
err := loadIAMConfigItemEtcd(ctx, &p, getMappedPolicyPath(name, isSTS, isGroup))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1338,7 +1726,7 @@ func loadEtcdMappedPolicy(ctx context.Context, name string, isSTS bool,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadEtcdMappedPolicies(isSTS bool, m map[string]MappedPolicy) error {
|
func loadEtcdMappedPolicies(isSTS, 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()
|
||||||
basePrefix := iamConfigPolicyDBUsersPrefix
|
basePrefix := iamConfigPolicyDBUsersPrefix
|
||||||
@ -1354,7 +1742,7 @@ func loadEtcdMappedPolicies(isSTS bool, m map[string]MappedPolicy) error {
|
|||||||
|
|
||||||
// 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 = loadEtcdMappedPolicy(ctx, user, isSTS, m); err != nil {
|
if err = loadEtcdMappedPolicy(ctx, user, isSTS, isGroup, m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1371,7 +1759,7 @@ func loadEtcdUser(ctx context.Context, user string, isSTS bool, m map[string]aut
|
|||||||
if u.Credentials.IsExpired() {
|
if u.Credentials.IsExpired() {
|
||||||
// Delete expired identity.
|
// Delete expired identity.
|
||||||
deleteKeyEtcd(ctx, globalEtcdClient, getUserIdentityPath(user, isSTS))
|
deleteKeyEtcd(ctx, globalEtcdClient, getUserIdentityPath(user, isSTS))
|
||||||
deleteKeyEtcd(ctx, globalEtcdClient, getMappedPolicyPath(user, isSTS))
|
deleteKeyEtcd(ctx, globalEtcdClient, getMappedPolicyPath(user, isSTS, false))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1394,7 +1782,7 @@ func loadEtcdUsers(isSTS bool, m map[string]auth.Credentials) error {
|
|||||||
|
|
||||||
users := etcdKvsToSet(basePrefix, r.Kvs)
|
users := etcdKvsToSet(basePrefix, r.Kvs)
|
||||||
|
|
||||||
// Reload config and policies for all users.
|
// Reload config for all users.
|
||||||
for _, user := range users.ToSlice() {
|
for _, user := range users.ToSlice() {
|
||||||
if err = loadEtcdUser(ctx, user, isSTS, m); err != nil {
|
if err = loadEtcdUser(ctx, user, isSTS, m); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1403,6 +1791,35 @@ func loadEtcdUsers(isSTS bool, m map[string]auth.Credentials) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadEtcdGroup(ctx context.Context, group string, m map[string]GroupInfo) error {
|
||||||
|
var gi GroupInfo
|
||||||
|
err := loadIAMConfigItemEtcd(ctx, &gi, getGroupInfoPath(group))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m[group] = gi
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadEtcdGroups(m map[string]GroupInfo) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
|
||||||
|
defer cancel()
|
||||||
|
r, err := globalEtcdClient.Get(ctx, iamConfigGroupsPrefix, etcd.WithPrefix(), etcd.WithKeysOnly())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := etcdKvsToSet(iamConfigGroupsPrefix, r.Kvs)
|
||||||
|
|
||||||
|
// Reload config for all groups.
|
||||||
|
for _, group := range groups.ToSlice() {
|
||||||
|
if err = loadEtcdGroup(ctx, group, m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadEtcdPolicy(ctx context.Context, policyName string, m map[string]iampolicy.Policy) error {
|
func loadEtcdPolicy(ctx context.Context, policyName string, m map[string]iampolicy.Policy) error {
|
||||||
var p iampolicy.Policy
|
var p iampolicy.Policy
|
||||||
err := loadIAMConfigItemEtcd(ctx, &p, getPolicyDocPath(policyName))
|
err := loadIAMConfigItemEtcd(ctx, &p, getPolicyDocPath(policyName))
|
||||||
@ -1451,8 +1868,10 @@ func setDefaultCannedPolicies(policies map[string]iampolicy.Policy) {
|
|||||||
|
|
||||||
func (sys *IAMSys) refreshEtcd() error {
|
func (sys *IAMSys) refreshEtcd() error {
|
||||||
iamUsersMap := make(map[string]auth.Credentials)
|
iamUsersMap := make(map[string]auth.Credentials)
|
||||||
|
iamGroupsMap := make(map[string]GroupInfo)
|
||||||
iamPolicyDocsMap := make(map[string]iampolicy.Policy)
|
iamPolicyDocsMap := make(map[string]iampolicy.Policy)
|
||||||
iamUserPolicyMap := make(map[string]MappedPolicy)
|
iamUserPolicyMap := make(map[string]MappedPolicy)
|
||||||
|
iamGroupPolicyMap := make(map[string]MappedPolicy)
|
||||||
|
|
||||||
if err := loadEtcdPolicies(iamPolicyDocsMap); err != nil {
|
if err := loadEtcdPolicies(iamPolicyDocsMap); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1464,11 +1883,19 @@ func (sys *IAMSys) refreshEtcd() error {
|
|||||||
if err := loadEtcdUsers(true, iamUsersMap); err != nil {
|
if err := loadEtcdUsers(true, iamUsersMap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := loadEtcdMappedPolicies(false, iamUserPolicyMap); err != nil {
|
if err := loadEtcdGroups(iamGroupsMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := loadEtcdMappedPolicies(false, false, iamUserPolicyMap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// load STS users policy mapping into the same map
|
// load STS users policy mapping into the same map
|
||||||
if err := loadEtcdMappedPolicies(true, iamUserPolicyMap); err != nil {
|
if err := loadEtcdMappedPolicies(true, false, iamUserPolicyMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// load group policy mapping
|
||||||
|
if err := loadEtcdMappedPolicies(false, true, iamGroupPolicyMap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1479,8 +1906,10 @@ func (sys *IAMSys) refreshEtcd() error {
|
|||||||
defer sys.Unlock()
|
defer sys.Unlock()
|
||||||
|
|
||||||
sys.iamUsersMap = iamUsersMap
|
sys.iamUsersMap = iamUsersMap
|
||||||
|
sys.iamGroupsMap = iamGroupsMap
|
||||||
sys.iamUserPolicyMap = iamUserPolicyMap
|
sys.iamUserPolicyMap = iamUserPolicyMap
|
||||||
sys.iamPolicyDocsMap = iamPolicyDocsMap
|
sys.iamPolicyDocsMap = iamPolicyDocsMap
|
||||||
|
sys.iamGroupPolicyMap = iamGroupPolicyMap
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1488,8 +1917,10 @@ func (sys *IAMSys) refreshEtcd() error {
|
|||||||
// 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)
|
||||||
|
iamGroupsMap := make(map[string]GroupInfo)
|
||||||
iamPolicyDocsMap := make(map[string]iampolicy.Policy)
|
iamPolicyDocsMap := make(map[string]iampolicy.Policy)
|
||||||
iamUserPolicyMap := make(map[string]MappedPolicy)
|
iamUserPolicyMap := make(map[string]MappedPolicy)
|
||||||
|
iamGroupPolicyMap := make(map[string]MappedPolicy)
|
||||||
|
|
||||||
if err := loadPolicyDocs(objAPI, iamPolicyDocsMap); err != nil {
|
if err := loadPolicyDocs(objAPI, iamPolicyDocsMap); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1501,12 +1932,19 @@ func (sys *IAMSys) refresh(objAPI ObjectLayer) error {
|
|||||||
if err := loadUsers(objAPI, true, iamUsersMap); err != nil {
|
if err := loadUsers(objAPI, true, iamUsersMap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := loadGroups(objAPI, iamGroupsMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := loadMappedPolicies(objAPI, false, iamUserPolicyMap); err != nil {
|
if err := loadMappedPolicies(objAPI, false, 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 := loadMappedPolicies(objAPI, true, iamUserPolicyMap); err != nil {
|
if err := loadMappedPolicies(objAPI, true, false, iamUserPolicyMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// load policies mapped to groups
|
||||||
|
if err := loadMappedPolicies(objAPI, false, false, iamGroupPolicyMap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1519,15 +1957,60 @@ func (sys *IAMSys) refresh(objAPI ObjectLayer) error {
|
|||||||
sys.iamUsersMap = iamUsersMap
|
sys.iamUsersMap = iamUsersMap
|
||||||
sys.iamPolicyDocsMap = iamPolicyDocsMap
|
sys.iamPolicyDocsMap = iamPolicyDocsMap
|
||||||
sys.iamUserPolicyMap = iamUserPolicyMap
|
sys.iamUserPolicyMap = iamUserPolicyMap
|
||||||
|
sys.iamGroupPolicyMap = iamGroupPolicyMap
|
||||||
|
sys.iamGroupsMap = iamGroupsMap
|
||||||
|
sys.buildUserGroupMemberships()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildUserGroupMemberships - builds the memberships map. IMPORTANT:
|
||||||
|
// Assumes that sys.Lock is held by caller.
|
||||||
|
func (sys *IAMSys) buildUserGroupMemberships() {
|
||||||
|
for group, gi := range sys.iamGroupsMap {
|
||||||
|
sys.updateGroupMembershipsMap(group, &gi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateGroupMembershipsMap - updates the memberships map for a
|
||||||
|
// group. IMPORTANT: Assumes sys.Lock() is held by caller.
|
||||||
|
func (sys *IAMSys) updateGroupMembershipsMap(group string, gi *GroupInfo) {
|
||||||
|
if gi == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, member := range gi.Members {
|
||||||
|
v := sys.iamUserGroupMemberships[member]
|
||||||
|
if v == nil {
|
||||||
|
v = set.CreateStringSet(group)
|
||||||
|
} else {
|
||||||
|
v.Add(group)
|
||||||
|
}
|
||||||
|
sys.iamUserGroupMemberships[member] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeGroupFromMembershipsMap - removes the group from every member
|
||||||
|
// in the cache. IMPORTANT: Assumes sys.Lock() is held by caller.
|
||||||
|
func (sys *IAMSys) removeGroupFromMembershipsMap(group string) {
|
||||||
|
if _, ok := sys.iamUserGroupMemberships[group]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for member, groups := range sys.iamUserGroupMemberships {
|
||||||
|
if !groups.Contains(group) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
groups.Remove(group)
|
||||||
|
sys.iamUserGroupMemberships[member] = groups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewIAMSys - creates new config system object.
|
// NewIAMSys - creates new config system object.
|
||||||
func NewIAMSys() *IAMSys {
|
func NewIAMSys() *IAMSys {
|
||||||
return &IAMSys{
|
return &IAMSys{
|
||||||
iamUsersMap: make(map[string]auth.Credentials),
|
iamUsersMap: make(map[string]auth.Credentials),
|
||||||
iamPolicyDocsMap: make(map[string]iampolicy.Policy),
|
iamPolicyDocsMap: make(map[string]iampolicy.Policy),
|
||||||
iamUserPolicyMap: make(map[string]MappedPolicy),
|
iamUserPolicyMap: make(map[string]MappedPolicy),
|
||||||
|
iamGroupsMap: make(map[string]GroupInfo),
|
||||||
|
iamUserGroupMemberships: make(map[string]set.StringSet),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,6 +232,19 @@ func (sys *NotificationSys) LoadUsers() []NotificationPeerErr {
|
|||||||
return ng.Wait()
|
return ng.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadGroup - loads a specific group on all peers.
|
||||||
|
func (sys *NotificationSys) LoadGroup(group 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.LoadGroup(group) }, idx, *client.host)
|
||||||
|
}
|
||||||
|
return ng.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
// BackgroundHealStatus - returns background heal status of all peers
|
// BackgroundHealStatus - returns background heal status of all peers
|
||||||
func (sys *NotificationSys) BackgroundHealStatus() []madmin.BgHealState {
|
func (sys *NotificationSys) BackgroundHealStatus() []madmin.BgHealState {
|
||||||
states := make([]madmin.BgHealState, len(sys.peerClients))
|
states := make([]madmin.BgHealState, len(sys.peerClients))
|
||||||
|
@ -443,6 +443,18 @@ func (client *peerRESTClient) LoadUsers() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadGroup - send load group command to peers.
|
||||||
|
func (client *peerRESTClient) LoadGroup(group string) error {
|
||||||
|
values := make(url.Values)
|
||||||
|
values.Set(peerRESTGroup, group)
|
||||||
|
respBody, err := client.call(peerRESTMethodLoadGroup, values, nil, -1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer http.DrainBody(respBody)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SignalService - sends signal to peer nodes.
|
// SignalService - sends signal to peer nodes.
|
||||||
func (client *peerRESTClient) SignalService(sig serviceSignal) error {
|
func (client *peerRESTClient) SignalService(sig serviceSignal) error {
|
||||||
values := make(url.Values)
|
values := make(url.Values)
|
||||||
|
@ -34,6 +34,7 @@ const (
|
|||||||
peerRESTMethodLoadPolicy = "loadpolicy"
|
peerRESTMethodLoadPolicy = "loadpolicy"
|
||||||
peerRESTMethodDeletePolicy = "deletepolicy"
|
peerRESTMethodDeletePolicy = "deletepolicy"
|
||||||
peerRESTMethodLoadUsers = "loadusers"
|
peerRESTMethodLoadUsers = "loadusers"
|
||||||
|
peerRESTMethodLoadGroup = "loadgroup"
|
||||||
peerRESTMethodStartProfiling = "startprofiling"
|
peerRESTMethodStartProfiling = "startprofiling"
|
||||||
peerRESTMethodDownloadProfilingData = "downloadprofilingdata"
|
peerRESTMethodDownloadProfilingData = "downloadprofilingdata"
|
||||||
peerRESTMethodBucketPolicySet = "setbucketpolicy"
|
peerRESTMethodBucketPolicySet = "setbucketpolicy"
|
||||||
@ -50,6 +51,7 @@ const (
|
|||||||
const (
|
const (
|
||||||
peerRESTBucket = "bucket"
|
peerRESTBucket = "bucket"
|
||||||
peerRESTUser = "user"
|
peerRESTUser = "user"
|
||||||
|
peerRESTGroup = "group"
|
||||||
peerRESTUserTemp = "user-temp"
|
peerRESTUserTemp = "user-temp"
|
||||||
peerRESTPolicy = "policy"
|
peerRESTPolicy = "policy"
|
||||||
peerRESTSignal = "signal"
|
peerRESTSignal = "signal"
|
||||||
|
@ -260,6 +260,30 @@ func (s *peerRESTServer) LoadUsersHandler(w http.ResponseWriter, r *http.Request
|
|||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadGroupHandler - reloads group along with members list.
|
||||||
|
func (s *peerRESTServer) LoadGroupHandler(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)
|
||||||
|
group := vars[peerRESTGroup]
|
||||||
|
err := globalIAMSys.LoadGroup(objAPI, group)
|
||||||
|
if err != nil {
|
||||||
|
s.writeErrorResponse(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// StartProfilingHandler - Issues the start profiling command.
|
// StartProfilingHandler - Issues the start profiling command.
|
||||||
func (s *peerRESTServer) StartProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *peerRESTServer) StartProfilingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !s.IsValid(w, r) {
|
if !s.IsValid(w, r) {
|
||||||
@ -800,6 +824,7 @@ func registerPeerRESTHandlers(router *mux.Router) {
|
|||||||
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodDeleteUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser)...)
|
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("/" + 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("/" + peerRESTMethodLoadGroup).HandlerFunc(httpTraceAll(server.LoadGroupHandler)).Queries(restQueries(peerRESTGroup)...)
|
||||||
|
|
||||||
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)...)
|
||||||
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodDownloadProfilingData).HandlerFunc(httpTraceHdrs(server.DownloadProflingDataHandler))
|
subrouter.Methods(http.MethodPost).Path("/" + peerRESTMethodDownloadProfilingData).HandlerFunc(httpTraceHdrs(server.DownloadProflingDataHandler))
|
||||||
|
@ -183,13 +183,18 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
policyName, err := globalIAMSys.PolicyDBGet(user.AccessKey)
|
policies, err := globalIAMSys.PolicyDBGet(user.AccessKey, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
policyName := ""
|
||||||
|
if len(policies) > 0 {
|
||||||
|
policyName = policies[0]
|
||||||
|
}
|
||||||
|
|
||||||
// This policy is the policy associated with the user
|
// This policy is the policy associated with the user
|
||||||
// requesting for temporary credentials. The temporary
|
// requesting for temporary credentials. The temporary
|
||||||
// credentials will inherit the same policy requirements.
|
// credentials will inherit the same policy requirements.
|
||||||
|
@ -80,6 +80,13 @@ var errInvalidDecompressedSize = errors.New("Invalid Decompressed Size")
|
|||||||
// error returned in IAM subsystem when user doesn't exist.
|
// error returned in IAM subsystem when user doesn't exist.
|
||||||
var errNoSuchUser = errors.New("Specified user does not exist")
|
var errNoSuchUser = errors.New("Specified user does not exist")
|
||||||
|
|
||||||
|
// error returned in IAM subsystem when groups doesn't exist.
|
||||||
|
var errNoSuchGroup = errors.New("Specified group does not exist")
|
||||||
|
|
||||||
|
// error returned in IAM subsystem when a non-empty group needs to be
|
||||||
|
// deleted.
|
||||||
|
var errGroupNotEmpty = errors.New("Specified group is not empty - cannot remove it")
|
||||||
|
|
||||||
// error returned in IAM subsystem when policy doesn't exist.
|
// error returned in IAM subsystem when policy doesn't exist.
|
||||||
var errNoSuchPolicy = errors.New("Specified canned policy does not exist")
|
var errNoSuchPolicy = errors.New("Specified canned policy does not exist")
|
||||||
|
|
||||||
|
164
pkg/madmin/group-commands.go
Normal file
164
pkg/madmin/group-commands.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2018-2019 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 madmin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupAddRemove is type for adding/removing members to/from a group.
|
||||||
|
type GroupAddRemove struct {
|
||||||
|
Group string `json:"group"`
|
||||||
|
Members []string `json:"members"`
|
||||||
|
IsRemove bool `json:"isRemove"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateGroupMembers - adds/removes users to/from a group. Server
|
||||||
|
// creates the group as needed. Group is removed if remove request is
|
||||||
|
// made on empty group.
|
||||||
|
func (adm *AdminClient) UpdateGroupMembers(g GroupAddRemove) error {
|
||||||
|
data, err := json.Marshal(g)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := requestData{
|
||||||
|
relPath: "/v1/update-group-members",
|
||||||
|
content: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute PUT on /minio/admin/v1/update-group-members
|
||||||
|
resp, err := adm.executeMethod("PUT", reqData)
|
||||||
|
|
||||||
|
defer closeResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return httpRespToErrorResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupDesc is a type that holds group info along with the policy
|
||||||
|
// attached to it.
|
||||||
|
type GroupDesc struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Members []string `json:"members"`
|
||||||
|
Policy string `json:"policy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroupDescription - fetches information on a group.
|
||||||
|
func (adm *AdminClient) GetGroupDescription(group string) (*GroupDesc, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("group", group)
|
||||||
|
reqData := requestData{
|
||||||
|
relPath: "/v1/group",
|
||||||
|
queryValues: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := adm.executeMethod("GET", reqData)
|
||||||
|
defer closeResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, httpRespToErrorResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gd := GroupDesc{}
|
||||||
|
if err = json.Unmarshal(data, &gd); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListGroups - lists all groups names present on the server.
|
||||||
|
func (adm *AdminClient) ListGroups() ([]string, error) {
|
||||||
|
reqData := requestData{
|
||||||
|
relPath: "/v1/groups",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := adm.executeMethod("GET", reqData)
|
||||||
|
defer closeResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, httpRespToErrorResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := []string{}
|
||||||
|
if err = json.Unmarshal(data, &groups); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupStatus - group status.
|
||||||
|
type GroupStatus string
|
||||||
|
|
||||||
|
// GroupStatus values.
|
||||||
|
const (
|
||||||
|
GroupEnabled GroupStatus = "enabled"
|
||||||
|
GroupDisabled GroupStatus = "disabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetGroupStatus - sets the status of a group.
|
||||||
|
func (adm *AdminClient) SetGroupStatus(group string, status GroupStatus) error {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("group", group)
|
||||||
|
v.Set("status", string(status))
|
||||||
|
|
||||||
|
reqData := requestData{
|
||||||
|
relPath: "/v1/set-group-status",
|
||||||
|
queryValues: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := adm.executeMethod("PUT", reqData)
|
||||||
|
defer closeResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return httpRespToErrorResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user