From a03ca80269bc0d5951685ecda4da0e11f22d1c19 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 6 May 2024 02:45:10 -0700 Subject: [PATCH] support 'mc support perf object' with root login disabled (#19672) It is expected that whoever is using the credentials which has the proper set of permissions must be able to run. `mc support perf object` While the root login is disabled. --- cmd/admin-handlers.go | 60 ++++++++++++++++++++++++++++++++++++++++- cmd/peer-rest-client.go | 1 + cmd/peer-rest-common.go | 54 +++++++++++++++++++------------------ cmd/peer-rest-server.go | 21 ++++++++++----- cmd/perf-tests.go | 26 +++++++++++++++--- cmd/speedtest.go | 25 ++++++++++------- 6 files changed, 141 insertions(+), 46 deletions(-) diff --git a/cmd/admin-handlers.go b/cmd/admin-handlers.go index 9918c273c..d8adad9eb 100644 --- a/cmd/admin-handlers.go +++ b/cmd/admin-handlers.go @@ -50,6 +50,7 @@ import ( "github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3/estream" "github.com/minio/minio-go/v7/pkg/set" + "github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/dsync" "github.com/minio/minio/internal/grid" "github.com/minio/minio/internal/handlers" @@ -1627,6 +1628,47 @@ func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request) } } +func isAllowedRWAccess(r *http.Request, cred auth.Credentials, bucketName string) (rd, wr bool) { + owner := cred.AccessKey == globalActiveCred.AccessKey + + // 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) { + if globalIAMSys.IsAllowed(policy.Args{ + AccountName: cred.AccessKey, + Groups: cred.Groups, + Action: policy.GetObjectAction, + BucketName: bucketName, + ConditionValues: getConditionValues(r, "", cred), + IsOwner: owner, + ObjectName: "", + Claims: cred.Claims, + }) { + rd = true + } + + if globalIAMSys.IsAllowed(policy.Args{ + AccountName: cred.AccessKey, + Groups: cred.Groups, + Action: policy.PutObjectAction, + BucketName: bucketName, + ConditionValues: getConditionValues(r, "", cred), + IsOwner: owner, + ObjectName: "", + Claims: cred.Claims, + }) { + wr = true + } + + return rd, wr + } + return isAllowedAccess(bucketName) +} + // ObjectSpeedTestHandler - reports maximum speed of a cluster by performing PUT and // GET operations on the server, supports auto tuning by default by automatically // increasing concurrency and stopping when we have reached the limits on the @@ -1635,11 +1677,24 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http. ctx, cancel := context.WithCancel(r.Context()) defer cancel() - objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction) + objectAPI, creds := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction) if objectAPI == nil { return } + if !globalAPIConfig.permitRootAccess() { + rd, wr := isAllowedRWAccess(r, creds, globalObjectPerfBucket) + if !rd || !wr { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, AdminError{ + Code: "XMinioSpeedtestInsufficientPermissions", + Message: fmt.Sprintf("%s does not have read and write access to '%s' bucket", creds.AccessKey, + globalObjectPerfBucket), + StatusCode: http.StatusForbidden, + }), r.URL) + return + } + } + sizeStr := r.Form.Get(peerRESTSize) durationStr := r.Form.Get(peerRESTDuration) concurrentStr := r.Form.Get(peerRESTConcurrent) @@ -1648,6 +1703,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http. autotune := r.Form.Get("autotune") == "true" noClear := r.Form.Get("noclear") == "true" enableSha256 := r.Form.Get("enableSha256") == "true" + enableMultipart := r.Form.Get("enableMultipart") == "true" size, err := strconv.Atoi(sizeStr) if err != nil { @@ -1721,6 +1777,8 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http. storageClass: storageClass, bucketName: customBucket, enableSha256: enableSha256, + enableMultipart: enableMultipart, + creds: creds, }) var prevResult madmin.SpeedTestResult for { diff --git a/cmd/peer-rest-client.go b/cmd/peer-rest-client.go index 8d58b4cb2..a744d772e 100644 --- a/cmd/peer-rest-client.go +++ b/cmd/peer-rest-client.go @@ -715,6 +715,7 @@ func (client *peerRESTClient) SpeedTest(ctx context.Context, opts speedTestOpts) values.Set(peerRESTStorageClass, opts.storageClass) values.Set(peerRESTBucket, opts.bucketName) values.Set(peerRESTEnableSha256, strconv.FormatBool(opts.enableSha256)) + values.Set(peerRESTEnableMultipart, strconv.FormatBool(opts.enableMultipart)) respBody, err := client.callWithContext(context.Background(), peerRESTMethodSpeedTest, values, nil, -1) if err != nil { diff --git a/cmd/peer-rest-common.go b/cmd/peer-rest-common.go index 26914adcd..ca03e3f8e 100644 --- a/cmd/peer-rest-common.go +++ b/cmd/peer-rest-common.go @@ -18,7 +18,7 @@ package cmd const ( - peerRESTVersion = "v38" // Convert RPC calls + peerRESTVersion = "v39" // add more flags to speedtest API peerRESTVersionPrefix = SlashSeparator + peerRESTVersion peerRESTPrefix = minioReservedBucketPath + "/peer" peerRESTPath = peerRESTPrefix + peerRESTVersionPrefix @@ -38,31 +38,33 @@ const ( ) const ( - peerRESTBucket = "bucket" - peerRESTBuckets = "buckets" - peerRESTUser = "user" - peerRESTGroup = "group" - peerRESTUserTemp = "user-temp" - peerRESTPolicy = "policy" - peerRESTUserOrGroup = "user-or-group" - peerRESTUserType = "user-type" - peerRESTIsGroup = "is-group" - peerRESTSignal = "signal" - peerRESTSubSys = "sub-sys" - peerRESTProfiler = "profiler" - peerRESTSize = "size" - peerRESTConcurrent = "concurrent" - peerRESTDuration = "duration" - peerRESTStorageClass = "storage-class" - peerRESTEnableSha256 = "enableSha256" - peerRESTMetricsTypes = "types" - peerRESTDisk = "disk" - peerRESTHost = "host" - peerRESTJobID = "job-id" - peerRESTDepID = "depID" - peerRESTStartRebalance = "start-rebalance" - peerRESTMetrics = "metrics" - peerRESTDryRun = "dry-run" + peerRESTBucket = "bucket" + peerRESTBuckets = "buckets" + peerRESTUser = "user" + peerRESTGroup = "group" + peerRESTUserTemp = "user-temp" + peerRESTPolicy = "policy" + peerRESTUserOrGroup = "user-or-group" + peerRESTUserType = "user-type" + peerRESTIsGroup = "is-group" + peerRESTSignal = "signal" + peerRESTSubSys = "sub-sys" + peerRESTProfiler = "profiler" + peerRESTSize = "size" + peerRESTConcurrent = "concurrent" + peerRESTDuration = "duration" + peerRESTStorageClass = "storage-class" + peerRESTEnableSha256 = "enableSha256" + peerRESTEnableMultipart = "enableMultipart" + peerRESTAccessKey = "access-key" + peerRESTMetricsTypes = "types" + peerRESTDisk = "disk" + peerRESTHost = "host" + peerRESTJobID = "job-id" + peerRESTDepID = "depID" + peerRESTStartRebalance = "start-rebalance" + peerRESTMetrics = "metrics" + peerRESTDryRun = "dry-run" peerRESTURL = "url" peerRESTSha256Sum = "sha256sum" diff --git a/cmd/peer-rest-server.go b/cmd/peer-rest-server.go index 49b903574..88bd41504 100644 --- a/cmd/peer-rest-server.go +++ b/cmd/peer-rest-server.go @@ -1059,6 +1059,13 @@ func (s *peerRESTServer) SpeedTestHandler(w http.ResponseWriter, r *http.Request storageClass := r.Form.Get(peerRESTStorageClass) bucketName := r.Form.Get(peerRESTBucket) enableSha256 := r.Form.Get(peerRESTEnableSha256) == "true" + enableMultipart := r.Form.Get(peerRESTEnableMultipart) == "true" + + u, ok := globalIAMSys.GetUser(r.Context(), r.Form.Get(peerRESTAccessKey)) + if !ok { + s.writeErrorResponse(w, errAuthentication) + return + } size, err := strconv.Atoi(sizeStr) if err != nil { @@ -1078,12 +1085,14 @@ func (s *peerRESTServer) SpeedTestHandler(w http.ResponseWriter, r *http.Request done := keepHTTPResponseAlive(w) result, err := selfSpeedTest(r.Context(), speedTestOpts{ - objectSize: size, - concurrency: concurrent, - duration: duration, - storageClass: storageClass, - bucketName: bucketName, - enableSha256: enableSha256, + objectSize: size, + concurrency: concurrent, + duration: duration, + storageClass: storageClass, + bucketName: bucketName, + enableSha256: enableSha256, + enableMultipart: enableMultipart, + creds: u.Credentials, }) if err != nil { result.Error = err.Error() diff --git a/cmd/perf-tests.go b/cmd/perf-tests.go index 5a9bcf380..8d8d7a353 100644 --- a/cmd/perf-tests.go +++ b/cmd/perf-tests.go @@ -33,6 +33,7 @@ import ( "github.com/dustin/go-humanize" "github.com/minio/madmin-go/v3" "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" xhttp "github.com/minio/minio/internal/http" xioutil "github.com/minio/minio/internal/ioutil" "github.com/minio/pkg/v2/randreader" @@ -72,7 +73,7 @@ func (f *firstByteRecorder) Read(p []byte) (n int, err error) { } // Runs the speedtest on local MinIO process. -func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, error) { +func selfSpeedTest(ctx context.Context, opts speedTestOpts) (res SpeedTestResult, err error) { objAPI := newObjectLayerFn() if objAPI == nil { return SpeedTestResult{}, errServerNotInitialized @@ -96,7 +97,24 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er popts := minio.PutObjectOptions{ UserMetadata: userMetadata, DisableContentSha256: !opts.enableSha256, - DisableMultipart: true, + DisableMultipart: !opts.enableMultipart, + } + + clnt := globalMinioClient + if !globalAPIConfig.permitRootAccess() { + region := globalSite.Region + if region == "" { + region = "us-east-1" + } + clnt, err = minio.New(globalLocalNodeName, &minio.Options{ + Creds: credentials.NewStaticV4(opts.creds.AccessKey, opts.creds.SecretKey, opts.creds.SessionToken), + Secure: globalIsTLS, + Transport: globalRemoteTargetTransport, + Region: region, + }) + if err != nil { + return res, err + } } var mu sync.Mutex @@ -109,7 +127,7 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er t := time.Now() reader := newRandomReader(opts.objectSize) tmpObjName := pathJoin(objNamePrefix, fmt.Sprintf("%d/%d", i, objCountPerThread[i])) - info, err := globalMinioClient.PutObject(uploadsCtx, opts.bucketName, tmpObjName, reader, int64(opts.objectSize), popts) + info, err := clnt.PutObject(uploadsCtx, opts.bucketName, tmpObjName, reader, int64(opts.objectSize), popts) if err != nil { if !contextCanceled(uploadsCtx) && !errors.Is(err, context.Canceled) { errOnce.Do(func() { @@ -150,7 +168,7 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er var downloadTTFB madmin.TimeDurations wg.Add(opts.concurrency) - c := minio.Core{Client: globalMinioClient} + c := minio.Core{Client: clnt} for i := 0; i < opts.concurrency; i++ { go func(i int) { defer wg.Done() diff --git a/cmd/speedtest.go b/cmd/speedtest.go index 7226686f3..ddf309964 100644 --- a/cmd/speedtest.go +++ b/cmd/speedtest.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015-2021 MinIO, Inc. +// Copyright (c) 2015-2024 MinIO, Inc. // // This file is part of MinIO Object Storage stack // @@ -27,6 +27,7 @@ import ( "github.com/minio/dperf/pkg/dperf" "github.com/minio/madmin-go/v3" + "github.com/minio/minio/internal/auth" xioutil "github.com/minio/minio/internal/ioutil" ) @@ -41,6 +42,8 @@ type speedTestOpts struct { storageClass string bucketName string enableSha256 bool + enableMultipart bool + creds auth.Credentials } // Get the max throughput and iops numbers. @@ -107,12 +110,14 @@ func objectSpeedTest(ctx context.Context, opts speedTestOpts) chan madmin.SpeedT // if the default concurrency yields zero results, throw an error. if throughputHighestResults[i].Downloads == 0 && opts.concurrencyStart == concurrency { - errStr = fmt.Sprintf("no results for downloads upon first attempt, concurrency %d and duration %s", opts.concurrencyStart, opts.duration) + errStr = fmt.Sprintf("no results for downloads upon first attempt, concurrency %d and duration %s", + opts.concurrencyStart, opts.duration) } // if the default concurrency yields zero results, throw an error. if throughputHighestResults[i].Uploads == 0 && opts.concurrencyStart == concurrency { - errStr = fmt.Sprintf("no results for uploads upon first attempt, concurrency %d and duration %s", opts.concurrencyStart, opts.duration) + errStr = fmt.Sprintf("no results for uploads upon first attempt, concurrency %d and duration %s", + opts.concurrencyStart, opts.duration) } result.PUTStats.Servers = append(result.PUTStats.Servers, madmin.SpeedTestStatServer{ @@ -160,12 +165,14 @@ func objectSpeedTest(ctx context.Context, opts speedTestOpts) chan madmin.SpeedT } sopts := speedTestOpts{ - objectSize: opts.objectSize, - concurrency: concurrency, - duration: opts.duration, - storageClass: opts.storageClass, - bucketName: opts.bucketName, - enableSha256: opts.enableSha256, + objectSize: opts.objectSize, + concurrency: concurrency, + duration: opts.duration, + storageClass: opts.storageClass, + bucketName: opts.bucketName, + enableSha256: opts.enableSha256, + enableMultipart: opts.enableMultipart, + creds: opts.creds, } results := globalNotificationSys.SpeedTest(ctx, sopts)