add missing admin actions, enhance AccountUsageInfo (#9607)

master
Harshavardhana 5 years ago committed by GitHub
parent 247795dd36
commit 814ddc0923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 105
      cmd/admin-handlers-users.go
  2. 64
      cmd/admin-handlers.go
  3. 5
      cmd/admin-router.go
  4. 105
      cmd/iam.go
  5. 12
      pkg/iam/policy/action.go
  6. 11
      pkg/iam/policy/admin-action.go
  7. 44
      pkg/madmin/info-commands.go
  8. 52
      pkg/madmin/user-commands.go

@ -572,6 +572,111 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
writeSuccessNoContent(w) 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} // InfoCannedPolicyV2 - GET /minio/admin/v2/info-canned-policy?name={policyName}
func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "InfoCannedPolicyV2") ctx := newContext(r, w, "InfoCannedPolicyV2")

@ -326,70 +326,6 @@ func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Re
writeSuccessResponseJSON(w, dataUsageInfoJSON) 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 { func newLockEntry(l lockRequesterInfo, resource, server string) *madmin.LockEntry {
entry := &madmin.LockEntry{ entry := &madmin.LockEntry{
Timestamp: l.Timestamp, Timestamp: l.Timestamp,

@ -62,8 +62,6 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps, enab
// DataUsageInfo operations // DataUsageInfo operations
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(httpTraceAll(adminAPI.DataUsageInfoHandler)) 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 { if globalIsDistXL || globalIsXL {
/// Heal operations /// 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:.*}") adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.AddCannedPolicy)).Queries("name", "{name:.*}")
// Add user IAM // 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+"/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:.*}") adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-status").HandlerFunc(httpTraceHdrs(adminAPI.SetUserStatus)).Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")

@ -28,7 +28,6 @@ import (
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/bucket/policy"
iampolicy "github.com/minio/minio/pkg/iam/policy" iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
) )
@ -1345,110 +1344,6 @@ func (sys *IAMSys) policyDBSet(name, policyName string, userType IAMUserType, is
return nil 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 // 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 // be a member of multiple groups, this function returns an array of
// applicable policies (each group is mapped to at most one policy). // applicable policies (each group is mapped to at most one policy).

@ -140,7 +140,6 @@ const (
// List of all supported actions. // List of all supported actions.
var supportedActions = map[Action]struct{}{ var supportedActions = map[Action]struct{}{
AllActions: {},
AbortMultipartUploadAction: {}, AbortMultipartUploadAction: {},
CreateBucketAction: {}, CreateBucketAction: {},
DeleteBucketAction: {}, DeleteBucketAction: {},
@ -157,25 +156,26 @@ var supportedActions = map[Action]struct{}{
ListBucketMultipartUploadsAction: {}, ListBucketMultipartUploadsAction: {},
ListenBucketNotificationAction: {}, ListenBucketNotificationAction: {},
ListMultipartUploadPartsAction: {}, ListMultipartUploadPartsAction: {},
PutBucketLifecycleAction: {},
GetBucketLifecycleAction: {},
PutBucketNotificationAction: {}, PutBucketNotificationAction: {},
PutBucketPolicyAction: {}, PutBucketPolicyAction: {},
PutObjectAction: {}, PutObjectAction: {},
GetBucketLifecycleAction: {}, BypassGovernanceRetentionAction: {},
PutBucketLifecycleAction: {},
PutObjectRetentionAction: {}, PutObjectRetentionAction: {},
GetObjectRetentionAction: {}, GetObjectRetentionAction: {},
GetObjectLegalHoldAction: {}, GetObjectLegalHoldAction: {},
PutObjectLegalHoldAction: {}, PutObjectLegalHoldAction: {},
PutBucketObjectLockConfigurationAction: {},
GetBucketObjectLockConfigurationAction: {}, GetBucketObjectLockConfigurationAction: {},
PutBucketTaggingAction: {}, PutBucketObjectLockConfigurationAction: {},
GetBucketTaggingAction: {}, GetBucketTaggingAction: {},
BypassGovernanceRetentionAction: {}, PutBucketTaggingAction: {},
GetObjectTaggingAction: {}, GetObjectTaggingAction: {},
PutObjectTaggingAction: {}, PutObjectTaggingAction: {},
DeleteObjectTaggingAction: {}, DeleteObjectTaggingAction: {},
PutBucketEncryptionAction: {}, PutBucketEncryptionAction: {},
GetBucketEncryptionAction: {}, GetBucketEncryptionAction: {},
AllActions: {},
} }
// List of all supported object actions. // List of all supported object actions.

@ -31,8 +31,6 @@ const (
// StorageInfoAdminAction - allow listing server info // StorageInfoAdminAction - allow listing server info
StorageInfoAdminAction = "admin:StorageInfo" StorageInfoAdminAction = "admin:StorageInfo"
// AccountingUsageInfoAdminAction - allow listing accounting usage info
AccountingUsageInfoAdminAction = "admin:AccountingUsageInfo"
// DataUsageInfoAdminAction - allow listing data usage info // DataUsageInfoAdminAction - allow listing data usage info
DataUsageInfoAdminAction = "admin:DataUsageInfo" DataUsageInfoAdminAction = "admin:DataUsageInfo"
// TopLocksAdminAction - allow listing top locks // TopLocksAdminAction - allow listing top locks
@ -114,17 +112,16 @@ const (
// List of all supported admin actions. // List of all supported admin actions.
var supportedAdminActions = map[AdminAction]struct{}{ var supportedAdminActions = map[AdminAction]struct{}{
AllAdminActions: {},
HealAdminAction: {}, HealAdminAction: {},
ServerInfoAdminAction: {},
StorageInfoAdminAction: {}, StorageInfoAdminAction: {},
DataUsageInfoAdminAction: {}, DataUsageInfoAdminAction: {},
TopLocksAdminAction: {}, TopLocksAdminAction: {},
ProfilingAdminAction: {}, ProfilingAdminAction: {},
TraceAdminAction: {}, TraceAdminAction: {},
OBDInfoAdminAction: {},
ConsoleLogAdminAction: {}, ConsoleLogAdminAction: {},
KMSKeyStatusAdminAction: {}, KMSKeyStatusAdminAction: {},
ServerInfoAdminAction: {},
OBDInfoAdminAction: {},
ServerUpdateAdminAction: {}, ServerUpdateAdminAction: {},
ServiceRestartAdminAction: {}, ServiceRestartAdminAction: {},
ServiceStopAdminAction: {}, ServiceStopAdminAction: {},
@ -137,6 +134,7 @@ var supportedAdminActions = map[AdminAction]struct{}{
GetUserAdminAction: {}, GetUserAdminAction: {},
AddUserToGroupAdminAction: {}, AddUserToGroupAdminAction: {},
RemoveUserFromGroupAdminAction: {}, RemoveUserFromGroupAdminAction: {},
GetGroupAdminAction: {},
ListGroupsAdminAction: {}, ListGroupsAdminAction: {},
EnableGroupAdminAction: {}, EnableGroupAdminAction: {},
DisableGroupAdminAction: {}, DisableGroupAdminAction: {},
@ -144,9 +142,10 @@ var supportedAdminActions = map[AdminAction]struct{}{
DeletePolicyAdminAction: {}, DeletePolicyAdminAction: {},
GetPolicyAdminAction: {}, GetPolicyAdminAction: {},
AttachPolicyAdminAction: {}, AttachPolicyAdminAction: {},
ListUserPoliciesAdminAction: {},
SetBucketQuotaAdminAction: {}, SetBucketQuotaAdminAction: {},
GetBucketQuotaAdminAction: {}, GetBucketQuotaAdminAction: {},
ListUserPoliciesAdminAction: {}, AllAdminActions: {},
} }
func parseAdminAction(s string) (AdminAction, error) { func parseAdminAction(s string) (AdminAction, error) {

@ -192,50 +192,6 @@ func (adm *AdminClient) DataUsageInfo(ctx context.Context) (DataUsageInfo, error
return dataUsageInfo, nil 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. // InfoMessage container to hold server admin related information.
type InfoMessage struct { type InfoMessage struct {
Mode string `json:"mode,omitempty"` Mode string `json:"mode,omitempty"`

@ -23,11 +23,63 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"time"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
iampolicy "github.com/minio/minio/pkg/iam/policy" 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. // AccountStatus - account status.
type AccountStatus string type AccountStatus string

Loading…
Cancel
Save