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:
Taran Pelkey 2022-12-13 15:13:23 -05:00 committed by GitHub
parent 76dde82b41
commit 709eb283d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 491 additions and 183 deletions

View File

@ -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"

View File

@ -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:.*}")

View File

@ -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

View File

@ -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 == "" {

View File

@ -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() {