diff --git a/cmd/admin-handlers-users.go b/cmd/admin-handlers-users.go index ddbc5caeb..8e9c5dc90 100644 --- a/cmd/admin-handlers-users.go +++ b/cmd/admin-handlers-users.go @@ -572,6 +572,111 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re writeSuccessNoContent(w) } +// AccountUsageInfoHandler returns usage +func (a adminAPIHandlers) AccountUsageInfoHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "AccountUsageInfo") + + defer logger.AuditLog(w, r, "AccountUsageInfo", mustGetClaimsFromToken(r)) + + // Get current object layer instance. + objectAPI := newObjectLayerWithoutSafeModeFn() + if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + + cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "") + if s3Err != ErrNone { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL) + return + } + + // Set prefix value for "s3:prefix" policy conditionals. + r.Header.Set("prefix", "") + + // Set delimiter value for "s3:delimiter" policy conditionals. + r.Header.Set("delimiter", SlashSeparator) + + isAllowedAccess := func(bucketName string) (rd, wr bool) { + // Use the following trick to filter in place + // https://github.com/golang/go/wiki/SliceTricks#filter-in-place + if globalIAMSys.IsAllowed(iampolicy.Args{ + AccountName: cred.AccessKey, + Action: iampolicy.ListBucketAction, + BucketName: bucketName, + ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), + IsOwner: owner, + ObjectName: "", + Claims: claims, + }) { + rd = true + } + + if globalIAMSys.IsAllowed(iampolicy.Args{ + AccountName: cred.AccessKey, + Action: iampolicy.PutObjectAction, + BucketName: bucketName, + ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), + IsOwner: owner, + ObjectName: "", + Claims: claims, + }) { + wr = true + } + + return rd, wr + } + + buckets, err := objectAPI.ListBuckets(ctx) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + // Load the latest calculated data usage + dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI) + if err != nil { + logger.LogIf(ctx, err) + } + + accountName := cred.AccessKey + if cred.ParentUser != "" { + accountName = cred.ParentUser + } + + acctInfo := madmin.AccountUsageInfo{ + AccountName: accountName, + } + + for _, bucket := range buckets { + rd, wr := isAllowedAccess(bucket.Name) + if rd || wr { + var size uint64 + // Fetch the data usage of the current bucket + if !dataUsageInfo.LastUpdate.IsZero() && len(dataUsageInfo.BucketsSizes) > 0 { + size = dataUsageInfo.BucketsSizes[bucket.Name] + } + acctInfo.Buckets = append(acctInfo.Buckets, madmin.BucketUsageInfo{ + Name: bucket.Name, + Created: bucket.Created, + Size: size, + Access: madmin.AccountAccess{ + Read: rd, + Write: wr, + }, + }) + } + } + + usageInfoJSON, err := json.Marshal(acctInfo) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + writeSuccessResponseJSON(w, usageInfoJSON) +} + // InfoCannedPolicyV2 - GET /minio/admin/v2/info-canned-policy?name={policyName} func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "InfoCannedPolicyV2") diff --git a/cmd/admin-handlers.go b/cmd/admin-handlers.go index 3f5e709a3..1b28016a4 100644 --- a/cmd/admin-handlers.go +++ b/cmd/admin-handlers.go @@ -326,70 +326,6 @@ func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Re writeSuccessResponseJSON(w, dataUsageInfoJSON) } -func (a adminAPIHandlers) AccountingUsageInfoHandler(w http.ResponseWriter, r *http.Request) { - ctx := newContext(r, w, "AccountingUsageInfo") - - defer logger.AuditLog(w, r, "AccountingUsageInfo", mustGetClaimsFromToken(r)) - - objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AccountingUsageInfoAdminAction) - if objectAPI == nil { - return - } - - var accountingUsageInfo = make(map[string]madmin.BucketAccountingUsage) - - buckets, err := objectAPI.ListBuckets(ctx) - if err != nil { - // writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), r.URL) - writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) - return - } - - users, err := globalIAMSys.ListUsers() - if err != nil { - writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) - return - } - - // Load the latest calculated data usage - dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI) - if err != nil { - logger.LogIf(ctx, err) - } - - // Calculate for each bucket, which users are allowed to access to it - for _, bucket := range buckets { - bucketUsageInfo := madmin.BucketAccountingUsage{} - - // Fetch the data usage of the current bucket - if !dataUsageInfo.LastUpdate.IsZero() && dataUsageInfo.BucketsSizes != nil { - bucketUsageInfo.Size = dataUsageInfo.BucketsSizes[bucket.Name] - } - - for user := range users { - rd, wr, custom := globalIAMSys.GetAccountAccess(user, bucket.Name) - if rd || wr || custom { - bucketUsageInfo.AccessList = append(bucketUsageInfo.AccessList, madmin.AccountAccess{ - AccountName: user, - Read: rd, - Write: wr, - Custom: custom, - }) - } - } - - accountingUsageInfo[bucket.Name] = bucketUsageInfo - } - - usageInfoJSON, err := json.Marshal(accountingUsageInfo) - if err != nil { - writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) - return - } - - writeSuccessResponseJSON(w, usageInfoJSON) -} - func newLockEntry(l lockRequesterInfo, resource, server string) *madmin.LockEntry { entry := &madmin.LockEntry{ Timestamp: l.Timestamp, diff --git a/cmd/admin-router.go b/cmd/admin-router.go index 519b6fc8f..702d739e1 100644 --- a/cmd/admin-router.go +++ b/cmd/admin-router.go @@ -62,8 +62,6 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps, enab // DataUsageInfo operations adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(httpTraceAll(adminAPI.DataUsageInfoHandler)) - adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountingusageinfo").HandlerFunc(httpTraceAll(adminAPI.AccountingUsageInfoHandler)) - if globalIsDistXL || globalIsXL { /// Heal operations @@ -115,6 +113,9 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps, enab adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.AddCannedPolicy)).Queries("name", "{name:.*}") // Add user IAM + + adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountusageinfo").HandlerFunc(httpTraceAll(adminAPI.AccountUsageInfoHandler)) + adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(httpTraceHdrs(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}") adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-status").HandlerFunc(httpTraceHdrs(adminAPI.SetUserStatus)).Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}") diff --git a/cmd/iam.go b/cmd/iam.go index 12072c1b4..6ebfba209 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -28,7 +28,6 @@ import ( "github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/auth" - "github.com/minio/minio/pkg/bucket/policy" iampolicy "github.com/minio/minio/pkg/iam/policy" "github.com/minio/minio/pkg/madmin" ) @@ -1345,110 +1344,6 @@ func (sys *IAMSys) policyDBSet(name, policyName string, userType IAMUserType, is return nil } -var iamAccountReadAccessActions = iampolicy.NewActionSet( - iampolicy.ListMultipartUploadPartsAction, - iampolicy.ListBucketMultipartUploadsAction, - iampolicy.ListBucketAction, - iampolicy.HeadBucketAction, - iampolicy.GetObjectAction, - iampolicy.GetBucketLocationAction, - - // iampolicy.ListAllMyBucketsAction, -) - -var iamAccountWriteAccessActions = iampolicy.NewActionSet( - iampolicy.AbortMultipartUploadAction, - iampolicy.CreateBucketAction, - iampolicy.PutObjectAction, - iampolicy.DeleteObjectAction, - iampolicy.DeleteBucketAction, -) - -var iamAccountOtherAccessActions = iampolicy.NewActionSet( - iampolicy.BypassGovernanceRetentionAction, - iampolicy.PutObjectRetentionAction, - iampolicy.GetObjectRetentionAction, - iampolicy.GetObjectLegalHoldAction, - iampolicy.PutObjectLegalHoldAction, - iampolicy.GetBucketObjectLockConfigurationAction, - iampolicy.PutBucketObjectLockConfigurationAction, - - iampolicy.ListenBucketNotificationAction, - - iampolicy.PutBucketLifecycleAction, - iampolicy.GetBucketLifecycleAction, - - iampolicy.PutBucketNotificationAction, - iampolicy.GetBucketNotificationAction, - - iampolicy.PutBucketPolicyAction, - iampolicy.DeleteBucketPolicyAction, - iampolicy.GetBucketPolicyAction, - - iampolicy.PutBucketEncryptionAction, - iampolicy.GetBucketEncryptionAction, -) - -// GetAccountAccess iterates over all policies documents associated to a user -// and returns if the user has read and/or write access to any resource. -func (sys *IAMSys) GetAccountAccess(accountName, bucket string) (rd, wr, o bool) { - policies, err := sys.PolicyDBGet(accountName, false) - if err != nil { - return false, false, false - } - - if len(policies) == 0 { - // No policy found. - return false, false, false - } - - // Policies were found, evaluate all of them. - sys.store.rlock() - defer sys.store.runlock() - - var availablePolicies []iampolicy.Policy - for _, pname := range policies { - p, found := sys.iamPolicyDocsMap[pname] - if found { - availablePolicies = append(availablePolicies, p) - } - } - - if len(availablePolicies) == 0 { - return false, false, false - } - - combinedPolicy := availablePolicies[0] - for i := 1; i < len(availablePolicies); i++ { - combinedPolicy.Statements = append(combinedPolicy.Statements, - availablePolicies[i].Statements...) - } - - allActions := iampolicy.NewActionSet(iampolicy.AllActions) - for _, st := range combinedPolicy.Statements { - // Ignore if this is not an allow policy statement - if st.Effect != policy.Allow { - continue - } - // Fast calculation if there is s3:* permissions to any resource - if !st.Actions.Intersection(allActions).IsEmpty() { - rd, wr, o = true, true, true - break - } - if !st.Actions.Intersection(iamAccountReadAccessActions).IsEmpty() { - rd = true - } - if !st.Actions.Intersection(iamAccountWriteAccessActions).IsEmpty() { - wr = true - } - if !st.Actions.Intersection(iamAccountOtherAccessActions).IsEmpty() { - o = true - } - } - - return -} - // PolicyDBGet - gets policy set on a user or group. Since a user may // be a member of multiple groups, this function returns an array of // applicable policies (each group is mapped to at most one policy). diff --git a/pkg/iam/policy/action.go b/pkg/iam/policy/action.go index bded00c77..d5022b386 100644 --- a/pkg/iam/policy/action.go +++ b/pkg/iam/policy/action.go @@ -140,7 +140,6 @@ const ( // List of all supported actions. var supportedActions = map[Action]struct{}{ - AllActions: {}, AbortMultipartUploadAction: {}, CreateBucketAction: {}, DeleteBucketAction: {}, @@ -157,25 +156,26 @@ var supportedActions = map[Action]struct{}{ ListBucketMultipartUploadsAction: {}, ListenBucketNotificationAction: {}, ListMultipartUploadPartsAction: {}, + PutBucketLifecycleAction: {}, + GetBucketLifecycleAction: {}, PutBucketNotificationAction: {}, PutBucketPolicyAction: {}, PutObjectAction: {}, - GetBucketLifecycleAction: {}, - PutBucketLifecycleAction: {}, + BypassGovernanceRetentionAction: {}, PutObjectRetentionAction: {}, GetObjectRetentionAction: {}, GetObjectLegalHoldAction: {}, PutObjectLegalHoldAction: {}, - PutBucketObjectLockConfigurationAction: {}, GetBucketObjectLockConfigurationAction: {}, - PutBucketTaggingAction: {}, + PutBucketObjectLockConfigurationAction: {}, GetBucketTaggingAction: {}, - BypassGovernanceRetentionAction: {}, + PutBucketTaggingAction: {}, GetObjectTaggingAction: {}, PutObjectTaggingAction: {}, DeleteObjectTaggingAction: {}, PutBucketEncryptionAction: {}, GetBucketEncryptionAction: {}, + AllActions: {}, } // List of all supported object actions. diff --git a/pkg/iam/policy/admin-action.go b/pkg/iam/policy/admin-action.go index 00a8329f3..838769ed3 100644 --- a/pkg/iam/policy/admin-action.go +++ b/pkg/iam/policy/admin-action.go @@ -31,8 +31,6 @@ const ( // StorageInfoAdminAction - allow listing server info StorageInfoAdminAction = "admin:StorageInfo" - // AccountingUsageInfoAdminAction - allow listing accounting usage info - AccountingUsageInfoAdminAction = "admin:AccountingUsageInfo" // DataUsageInfoAdminAction - allow listing data usage info DataUsageInfoAdminAction = "admin:DataUsageInfo" // TopLocksAdminAction - allow listing top locks @@ -114,17 +112,16 @@ const ( // List of all supported admin actions. var supportedAdminActions = map[AdminAction]struct{}{ - AllAdminActions: {}, HealAdminAction: {}, - ServerInfoAdminAction: {}, StorageInfoAdminAction: {}, DataUsageInfoAdminAction: {}, TopLocksAdminAction: {}, ProfilingAdminAction: {}, TraceAdminAction: {}, - OBDInfoAdminAction: {}, ConsoleLogAdminAction: {}, KMSKeyStatusAdminAction: {}, + ServerInfoAdminAction: {}, + OBDInfoAdminAction: {}, ServerUpdateAdminAction: {}, ServiceRestartAdminAction: {}, ServiceStopAdminAction: {}, @@ -137,6 +134,7 @@ var supportedAdminActions = map[AdminAction]struct{}{ GetUserAdminAction: {}, AddUserToGroupAdminAction: {}, RemoveUserFromGroupAdminAction: {}, + GetGroupAdminAction: {}, ListGroupsAdminAction: {}, EnableGroupAdminAction: {}, DisableGroupAdminAction: {}, @@ -144,9 +142,10 @@ var supportedAdminActions = map[AdminAction]struct{}{ DeletePolicyAdminAction: {}, GetPolicyAdminAction: {}, AttachPolicyAdminAction: {}, + ListUserPoliciesAdminAction: {}, SetBucketQuotaAdminAction: {}, GetBucketQuotaAdminAction: {}, - ListUserPoliciesAdminAction: {}, + AllAdminActions: {}, } func parseAdminAction(s string) (AdminAction, error) { diff --git a/pkg/madmin/info-commands.go b/pkg/madmin/info-commands.go index 0a049f89b..72c88e4f4 100644 --- a/pkg/madmin/info-commands.go +++ b/pkg/madmin/info-commands.go @@ -192,50 +192,6 @@ func (adm *AdminClient) DataUsageInfo(ctx context.Context) (DataUsageInfo, error return dataUsageInfo, nil } -// AccountAccess contains information about -type AccountAccess struct { - AccountName string `json:"accountName"` - Read bool `json:"read"` - Write bool `json:"write"` - Custom bool `json:"custom"` -} - -// BucketAccountingUsage represents the accounting usage of a particular bucket -type BucketAccountingUsage struct { - Size uint64 `json:"size"` - AccessList []AccountAccess `json:"accessList"` -} - -// AccountingUsageInfo returns the accounting usage info, currently it returns -// the type of access of different accounts to the different buckets. -func (adm *AdminClient) AccountingUsageInfo(ctx context.Context) (map[string]BucketAccountingUsage, error) { - resp, err := adm.executeMethod(ctx, http.MethodGet, requestData{relPath: adminAPIPrefix + "/accountingusageinfo"}) - defer closeResponse(resp) - if err != nil { - return nil, err - } - - // Check response http status code - if resp.StatusCode != http.StatusOK { - return nil, httpRespToErrorResponse(resp) - } - - // Unmarshal the server's json response - var accountingUsageInfo map[string]BucketAccountingUsage - - respBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - err = json.Unmarshal(respBytes, &accountingUsageInfo) - if err != nil { - return nil, err - } - - return accountingUsageInfo, nil -} - // InfoMessage container to hold server admin related information. type InfoMessage struct { Mode string `json:"mode,omitempty"` diff --git a/pkg/madmin/user-commands.go b/pkg/madmin/user-commands.go index a65b1e3dd..a5b9f0fb0 100644 --- a/pkg/madmin/user-commands.go +++ b/pkg/madmin/user-commands.go @@ -23,11 +23,63 @@ import ( "io/ioutil" "net/http" "net/url" + "time" "github.com/minio/minio/pkg/auth" iampolicy "github.com/minio/minio/pkg/iam/policy" ) +// AccountAccess contains information about +type AccountAccess struct { + Read bool `json:"read"` + Write bool `json:"write"` +} + +// BucketUsageInfo represents bucket usage of a bucket, and its relevant +// access type for an account +type BucketUsageInfo struct { + Name string `json:"name"` + Size uint64 `json:"size"` + Created time.Time `json:"created"` + Access AccountAccess `json:"access"` +} + +// AccountUsageInfo represents the account usage info of an +// account across buckets. +type AccountUsageInfo struct { + AccountName string + Buckets []BucketUsageInfo +} + +// AccountUsageInfo returns the usage info for the authenticating account. +func (adm *AdminClient) AccountUsageInfo(ctx context.Context) (AccountUsageInfo, error) { + resp, err := adm.executeMethod(ctx, http.MethodGet, requestData{relPath: adminAPIPrefix + "/accountusageinfo"}) + defer closeResponse(resp) + if err != nil { + return AccountUsageInfo{}, err + } + + // Check response http status code + if resp.StatusCode != http.StatusOK { + return AccountUsageInfo{}, httpRespToErrorResponse(resp) + } + + // Unmarshal the server's json response + var accountInfo AccountUsageInfo + + respBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return AccountUsageInfo{}, err + } + + err = json.Unmarshal(respBytes, &accountInfo) + if err != nil { + return AccountUsageInfo{}, err + } + + return accountInfo, nil +} + // AccountStatus - account status. type AccountStatus string