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.
This commit is contained in:
Harshavardhana 2024-05-06 02:45:10 -07:00 committed by GitHub
parent 523bd769f1
commit a03ca80269
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 141 additions and 46 deletions

View File

@ -50,6 +50,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/madmin-go/v3/estream" "github.com/minio/madmin-go/v3/estream"
"github.com/minio/minio-go/v7/pkg/set" "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/dsync"
"github.com/minio/minio/internal/grid" "github.com/minio/minio/internal/grid"
"github.com/minio/minio/internal/handlers" "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 // ObjectSpeedTestHandler - reports maximum speed of a cluster by performing PUT and
// GET operations on the server, supports auto tuning by default by automatically // GET operations on the server, supports auto tuning by default by automatically
// increasing concurrency and stopping when we have reached the limits on the // 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()) ctx, cancel := context.WithCancel(r.Context())
defer cancel() defer cancel()
objectAPI, _ := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction) objectAPI, creds := validateAdminReq(ctx, w, r, policy.HealthInfoAdminAction)
if objectAPI == nil { if objectAPI == nil {
return 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) sizeStr := r.Form.Get(peerRESTSize)
durationStr := r.Form.Get(peerRESTDuration) durationStr := r.Form.Get(peerRESTDuration)
concurrentStr := r.Form.Get(peerRESTConcurrent) concurrentStr := r.Form.Get(peerRESTConcurrent)
@ -1648,6 +1703,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
autotune := r.Form.Get("autotune") == "true" autotune := r.Form.Get("autotune") == "true"
noClear := r.Form.Get("noclear") == "true" noClear := r.Form.Get("noclear") == "true"
enableSha256 := r.Form.Get("enableSha256") == "true" enableSha256 := r.Form.Get("enableSha256") == "true"
enableMultipart := r.Form.Get("enableMultipart") == "true"
size, err := strconv.Atoi(sizeStr) size, err := strconv.Atoi(sizeStr)
if err != nil { if err != nil {
@ -1721,6 +1777,8 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
storageClass: storageClass, storageClass: storageClass,
bucketName: customBucket, bucketName: customBucket,
enableSha256: enableSha256, enableSha256: enableSha256,
enableMultipart: enableMultipart,
creds: creds,
}) })
var prevResult madmin.SpeedTestResult var prevResult madmin.SpeedTestResult
for { for {

View File

@ -715,6 +715,7 @@ func (client *peerRESTClient) SpeedTest(ctx context.Context, opts speedTestOpts)
values.Set(peerRESTStorageClass, opts.storageClass) values.Set(peerRESTStorageClass, opts.storageClass)
values.Set(peerRESTBucket, opts.bucketName) values.Set(peerRESTBucket, opts.bucketName)
values.Set(peerRESTEnableSha256, strconv.FormatBool(opts.enableSha256)) values.Set(peerRESTEnableSha256, strconv.FormatBool(opts.enableSha256))
values.Set(peerRESTEnableMultipart, strconv.FormatBool(opts.enableMultipart))
respBody, err := client.callWithContext(context.Background(), peerRESTMethodSpeedTest, values, nil, -1) respBody, err := client.callWithContext(context.Background(), peerRESTMethodSpeedTest, values, nil, -1)
if err != nil { if err != nil {

View File

@ -18,7 +18,7 @@
package cmd package cmd
const ( const (
peerRESTVersion = "v38" // Convert RPC calls peerRESTVersion = "v39" // add more flags to speedtest API
peerRESTVersionPrefix = SlashSeparator + peerRESTVersion peerRESTVersionPrefix = SlashSeparator + peerRESTVersion
peerRESTPrefix = minioReservedBucketPath + "/peer" peerRESTPrefix = minioReservedBucketPath + "/peer"
peerRESTPath = peerRESTPrefix + peerRESTVersionPrefix peerRESTPath = peerRESTPrefix + peerRESTVersionPrefix
@ -38,31 +38,33 @@ const (
) )
const ( const (
peerRESTBucket = "bucket" peerRESTBucket = "bucket"
peerRESTBuckets = "buckets" peerRESTBuckets = "buckets"
peerRESTUser = "user" peerRESTUser = "user"
peerRESTGroup = "group" peerRESTGroup = "group"
peerRESTUserTemp = "user-temp" peerRESTUserTemp = "user-temp"
peerRESTPolicy = "policy" peerRESTPolicy = "policy"
peerRESTUserOrGroup = "user-or-group" peerRESTUserOrGroup = "user-or-group"
peerRESTUserType = "user-type" peerRESTUserType = "user-type"
peerRESTIsGroup = "is-group" peerRESTIsGroup = "is-group"
peerRESTSignal = "signal" peerRESTSignal = "signal"
peerRESTSubSys = "sub-sys" peerRESTSubSys = "sub-sys"
peerRESTProfiler = "profiler" peerRESTProfiler = "profiler"
peerRESTSize = "size" peerRESTSize = "size"
peerRESTConcurrent = "concurrent" peerRESTConcurrent = "concurrent"
peerRESTDuration = "duration" peerRESTDuration = "duration"
peerRESTStorageClass = "storage-class" peerRESTStorageClass = "storage-class"
peerRESTEnableSha256 = "enableSha256" peerRESTEnableSha256 = "enableSha256"
peerRESTMetricsTypes = "types" peerRESTEnableMultipart = "enableMultipart"
peerRESTDisk = "disk" peerRESTAccessKey = "access-key"
peerRESTHost = "host" peerRESTMetricsTypes = "types"
peerRESTJobID = "job-id" peerRESTDisk = "disk"
peerRESTDepID = "depID" peerRESTHost = "host"
peerRESTStartRebalance = "start-rebalance" peerRESTJobID = "job-id"
peerRESTMetrics = "metrics" peerRESTDepID = "depID"
peerRESTDryRun = "dry-run" peerRESTStartRebalance = "start-rebalance"
peerRESTMetrics = "metrics"
peerRESTDryRun = "dry-run"
peerRESTURL = "url" peerRESTURL = "url"
peerRESTSha256Sum = "sha256sum" peerRESTSha256Sum = "sha256sum"

View File

@ -1059,6 +1059,13 @@ func (s *peerRESTServer) SpeedTestHandler(w http.ResponseWriter, r *http.Request
storageClass := r.Form.Get(peerRESTStorageClass) storageClass := r.Form.Get(peerRESTStorageClass)
bucketName := r.Form.Get(peerRESTBucket) bucketName := r.Form.Get(peerRESTBucket)
enableSha256 := r.Form.Get(peerRESTEnableSha256) == "true" 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) size, err := strconv.Atoi(sizeStr)
if err != nil { if err != nil {
@ -1078,12 +1085,14 @@ func (s *peerRESTServer) SpeedTestHandler(w http.ResponseWriter, r *http.Request
done := keepHTTPResponseAlive(w) done := keepHTTPResponseAlive(w)
result, err := selfSpeedTest(r.Context(), speedTestOpts{ result, err := selfSpeedTest(r.Context(), speedTestOpts{
objectSize: size, objectSize: size,
concurrency: concurrent, concurrency: concurrent,
duration: duration, duration: duration,
storageClass: storageClass, storageClass: storageClass,
bucketName: bucketName, bucketName: bucketName,
enableSha256: enableSha256, enableSha256: enableSha256,
enableMultipart: enableMultipart,
creds: u.Credentials,
}) })
if err != nil { if err != nil {
result.Error = err.Error() result.Error = err.Error()

View File

@ -33,6 +33,7 @@ import (
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/pkg/v2/randreader" "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. // 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() objAPI := newObjectLayerFn()
if objAPI == nil { if objAPI == nil {
return SpeedTestResult{}, errServerNotInitialized return SpeedTestResult{}, errServerNotInitialized
@ -96,7 +97,24 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
popts := minio.PutObjectOptions{ popts := minio.PutObjectOptions{
UserMetadata: userMetadata, UserMetadata: userMetadata,
DisableContentSha256: !opts.enableSha256, 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 var mu sync.Mutex
@ -109,7 +127,7 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
t := time.Now() t := time.Now()
reader := newRandomReader(opts.objectSize) reader := newRandomReader(opts.objectSize)
tmpObjName := pathJoin(objNamePrefix, fmt.Sprintf("%d/%d", i, objCountPerThread[i])) 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 err != nil {
if !contextCanceled(uploadsCtx) && !errors.Is(err, context.Canceled) { if !contextCanceled(uploadsCtx) && !errors.Is(err, context.Canceled) {
errOnce.Do(func() { errOnce.Do(func() {
@ -150,7 +168,7 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
var downloadTTFB madmin.TimeDurations var downloadTTFB madmin.TimeDurations
wg.Add(opts.concurrency) wg.Add(opts.concurrency)
c := minio.Core{Client: globalMinioClient} c := minio.Core{Client: clnt}
for i := 0; i < opts.concurrency; i++ { for i := 0; i < opts.concurrency; i++ {
go func(i int) { go func(i int) {
defer wg.Done() defer wg.Done()

View File

@ -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 // This file is part of MinIO Object Storage stack
// //
@ -27,6 +27,7 @@ import (
"github.com/minio/dperf/pkg/dperf" "github.com/minio/dperf/pkg/dperf"
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/auth"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
) )
@ -41,6 +42,8 @@ type speedTestOpts struct {
storageClass string storageClass string
bucketName string bucketName string
enableSha256 bool enableSha256 bool
enableMultipart bool
creds auth.Credentials
} }
// Get the max throughput and iops numbers. // 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 the default concurrency yields zero results, throw an error.
if throughputHighestResults[i].Downloads == 0 && opts.concurrencyStart == concurrency { 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 the default concurrency yields zero results, throw an error.
if throughputHighestResults[i].Uploads == 0 && opts.concurrencyStart == concurrency { 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{ 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{ sopts := speedTestOpts{
objectSize: opts.objectSize, objectSize: opts.objectSize,
concurrency: concurrency, concurrency: concurrency,
duration: opts.duration, duration: opts.duration,
storageClass: opts.storageClass, storageClass: opts.storageClass,
bucketName: opts.bucketName, bucketName: opts.bucketName,
enableSha256: opts.enableSha256, enableSha256: opts.enableSha256,
enableMultipart: opts.enableMultipart,
creds: opts.creds,
} }
results := globalNotificationSys.SpeedTest(ctx, sopts) results := globalNotificationSys.SpeedTest(ctx, sopts)