mirror of
https://github.com/minio/minio.git
synced 2025-01-23 12:43:16 -05:00
Add endpoints for managing IAM policies (#15897)
Co-authored-by: Taran <taran@minio.io> Co-authored-by: ¨taran-p¨ <¨taran@minio.io¨> Co-authored-by: Aditya Manthramurthy <donatello@users.noreply.github.com>
This commit is contained in:
parent
76dde82b41
commit
709eb283d9
@ -25,6 +25,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@ -1666,6 +1667,267 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AttachPolicyBuiltin - POST /minio/admin/v3/idp/builtin/attach
|
||||||
|
func (a adminAPIHandlers) AttachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "AttachPolicyBuiltin")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, _, _, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
password := cred.SecretKey
|
||||||
|
|
||||||
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var par madmin.PolicyAssociationReq
|
||||||
|
if err = json.Unmarshal(reqBytes, &par); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = par.IsValid(); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userOrGroup := par.User
|
||||||
|
isGroup := false
|
||||||
|
|
||||||
|
if userOrGroup == "" {
|
||||||
|
userOrGroup = par.Group
|
||||||
|
isGroup = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isGroup {
|
||||||
|
_, err := globalIAMSys.GetGroupDescription(userOrGroup)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok, _, err := globalIAMSys.IsTempUser(userOrGroup)
|
||||||
|
if err != nil && err != errNoSuchUser {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that user exists.
|
||||||
|
if globalIAMSys.GetUsersSysType() == MinIOUsersSysType {
|
||||||
|
_, ok := globalIAMSys.GetUser(ctx, userOrGroup)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userType := regUser
|
||||||
|
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType {
|
||||||
|
userType = stsUser
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingPolicies []string
|
||||||
|
if isGroup {
|
||||||
|
existingPolicies, err = globalIAMSys.PolicyDBGet(userOrGroup, true)
|
||||||
|
} else {
|
||||||
|
existingPolicies, err = globalIAMSys.GetUserPolicies(userOrGroup)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
policyMap := make(map[string]bool)
|
||||||
|
for _, p := range existingPolicies {
|
||||||
|
policyMap[p] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
policiesToAttach := par.Policies
|
||||||
|
|
||||||
|
// Check if policy is already attached to user.
|
||||||
|
for _, p := range policiesToAttach {
|
||||||
|
if _, ok := policyMap[p]; ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrPolicyAlreadyAttached), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existingPolicies = append(existingPolicies, policiesToAttach...)
|
||||||
|
newPolicies := strings.Join(existingPolicies, ",")
|
||||||
|
|
||||||
|
updatedAt, err := globalIAMSys.PolicyDBSet(ctx, userOrGroup, newPolicies, userType, isGroup)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
|
Type: madmin.SRIAMItemPolicyMapping,
|
||||||
|
PolicyMapping: &madmin.SRPolicyMapping{
|
||||||
|
UserOrGroup: userOrGroup,
|
||||||
|
UserType: int(userType),
|
||||||
|
IsGroup: isGroup,
|
||||||
|
Policy: strings.Join(policiesToAttach, ","),
|
||||||
|
},
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeResponse(w, http.StatusCreated, nil, mimeNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetachPolicyBuiltin - POST /minio/admin/v3/idp/builtin/detach
|
||||||
|
func (a adminAPIHandlers) DetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "DetachPolicyBuiltin")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, _, _, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
password := cred.SecretKey
|
||||||
|
|
||||||
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var par madmin.PolicyAssociationReq
|
||||||
|
if err = json.Unmarshal(reqBytes, &par); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = par.IsValid(); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userOrGroup := par.User
|
||||||
|
isGroup := false
|
||||||
|
|
||||||
|
if userOrGroup == "" {
|
||||||
|
userOrGroup = par.Group
|
||||||
|
isGroup = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isGroup {
|
||||||
|
_, err := globalIAMSys.GetGroupDescription(userOrGroup)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok, _, err := globalIAMSys.IsTempUser(userOrGroup)
|
||||||
|
if err != nil && err != errNoSuchUser {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that user exists.
|
||||||
|
if globalIAMSys.GetUsersSysType() == MinIOUsersSysType {
|
||||||
|
_, ok := globalIAMSys.GetUser(ctx, userOrGroup)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return successful JSON response
|
||||||
|
writeSuccessNoContent(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
userType := regUser
|
||||||
|
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType {
|
||||||
|
userType = stsUser
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingPolicies []string
|
||||||
|
if isGroup {
|
||||||
|
existingPolicies, err = globalIAMSys.PolicyDBGet(userOrGroup, true)
|
||||||
|
} else {
|
||||||
|
existingPolicies, err = globalIAMSys.GetUserPolicies(userOrGroup)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
policyMap := make(map[string]bool)
|
||||||
|
for _, p := range existingPolicies {
|
||||||
|
policyMap[p] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
policiesToDetach := par.Policies
|
||||||
|
|
||||||
|
// Check if policy is already attached to user.
|
||||||
|
for _, p := range policiesToDetach {
|
||||||
|
if _, ok := policyMap[p]; ok {
|
||||||
|
delete(policyMap, p)
|
||||||
|
} else {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrPolicyNotAttached), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newPoliciesSl := []string{}
|
||||||
|
for p := range policyMap {
|
||||||
|
newPoliciesSl = append(newPoliciesSl, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
newPolicies := strings.Join(newPoliciesSl, ",")
|
||||||
|
|
||||||
|
updatedAt, err := globalIAMSys.PolicyDBSet(ctx, userOrGroup, newPolicies, userType, isGroup)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
|
Type: madmin.SRIAMItemPolicyMapping,
|
||||||
|
PolicyMapping: &madmin.SRPolicyMapping{
|
||||||
|
UserOrGroup: userOrGroup,
|
||||||
|
UserType: int(userType),
|
||||||
|
IsGroup: isGroup,
|
||||||
|
Policy: strings.Join(policiesToDetach, ","),
|
||||||
|
},
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
allPoliciesFile = "policies.json"
|
allPoliciesFile = "policies.json"
|
||||||
allUsersFile = "users.json"
|
allUsersFile = "users.json"
|
||||||
|
@ -159,6 +159,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))).
|
HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))).
|
||||||
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
||||||
|
|
||||||
|
// Attach policies to user or group
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/attach").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachPolicyBuiltin)))
|
||||||
|
|
||||||
|
// Detach policies from user or group
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/detach").HandlerFunc(gz(httpTraceHdrs(adminAPI.DetachPolicyBuiltin)))
|
||||||
|
|
||||||
// Remove user IAM
|
// Remove user IAM
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
|
@ -195,6 +195,8 @@ const (
|
|||||||
ErrBucketTaggingNotFound
|
ErrBucketTaggingNotFound
|
||||||
ErrObjectLockInvalidHeaders
|
ErrObjectLockInvalidHeaders
|
||||||
ErrInvalidTagDirective
|
ErrInvalidTagDirective
|
||||||
|
ErrPolicyAlreadyAttached
|
||||||
|
ErrPolicyNotAttached
|
||||||
// Add new error codes here.
|
// Add new error codes here.
|
||||||
|
|
||||||
// SSE-S3/SSE-KMS related API errors
|
// SSE-S3/SSE-KMS related API errors
|
||||||
@ -1939,6 +1941,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Invalid checksum provided.",
|
Description: "Invalid checksum provided.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrPolicyAlreadyAttached: {
|
||||||
|
Code: "XMinioPolicyAlreadyAttached",
|
||||||
|
Description: "The specified policy is already attached.",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
},
|
||||||
|
ErrPolicyNotAttached: {
|
||||||
|
Code: "XMinioPolicyNotAttached",
|
||||||
|
Description: "The specified policy is not found.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
// Add your error structure here.
|
// Add your error structure here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1552,6 +1552,23 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserPolicies - returns the policies attached to a user.
|
||||||
|
func (store *IAMStoreSys) GetUserPolicies(name string) ([]string, error) {
|
||||||
|
if name == "" {
|
||||||
|
return nil, errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
cache := store.rlock()
|
||||||
|
defer store.runlock()
|
||||||
|
|
||||||
|
if cache.iamUserPolicyMap[name].Policies == "" {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
policies := cache.iamUserPolicyMap[name].toSlice()
|
||||||
|
return policies, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PolicyMappingNotificationHandler - handles updating a policy mapping from storage.
|
// PolicyMappingNotificationHandler - handles updating a policy mapping from storage.
|
||||||
func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context, userOrGroup string, isGroup bool, userType IAMUserType) error {
|
func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context, userOrGroup string, isGroup bool, userType IAMUserType) error {
|
||||||
if userOrGroup == "" {
|
if userOrGroup == "" {
|
||||||
|
@ -857,6 +857,15 @@ func (sys *IAMSys) GetUserInfo(ctx context.Context, name string) (u madmin.UserI
|
|||||||
return sys.store.GetUserInfo(name)
|
return sys.store.GetUserInfo(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserPolicies - get policies attached to a user.
|
||||||
|
func (sys *IAMSys) GetUserPolicies(name string) (p []string, err error) {
|
||||||
|
if !sys.Initialized() {
|
||||||
|
return p, errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
return sys.store.GetUserPolicies(name)
|
||||||
|
}
|
||||||
|
|
||||||
// SetUserStatus - sets current user status, supports disabled or enabled.
|
// SetUserStatus - sets current user status, supports disabled or enabled.
|
||||||
func (sys *IAMSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) (updatedAt time.Time, err error) {
|
func (sys *IAMSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) (updatedAt time.Time, err error) {
|
||||||
if !sys.Initialized() {
|
if !sys.Initialized() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user