mirror of
https://github.com/minio/minio.git
synced 2025-02-05 10:48:07 -05:00
Merge branch 'master' into add-bucket-limit-v2
This commit is contained in:
commit
5e814a1c85
5
.github/workflows/run-mint.sh
vendored
5
.github/workflows/run-mint.sh
vendored
@ -7,6 +7,7 @@ export ACCESS_KEY="$2"
|
||||
export SECRET_KEY="$3"
|
||||
export JOB_NAME="$4"
|
||||
export MINT_MODE="full"
|
||||
export MINT_NO_FULL_OBJECT="true"
|
||||
|
||||
docker system prune -f || true
|
||||
docker volume prune -f || true
|
||||
@ -15,6 +16,9 @@ docker volume rm $(docker volume ls -f dangling=true) || true
|
||||
## change working directory
|
||||
cd .github/workflows/mint
|
||||
|
||||
## always pull latest
|
||||
docker pull docker.io/minio/mint:edge
|
||||
|
||||
docker-compose -f minio-${MODE}.yaml up -d
|
||||
sleep 1m
|
||||
|
||||
@ -35,6 +39,7 @@ docker run --rm --net=mint_default \
|
||||
-e ACCESS_KEY="${ACCESS_KEY}" \
|
||||
-e SECRET_KEY="${SECRET_KEY}" \
|
||||
-e ENABLE_HTTPS=0 \
|
||||
-e MINT_NO_FULL_OBJECT="${MINT_NO_FULL_OBJECT}" \
|
||||
-e MINT_MODE="${MINT_MODE}" \
|
||||
docker.io/minio/mint:edge
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
[![MinIO](https://raw.githubusercontent.com/minio/minio/master/.github/logo.svg?sanitize=true)](https://min.io)
|
||||
|
||||
MinIO is a High Performance Object Storage released under GNU Affero General Public License v3.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads.
|
||||
MinIO is a High Performance Object Storage released under GNU Affero General Public License v3.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads. To learn more about what MinIO is doing for AI storage, go to [AI storage documentation](https://min.io/solutions/object-storage-for-ai).
|
||||
|
||||
This README provides quickstart instructions on running MinIO on bare metal hardware, including container-based installations. For Kubernetes environments, use the [MinIO Kubernetes Operator](https://github.com/minio/operator/blob/master/README.md).
|
||||
|
||||
|
@ -2032,6 +2032,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
// Get current object layer instance.
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportIAMAction)
|
||||
if objectAPI == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
// Initialize a zip writer which will provide a zipped content
|
||||
@ -2254,17 +2255,13 @@ func (a adminAPIHandlers) ImportIAMV2(w http.ResponseWriter, r *http.Request) {
|
||||
func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiVer string) {
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
// Validate signature, permissions and get current object layer instance.
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ImportIAMAction)
|
||||
if objectAPI == nil || globalNotificationSys == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||
if s3Err != ErrNone {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||
@ -2354,38 +2351,12 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
|
||||
return
|
||||
}
|
||||
|
||||
if (cred.IsTemp() || cred.IsServiceAccount()) && cred.ParentUser == accessKey {
|
||||
// Incoming access key matches parent user then we should
|
||||
// reject password change requests.
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAddUserInvalidArgument, err, allUsersFile, accessKey), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if accessKey has beginning and end space characters, this only applies to new users.
|
||||
if !exists && hasSpaceBE(accessKey) {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, err, allUsersFile, accessKey), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
checkDenyOnly := false
|
||||
if accessKey == cred.AccessKey {
|
||||
// Check that there is no explicit deny - otherwise it's allowed
|
||||
// to change one's own password.
|
||||
checkDenyOnly = true
|
||||
}
|
||||
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: policy.CreateUserAdminAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
DenyOnly: checkDenyOnly,
|
||||
}) {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allUsersFile, accessKey), r.URL)
|
||||
return
|
||||
}
|
||||
if _, err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil {
|
||||
failed.Users = append(failed.Users, madmin.IAMErrEntity{Name: accessKey, Error: err})
|
||||
} else {
|
||||
@ -2485,17 +2456,6 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: policy.CreateServiceAccountAdminAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allSvcAcctsFile, user), r.URL)
|
||||
return
|
||||
}
|
||||
updateReq := true
|
||||
_, _, err = globalIAMSys.GetServiceAccount(ctx, svcAcctReq.AccessKey)
|
||||
if err != nil {
|
||||
|
@ -1338,6 +1338,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
}
|
||||
opts.EncryptFn = metadataEncrypter(objectEncryptionKey)
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
|
100
cmd/iam-store.go
100
cmd/iam-store.go
@ -26,6 +26,7 @@ import (
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
@ -37,6 +38,7 @@ import (
|
||||
"github.com/minio/minio/internal/jwt"
|
||||
"github.com/minio/pkg/v3/env"
|
||||
"github.com/minio/pkg/v3/policy"
|
||||
"github.com/minio/pkg/v3/sync/errgroup"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
@ -357,6 +359,68 @@ func (c *iamCache) removeGroupFromMembershipsMap(group string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *iamCache) policyDBGetGroups(store *IAMStoreSys, userPolicyPresent bool, groups ...string) ([]string, error) {
|
||||
var policies []string
|
||||
for _, group := range groups {
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
g, ok := c.iamGroupsMap[group]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Group is disabled, so we return no policy - this
|
||||
// ensures the request is denied.
|
||||
if g.Status == statusDisabled {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
policy, ok := c.iamGroupPolicyMap.Load(group)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
policies = append(policies, policy.toSlice()...)
|
||||
}
|
||||
|
||||
found := len(policies) > 0
|
||||
if found {
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
if userPolicyPresent {
|
||||
// if user mapping present and no group policies found
|
||||
// rely on user policy for access, instead of fallback.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
|
||||
// no mappings found, fallback for all groups.
|
||||
g := errgroup.WithNErrs(len(groups)).WithConcurrency(10) // load like 10 groups at a time.
|
||||
|
||||
for index := range groups {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
err := store.loadMappedPolicy(context.TODO(), groups[index], regUser, true, c.iamGroupPolicyMap)
|
||||
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
return err
|
||||
}
|
||||
if errors.Is(err, errNoSuchPolicy) {
|
||||
return nil
|
||||
}
|
||||
policy, _ := c.iamGroupPolicyMap.Load(groups[index])
|
||||
mu.Lock()
|
||||
policies = append(policies, policy.toSlice()...)
|
||||
mu.Unlock()
|
||||
return nil
|
||||
}, index)
|
||||
}
|
||||
|
||||
err := errors.Join(g.Wait()...)
|
||||
return policies, err
|
||||
}
|
||||
|
||||
// policyDBGet - lower-level helper; does not take locks.
|
||||
//
|
||||
// If a group is passed, it returns policies associated with the group.
|
||||
@ -676,7 +740,8 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context, firstTime bool) erro
|
||||
type IAMStoreSys struct {
|
||||
IAMStorageAPI
|
||||
|
||||
group *singleflight.Group
|
||||
group *singleflight.Group
|
||||
policy *singleflight.Group
|
||||
}
|
||||
|
||||
// HasWatcher - returns if the storage system has a watcher.
|
||||
@ -757,21 +822,32 @@ func (store *IAMStoreSys) PolicyDBGet(name string, groups ...string) ([]string,
|
||||
cache := store.rlock()
|
||||
defer store.runlock()
|
||||
|
||||
policies, _, err := cache.policyDBGet(store, name, false, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userPolicyPresent := len(policies) > 0
|
||||
for _, group := range groups {
|
||||
ps, _, err := cache.policyDBGet(store, group, true, userPolicyPresent)
|
||||
getPolicies := func() ([]string, error) {
|
||||
policies, _, err := cache.policyDBGet(store, name, false, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policies = append(policies, ps...)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
userPolicyPresent := len(policies) > 0
|
||||
|
||||
groupPolicies, err := cache.policyDBGetGroups(store, userPolicyPresent, groups...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policies = append(policies, groupPolicies...)
|
||||
return policies, nil
|
||||
}
|
||||
if store.policy != nil {
|
||||
val, err, _ := store.policy.Do(name, func() (interface{}, error) {
|
||||
return getPolicies()
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return val.([]string), nil
|
||||
}
|
||||
return getPolicies()
|
||||
}
|
||||
|
||||
// AddUsersToGroup - adds users to group, creating the group if needed.
|
||||
|
@ -178,13 +178,18 @@ func (sys *IAMSys) initStore(objAPI ObjectLayer, etcdClient *etcd.Client) {
|
||||
}
|
||||
|
||||
if etcdClient == nil {
|
||||
var group *singleflight.Group
|
||||
var (
|
||||
group *singleflight.Group
|
||||
policy *singleflight.Group
|
||||
)
|
||||
if env.Get("_MINIO_IAM_SINGLE_FLIGHT", config.EnableOn) == config.EnableOn {
|
||||
group = &singleflight.Group{}
|
||||
policy = &singleflight.Group{}
|
||||
}
|
||||
sys.store = &IAMStoreSys{
|
||||
IAMStorageAPI: newIAMObjectStore(objAPI, sys.usersSysType),
|
||||
group: group,
|
||||
policy: policy,
|
||||
}
|
||||
} else {
|
||||
sys.store = &IAMStoreSys{IAMStorageAPI: newIAMEtcdStore(etcdClient, sys.usersSysType)}
|
||||
|
@ -404,12 +404,7 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
|
||||
metadata = make(map[string]string)
|
||||
}
|
||||
|
||||
wantCRC, err := hash.GetContentChecksum(hdr)
|
||||
if err != nil {
|
||||
return opts, fmt.Errorf("invalid/unknown checksum sent: %v", err)
|
||||
}
|
||||
etag := strings.TrimSpace(hdr.Get(xhttp.MinIOSourceETag))
|
||||
|
||||
if crypto.S3KMS.IsRequested(hdr) {
|
||||
keyID, context, err := crypto.S3KMS.ParseHTTP(hdr)
|
||||
if err != nil {
|
||||
@ -423,7 +418,6 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
|
||||
ServerSideEncryption: sseKms,
|
||||
UserDefined: metadata,
|
||||
MTime: mtime,
|
||||
WantChecksum: wantCRC,
|
||||
PreserveETag: etag,
|
||||
}, nil
|
||||
}
|
||||
@ -438,7 +432,6 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
|
||||
opts.ReplicationSourceRetentionTimestamp = retaintimestmp
|
||||
opts.ReplicationSourceTaggingTimestamp = taggingtimestmp
|
||||
opts.PreserveETag = etag
|
||||
opts.WantChecksum = wantCRC
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
@ -1899,6 +1899,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
var reader io.Reader
|
||||
reader = rd
|
||||
|
||||
var opts ObjectOptions
|
||||
opts, err = putOptsFromReq(ctx, r, bucket, object, metadata)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
actualSize := size
|
||||
var idxCb func() []byte
|
||||
if isCompressible(r.Header, object) && size > minCompressibleSize {
|
||||
@ -1915,6 +1922,8 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||
return
|
||||
}
|
||||
opts.WantChecksum = actualReader.Checksum()
|
||||
|
||||
// Set compression metrics.
|
||||
var s2c io.ReadCloser
|
||||
wantEncryption := crypto.Requested(r.Header)
|
||||
@ -1945,22 +1954,17 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if err := hashReader.AddChecksum(r, size < 0); err != nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||
return
|
||||
if size >= 0 {
|
||||
if err := hashReader.AddChecksum(r, false); err != nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||
return
|
||||
}
|
||||
opts.WantChecksum = hashReader.Checksum()
|
||||
}
|
||||
|
||||
rawReader := hashReader
|
||||
pReader := NewPutObjReader(rawReader)
|
||||
|
||||
var opts ObjectOptions
|
||||
opts, err = putOptsFromReq(ctx, r, bucket, object, metadata)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
opts.IndexCB = idxCb
|
||||
opts.WantChecksum = hashReader.Checksum()
|
||||
|
||||
if opts.PreserveETag != "" ||
|
||||
r.Header.Get(xhttp.IfMatch) != "" ||
|
||||
|
@ -56,10 +56,12 @@ func newPostPolicyBytesV4WithContentRange(credential, bucketName, objectKey stri
|
||||
credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
|
||||
// Add the meta-uuid string, set to 1234
|
||||
uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
|
||||
// Add the content-encoding string, set to gzip.
|
||||
contentEncodingConditionStr := fmt.Sprintf(`["eq", "$content-encoding", "%s"]`, "gzip")
|
||||
|
||||
// Combine all conditions into one string.
|
||||
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr,
|
||||
keyConditionStr, contentLengthCondStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr)
|
||||
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr,
|
||||
keyConditionStr, contentLengthCondStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr, contentEncodingConditionStr)
|
||||
retStr := "{"
|
||||
retStr = retStr + expirationStr + ","
|
||||
retStr += conditionStr
|
||||
@ -85,9 +87,11 @@ func newPostPolicyBytesV4(credential, bucketName, objectKey string, expiration t
|
||||
credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
|
||||
// Add the meta-uuid string, set to 1234
|
||||
uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
|
||||
// Add the content-encoding string, set to gzip
|
||||
contentEncodingConditionStr := fmt.Sprintf(`["eq", "$content-encoding", "%s"]`, "gzip")
|
||||
|
||||
// Combine all conditions into one string.
|
||||
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s]`, bucketConditionStr, keyConditionStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr)
|
||||
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr, keyConditionStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr, contentEncodingConditionStr)
|
||||
retStr := "{"
|
||||
retStr = retStr + expirationStr + ","
|
||||
retStr += conditionStr
|
||||
@ -331,7 +335,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
||||
accessKey: credentials.AccessKey,
|
||||
secretKey: credentials.SecretKey,
|
||||
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
|
||||
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`,
|
||||
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"],["eq", "$content-encoding", "gzip"]]}`,
|
||||
},
|
||||
// Success case, no multipart filename.
|
||||
{
|
||||
@ -341,7 +345,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
||||
accessKey: credentials.AccessKey,
|
||||
secretKey: credentials.SecretKey,
|
||||
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
|
||||
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`,
|
||||
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"],["eq", "$content-encoding", "gzip"]]}`,
|
||||
noFilename: true,
|
||||
},
|
||||
// Success case, big body.
|
||||
@ -352,7 +356,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
||||
accessKey: credentials.AccessKey,
|
||||
secretKey: credentials.SecretKey,
|
||||
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
|
||||
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`,
|
||||
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"],["eq", "$content-encoding", "gzip"]]}`,
|
||||
},
|
||||
// Corrupted Base 64 result
|
||||
{
|
||||
@ -447,7 +451,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
||||
malformedBody: false,
|
||||
ignoreContentLength: false,
|
||||
},
|
||||
// Failed with Content-Length not specified.
|
||||
// Success with Content-Length not specified.
|
||||
{
|
||||
objectName: "test",
|
||||
data: bytes.Repeat([]byte("a"), 1025),
|
||||
@ -547,7 +551,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
dates := []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}
|
||||
policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-meta-uuid", "1234"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}`
|
||||
policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-meta-uuid", "1234"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$content-encoding", "gzip"]]}`
|
||||
|
||||
// Generate the final policy document
|
||||
policy = fmt.Sprintf(policy, dates...)
|
||||
|
@ -53,18 +53,6 @@ var startsWithConds = map[string]bool{
|
||||
"$x-amz-date": false,
|
||||
}
|
||||
|
||||
var postPolicyIgnoreKeys = map[string]bool{
|
||||
"Policy": true,
|
||||
xhttp.AmzSignature: true,
|
||||
xhttp.ContentEncoding: true,
|
||||
http.CanonicalHeaderKey(xhttp.AmzChecksumAlgo): true,
|
||||
http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32): true,
|
||||
http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32C): true,
|
||||
http.CanonicalHeaderKey(xhttp.AmzChecksumSHA1): true,
|
||||
http.CanonicalHeaderKey(xhttp.AmzChecksumSHA256): true,
|
||||
http.CanonicalHeaderKey(xhttp.AmzChecksumMode): true,
|
||||
}
|
||||
|
||||
// Add policy conditionals.
|
||||
const (
|
||||
policyCondEqual = "eq"
|
||||
@ -272,60 +260,57 @@ func checkPolicyCond(op string, input1, input2 string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// S3 docs: "Each form field that you specify in a form (except x-amz-signature, file, policy, and field names
|
||||
// that have an x-ignore- prefix) must appear in the list of conditions."
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
|
||||
// keyInPolicyExceptions - list of keys that, when present in the form, can be missing in the conditions of the policy.
|
||||
var keyInPolicyExceptions = map[string]bool{
|
||||
xhttp.AmzSignature: true,
|
||||
"File": true,
|
||||
"Policy": true,
|
||||
|
||||
// MinIO specific exceptions to the general S3 rule above.
|
||||
encrypt.SseKmsKeyID: true,
|
||||
encrypt.SseEncryptionContext: true,
|
||||
encrypt.SseCustomerAlgorithm: true,
|
||||
encrypt.SseCustomerKey: true,
|
||||
encrypt.SseCustomerKeyMD5: true,
|
||||
}
|
||||
|
||||
// checkPostPolicy - apply policy conditions and validate input values.
|
||||
// (http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html)
|
||||
// Note that content-length-range is checked in the API handler function PostPolicyBucketHandler.
|
||||
// formValues is the already-canonicalized form values from the POST request.
|
||||
func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) error {
|
||||
// Check if policy document expiry date is still not reached
|
||||
if !postPolicyForm.Expiration.After(UTCNow()) {
|
||||
return fmt.Errorf("Invalid according to Policy: Policy expired")
|
||||
}
|
||||
// check all formValues appear in postPolicyForm or return error. #https://github.com/minio/minio/issues/17391
|
||||
checkHeader := map[string][]string{}
|
||||
ignoreKeys := map[string]bool{}
|
||||
for key, value := range formValues {
|
||||
switch {
|
||||
case ignoreKeys[key], postPolicyIgnoreKeys[key], strings.HasPrefix(key, encrypt.SseGenericHeader):
|
||||
continue
|
||||
case strings.HasPrefix(key, "X-Amz-Ignore-"):
|
||||
ignoreKey := strings.Replace(key, "X-Amz-Ignore-", "", 1)
|
||||
ignoreKeys[ignoreKey] = true
|
||||
// if it have already
|
||||
delete(checkHeader, ignoreKey)
|
||||
default:
|
||||
checkHeader[key] = value
|
||||
}
|
||||
}
|
||||
// map to store the metadata
|
||||
metaMap := make(map[string]string)
|
||||
for _, policy := range postPolicyForm.Conditions.Policies {
|
||||
if strings.HasPrefix(policy.Key, "$x-amz-meta-") {
|
||||
formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$"))
|
||||
metaMap[formCanonicalName] = policy.Value
|
||||
}
|
||||
}
|
||||
// Check if any extra metadata field is passed as input
|
||||
for key := range formValues {
|
||||
if strings.HasPrefix(key, "X-Amz-Meta-") {
|
||||
if _, ok := metaMap[key]; !ok {
|
||||
return fmt.Errorf("Invalid according to Policy: Extra input fields: %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flag to indicate if all policies conditions are satisfied
|
||||
var condPassed bool
|
||||
// mustFindInPolicy is a map to list all the keys that we must find in the policy as
|
||||
// we process it below. At the end of checkPostPolicy function, if any key is left in
|
||||
// this map, that's an error.
|
||||
mustFindInPolicy := make(map[string][]string, len(formValues))
|
||||
for key, values := range formValues {
|
||||
if keyInPolicyExceptions[key] || strings.HasPrefix(key, "X-Ignore-") {
|
||||
continue
|
||||
}
|
||||
mustFindInPolicy[key] = values
|
||||
}
|
||||
|
||||
// Iterate over policy conditions and check them against received form fields
|
||||
for _, policy := range postPolicyForm.Conditions.Policies {
|
||||
// Form fields names are in canonical format, convert conditions names
|
||||
// to canonical for simplification purpose, so `$key` will become `Key`
|
||||
formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$"))
|
||||
|
||||
// Operator for the current policy condition
|
||||
op := policy.Operator
|
||||
// Multiple values should not occur
|
||||
if len(checkHeader[formCanonicalName]) >= 2 {
|
||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]. FormValues have multiple values: [%s]", op, policy.Key, policy.Value, strings.Join(checkHeader[formCanonicalName], ", "))
|
||||
|
||||
// Multiple values are not allowed for a single form field
|
||||
if len(mustFindInPolicy[formCanonicalName]) >= 2 {
|
||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]. FormValues have multiple values: [%s]", op, policy.Key, policy.Value, strings.Join(mustFindInPolicy[formCanonicalName], ", "))
|
||||
}
|
||||
|
||||
// If the current policy condition is known
|
||||
if startsWithSupported, condFound := startsWithConds[policy.Key]; condFound {
|
||||
// Check if the current condition supports starts-with operator
|
||||
@ -333,35 +318,35 @@ func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) erro
|
||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
|
||||
}
|
||||
// Check if current policy condition is satisfied
|
||||
condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value)
|
||||
if !condPassed {
|
||||
if !checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) {
|
||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
|
||||
}
|
||||
} else if strings.HasPrefix(policy.Key, "$x-amz-meta-") || strings.HasPrefix(policy.Key, "$x-amz-") {
|
||||
// This covers all conditions X-Amz-Meta-* and X-Amz-*
|
||||
// Check if policy condition is satisfied
|
||||
condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value)
|
||||
if !condPassed {
|
||||
if !checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) {
|
||||
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]", op, policy.Key, policy.Value)
|
||||
}
|
||||
}
|
||||
delete(checkHeader, formCanonicalName)
|
||||
delete(mustFindInPolicy, formCanonicalName)
|
||||
}
|
||||
// For SignV2 - Signature/AWSAccessKeyId field will be ignored.
|
||||
|
||||
// For SignV2 - Signature/AWSAccessKeyId fields do not need to be in the policy
|
||||
if _, ok := formValues[xhttp.AmzSignatureV2]; ok {
|
||||
delete(checkHeader, xhttp.AmzSignatureV2)
|
||||
for k := range checkHeader {
|
||||
delete(mustFindInPolicy, xhttp.AmzSignatureV2)
|
||||
for k := range mustFindInPolicy {
|
||||
// case-insensitivity for AWSAccessKeyId
|
||||
if strings.EqualFold(k, xhttp.AmzAccessKeyID) {
|
||||
delete(checkHeader, k)
|
||||
delete(mustFindInPolicy, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(checkHeader) != 0 {
|
||||
logKeys := make([]string, 0, len(checkHeader))
|
||||
for key := range checkHeader {
|
||||
// Check mustFindInPolicy to see if any key is left, if so, it was not found in policy and we return an error.
|
||||
if len(mustFindInPolicy) != 0 {
|
||||
logKeys := make([]string, 0, len(mustFindInPolicy))
|
||||
for key := range mustFindInPolicy {
|
||||
logKeys = append(logKeys, key)
|
||||
}
|
||||
return fmt.Errorf("Each form field that you specify in a form must appear in the list of policy conditions. %q not specified in the policy.", strings.Join(logKeys, ", "))
|
||||
|
@ -20,12 +20,12 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
minio "github.com/minio/minio-go/v7"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
)
|
||||
|
||||
func TestParsePostPolicyForm(t *testing.T) {
|
||||
@ -78,6 +78,28 @@ func TestParsePostPolicyForm(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type formValues struct {
|
||||
http.Header
|
||||
}
|
||||
|
||||
func newFormValues() formValues {
|
||||
return formValues{make(http.Header)}
|
||||
}
|
||||
|
||||
func (f formValues) Set(key, value string) formValues {
|
||||
f.Header.Set(key, value)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f formValues) Add(key, value string) formValues {
|
||||
f.Header.Add(key, value)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f formValues) Clone() formValues {
|
||||
return formValues{f.Header.Clone()}
|
||||
}
|
||||
|
||||
// Test Post Policy parsing and checking conditions
|
||||
func TestPostPolicyForm(t *testing.T) {
|
||||
pp := minio.NewPostPolicy()
|
||||
@ -85,76 +107,193 @@ func TestPostPolicyForm(t *testing.T) {
|
||||
pp.SetContentType("image/jpeg")
|
||||
pp.SetUserMetadata("uuid", "14365123651274")
|
||||
pp.SetKeyStartsWith("user/user1/filename")
|
||||
pp.SetContentLengthRange(1048579, 10485760)
|
||||
pp.SetContentLengthRange(100, 999999) // not testable from this layer, condition is checked in the API handler.
|
||||
pp.SetSuccessStatusAction("201")
|
||||
pp.SetCondition("eq", "X-Amz-Credential", "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request")
|
||||
pp.SetCondition("eq", "X-Amz-Algorithm", "AWS4-HMAC-SHA256")
|
||||
pp.SetCondition("eq", xhttp.AmzDate, "20160727T000000Z")
|
||||
|
||||
defaultFormVals := newFormValues()
|
||||
defaultFormVals.Set("Bucket", "testbucket")
|
||||
defaultFormVals.Set("Content-Type", "image/jpeg")
|
||||
defaultFormVals.Set(xhttp.AmzMetaUUID, "14365123651274")
|
||||
defaultFormVals.Set("Key", "user/user1/filename/${filename}/myfile.txt")
|
||||
defaultFormVals.Set("X-Amz-Credential", "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request")
|
||||
defaultFormVals.Set("X-Amz-Algorithm", "AWS4-HMAC-SHA256")
|
||||
defaultFormVals.Set(xhttp.AmzDate, "20160727T000000Z")
|
||||
defaultFormVals.Set("Success_action_status", "201")
|
||||
|
||||
policyCondFailedErr := "Invalid according to Policy: Policy Condition failed"
|
||||
|
||||
type testCase struct {
|
||||
Bucket string
|
||||
Key string
|
||||
XAmzDate string
|
||||
XAmzAlgorithm string
|
||||
XAmzCredential string
|
||||
XAmzMetaUUID string
|
||||
ContentType string
|
||||
SuccessActionStatus string
|
||||
Policy string
|
||||
Expired bool
|
||||
expectedErr error
|
||||
name string
|
||||
fv formValues
|
||||
expired bool
|
||||
wantErr string
|
||||
}
|
||||
|
||||
// Test case just contains fields we override from defaultFormVals.
|
||||
testCases := []testCase{
|
||||
// Everything is fine with this test
|
||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", SuccessActionStatus: "201", XAmzCredential: "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: nil},
|
||||
// Expired policy document
|
||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", SuccessActionStatus: "201", XAmzCredential: "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", Expired: true, expectedErr: fmt.Errorf("Invalid according to Policy: Policy expired")},
|
||||
// Different AMZ date
|
||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "2017T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
||||
// Key which doesn't start with user/user1/filename
|
||||
{Bucket: "testbucket", Key: "myfile.txt", XAmzDate: "20160727T000000Z", XAmzMetaUUID: "14365123651274", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
||||
// Incorrect bucket name.
|
||||
{Bucket: "incorrect", Key: "user/user1/filename/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
||||
// Incorrect key name
|
||||
{Bucket: "testbucket", Key: "incorrect", XAmzDate: "20160727T000000Z", XAmzMetaUUID: "14365123651274", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
||||
// Incorrect date
|
||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "incorrect", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
||||
// Incorrect ContentType
|
||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "incorrect", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")},
|
||||
// Incorrect Metadata
|
||||
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "151274", SuccessActionStatus: "201", XAmzCredential: "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed: [eq, $x-amz-meta-uuid, 14365123651274]")},
|
||||
{
|
||||
name: "happy path no errors",
|
||||
fv: defaultFormVals.Clone(),
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "expired policy document",
|
||||
fv: defaultFormVals.Clone(),
|
||||
expired: true,
|
||||
wantErr: "Invalid according to Policy: Policy expired",
|
||||
},
|
||||
{
|
||||
name: "different AMZ date",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.AmzDate, "2017T000000Z"),
|
||||
wantErr: policyCondFailedErr,
|
||||
},
|
||||
{
|
||||
name: "incorrect date",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.AmzDate, "incorrect"),
|
||||
wantErr: policyCondFailedErr,
|
||||
},
|
||||
{
|
||||
name: "key which doesn't start with user/user1/filename",
|
||||
fv: defaultFormVals.Clone().Set("Key", "myfile.txt"),
|
||||
wantErr: policyCondFailedErr,
|
||||
},
|
||||
{
|
||||
name: "incorrect key name",
|
||||
fv: defaultFormVals.Clone().Set("Key", "incorrect"),
|
||||
wantErr: policyCondFailedErr,
|
||||
},
|
||||
{
|
||||
name: "incorrect bucket name",
|
||||
fv: defaultFormVals.Clone().Set("Bucket", "incorrect"),
|
||||
wantErr: policyCondFailedErr,
|
||||
},
|
||||
{
|
||||
name: "incorrect ContentType",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.ContentType, "incorrect"),
|
||||
wantErr: policyCondFailedErr,
|
||||
},
|
||||
{
|
||||
name: "incorrect X-Amz-Algorithm",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.AmzAlgorithm, "incorrect"),
|
||||
wantErr: policyCondFailedErr,
|
||||
},
|
||||
{
|
||||
name: "incorrect X-Amz-Credential",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.AmzCredential, "incorrect"),
|
||||
wantErr: policyCondFailedErr,
|
||||
},
|
||||
{
|
||||
name: "incorrect metadata uuid",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.AmzMetaUUID, "151274"),
|
||||
wantErr: "Invalid according to Policy: Policy Condition failed: [eq, $x-amz-meta-uuid, 14365123651274]",
|
||||
},
|
||||
{
|
||||
name: "unknown key XAmzMetaName is error as it does not appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.AmzMetaName, "my-name"),
|
||||
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Meta-Name" not specified in the policy.`,
|
||||
},
|
||||
{
|
||||
name: "unknown key XAmzChecksumAlgo is error as it does not appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumAlgo), "algo-val"),
|
||||
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Algorithm" not specified in the policy.`,
|
||||
},
|
||||
{
|
||||
name: "unknown key XAmzChecksumCRC32 is error as it does not appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32), "crc32-val"),
|
||||
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Crc32" not specified in the policy.`,
|
||||
},
|
||||
{
|
||||
name: "unknown key XAmzChecksumCRC32C is error as it does not appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32C), "crc32c-val"),
|
||||
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Crc32c" not specified in the policy.`,
|
||||
},
|
||||
{
|
||||
name: "unknown key XAmzChecksumSHA1 is error as it does not appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumSHA1), "sha1-val"),
|
||||
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Sha1" not specified in the policy.`,
|
||||
},
|
||||
{
|
||||
name: "unknown key XAmzChecksumSHA256 is error as it does not appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumSHA256), "sha256-val"),
|
||||
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Sha256" not specified in the policy.`,
|
||||
},
|
||||
{
|
||||
name: "unknown key XAmzChecksumMode is error as it does not appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumMode), "mode-val"),
|
||||
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Mode" not specified in the policy.`,
|
||||
},
|
||||
{
|
||||
name: "unknown key Content-Encoding is error as it does not appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.ContentEncoding), "encoding-val"),
|
||||
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "Content-Encoding" not specified in the policy.`,
|
||||
},
|
||||
{
|
||||
name: "many bucket values",
|
||||
fv: defaultFormVals.Clone().Add("Bucket", "anotherbucket"),
|
||||
wantErr: "Invalid according to Policy: Policy Condition failed: [eq, $bucket, testbucket]. FormValues have multiple values: [testbucket, anotherbucket]",
|
||||
},
|
||||
{
|
||||
name: "XAmzSignature does not have to appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.AmzSignature, "my-signature"),
|
||||
},
|
||||
{
|
||||
name: "XIgnoreFoo does not have to appear in policy",
|
||||
fv: defaultFormVals.Clone().Set("X-Ignore-Foo", "my-foo-value"),
|
||||
},
|
||||
{
|
||||
name: "File does not have to appear in policy",
|
||||
fv: defaultFormVals.Clone().Set("File", "file-value"),
|
||||
},
|
||||
{
|
||||
name: "Signature does not have to appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.AmzSignatureV2, "signature-value"),
|
||||
},
|
||||
{
|
||||
name: "AWSAccessKeyID does not have to appear in policy",
|
||||
fv: defaultFormVals.Clone().Set(xhttp.AmzAccessKeyID, "access").Set(xhttp.AmzSignatureV2, "signature-value"),
|
||||
},
|
||||
{
|
||||
name: "any form value starting with X-Amz-Server-Side-Encryption- does not have to appear in policy",
|
||||
fv: defaultFormVals.Clone().
|
||||
Set(xhttp.AmzServerSideEncryptionKmsContext, "context-val").
|
||||
Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, "algo-val"),
|
||||
},
|
||||
}
|
||||
// Validate all the test cases.
|
||||
for i, tt := range testCases {
|
||||
formValues := make(http.Header)
|
||||
formValues.Set("Bucket", tt.Bucket)
|
||||
formValues.Set("Key", tt.Key)
|
||||
formValues.Set("Content-Type", tt.ContentType)
|
||||
formValues.Set("X-Amz-Date", tt.XAmzDate)
|
||||
formValues.Set("X-Amz-Meta-Uuid", tt.XAmzMetaUUID)
|
||||
formValues.Set("X-Amz-Algorithm", tt.XAmzAlgorithm)
|
||||
formValues.Set("X-Amz-Credential", tt.XAmzCredential)
|
||||
if tt.Expired {
|
||||
// Expired already.
|
||||
pp.SetExpires(UTCNow().AddDate(0, 0, -10))
|
||||
} else {
|
||||
// Expires in 10 days.
|
||||
pp.SetExpires(UTCNow().AddDate(0, 0, 10))
|
||||
}
|
||||
|
||||
formValues.Set("Policy", base64.StdEncoding.EncodeToString([]byte(pp.String())))
|
||||
formValues.Set("Success_action_status", tt.SuccessActionStatus)
|
||||
policyBytes, err := base64.StdEncoding.DecodeString(base64.StdEncoding.EncodeToString([]byte(pp.String())))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Run tests
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.expired {
|
||||
// Expired already.
|
||||
pp.SetExpires(UTCNow().AddDate(0, 0, -10))
|
||||
} else {
|
||||
// Expires in 10 days.
|
||||
pp.SetExpires(UTCNow().AddDate(0, 0, 10))
|
||||
}
|
||||
|
||||
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tt.fv.Set("Policy", base64.StdEncoding.EncodeToString([]byte(pp.String())))
|
||||
|
||||
err = checkPostPolicy(formValues, postPolicyForm)
|
||||
if err != nil && tt.expectedErr != nil && err.Error() != tt.expectedErr.Error() {
|
||||
t.Fatalf("Test %d:, Expected %s, got %s", i+1, tt.expectedErr.Error(), err.Error())
|
||||
}
|
||||
policyBytes, err := base64.StdEncoding.DecodeString(base64.StdEncoding.EncodeToString([]byte(pp.String())))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
errStr := ""
|
||||
err = checkPostPolicy(tt.fv.Header, postPolicyForm)
|
||||
if err != nil {
|
||||
errStr = err.Error()
|
||||
}
|
||||
if errStr != tt.wantErr {
|
||||
t.Errorf("test: '%s', want error: '%s', got error: '%s'", tt.name, tt.wantErr, errStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -464,16 +464,20 @@ func (client *storageRESTClient) WriteAll(ctx context.Context, volume string, pa
|
||||
// CheckParts - stat all file parts.
|
||||
func (client *storageRESTClient) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) (*CheckPartsResp, error) {
|
||||
var resp *CheckPartsResp
|
||||
resp, err := storageCheckPartsRPC.Call(ctx, client.gridConn, &CheckPartsHandlerParams{
|
||||
st, err := storageCheckPartsRPC.Call(ctx, client.gridConn, &CheckPartsHandlerParams{
|
||||
DiskID: *client.diskID.Load(),
|
||||
Volume: volume,
|
||||
FilePath: path,
|
||||
FI: fi,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, toStorageErr(err)
|
||||
}
|
||||
return resp, nil
|
||||
err = st.Results(func(r *CheckPartsResp) error {
|
||||
resp = r
|
||||
return nil
|
||||
})
|
||||
return resp, toStorageErr(err)
|
||||
}
|
||||
|
||||
// RenameData - rename source path to destination path atomically, metadata and data file.
|
||||
|
@ -56,7 +56,7 @@ type storageRESTServer struct {
|
||||
}
|
||||
|
||||
var (
|
||||
storageCheckPartsRPC = grid.NewSingleHandler[*CheckPartsHandlerParams, *CheckPartsResp](grid.HandlerCheckParts2, func() *CheckPartsHandlerParams { return &CheckPartsHandlerParams{} }, func() *CheckPartsResp { return &CheckPartsResp{} })
|
||||
storageCheckPartsRPC = grid.NewStream[*CheckPartsHandlerParams, grid.NoPayload, *CheckPartsResp](grid.HandlerCheckParts3, func() *CheckPartsHandlerParams { return &CheckPartsHandlerParams{} }, nil, func() *CheckPartsResp { return &CheckPartsResp{} })
|
||||
storageDeleteFileRPC = grid.NewSingleHandler[*DeleteFileHandlerParams, grid.NoPayload](grid.HandlerDeleteFile, func() *DeleteFileHandlerParams { return &DeleteFileHandlerParams{} }, grid.NewNoPayload).AllowCallRequestPool(true)
|
||||
storageDeleteVersionRPC = grid.NewSingleHandler[*DeleteVersionHandlerParams, grid.NoPayload](grid.HandlerDeleteVersion, func() *DeleteVersionHandlerParams { return &DeleteVersionHandlerParams{} }, grid.NewNoPayload)
|
||||
storageDiskInfoRPC = grid.NewSingleHandler[*DiskInfoOptions, *DiskInfo](grid.HandlerDiskInfo, func() *DiskInfoOptions { return &DiskInfoOptions{} }, func() *DiskInfo { return &DiskInfo{} }).WithSharedResponse().AllowCallRequestPool(true)
|
||||
@ -456,16 +456,20 @@ func (s *storageRESTServer) UpdateMetadataHandler(p *MetadataHandlerParams) (gri
|
||||
return grid.NewNPErr(s.getStorage().UpdateMetadata(context.Background(), volume, filePath, p.FI, p.UpdateOpts))
|
||||
}
|
||||
|
||||
// CheckPartsHandler - check if a file metadata exists.
|
||||
func (s *storageRESTServer) CheckPartsHandler(p *CheckPartsHandlerParams) (*CheckPartsResp, *grid.RemoteErr) {
|
||||
// CheckPartsHandler - check if a file parts exists.
|
||||
func (s *storageRESTServer) CheckPartsHandler(ctx context.Context, p *CheckPartsHandlerParams, out chan<- *CheckPartsResp) *grid.RemoteErr {
|
||||
if !s.checkID(p.DiskID) {
|
||||
return nil, grid.NewRemoteErr(errDiskNotFound)
|
||||
return grid.NewRemoteErr(errDiskNotFound)
|
||||
}
|
||||
volume := p.Volume
|
||||
filePath := p.FilePath
|
||||
|
||||
resp, err := s.getStorage().CheckParts(context.Background(), volume, filePath, p.FI)
|
||||
return resp, grid.NewRemoteErr(err)
|
||||
resp, err := s.getStorage().CheckParts(ctx, volume, filePath, p.FI)
|
||||
if err != nil {
|
||||
return grid.NewRemoteErr(err)
|
||||
}
|
||||
out <- resp
|
||||
return grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
func (s *storageRESTServer) WriteAllHandler(p *WriteAllHandlerParams) (grid.NoPayload, *grid.RemoteErr) {
|
||||
@ -1382,7 +1386,7 @@ func registerStorageRESTHandlers(router *mux.Router, endpointServerPools Endpoin
|
||||
logger.FatalIf(storageRenameDataRPC.Register(gm, server.RenameDataHandler, endpoint.Path), "unable to register handler")
|
||||
logger.FatalIf(storageRenameDataInlineRPC.Register(gm, server.RenameDataInlineHandler, endpoint.Path), "unable to register handler")
|
||||
logger.FatalIf(storageDeleteFileRPC.Register(gm, server.DeleteFileHandler, endpoint.Path), "unable to register handler")
|
||||
logger.FatalIf(storageCheckPartsRPC.Register(gm, server.CheckPartsHandler, endpoint.Path), "unable to register handler")
|
||||
logger.FatalIf(storageCheckPartsRPC.RegisterNoInput(gm, server.CheckPartsHandler, endpoint.Path), "unable to register handler")
|
||||
logger.FatalIf(storageReadVersionRPC.Register(gm, server.ReadVersionHandlerWS, endpoint.Path), "unable to register handler")
|
||||
logger.FatalIf(storageWriteMetadataRPC.Register(gm, server.WriteMetadataHandler, endpoint.Path), "unable to register handler")
|
||||
logger.FatalIf(storageUpdateMetadataRPC.Register(gm, server.UpdateMetadataHandler, endpoint.Path), "unable to register handler")
|
||||
|
@ -13,9 +13,9 @@ require (
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
@ -23,15 +23,15 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
2
go.mod
2
go.mod
@ -42,7 +42,7 @@ require (
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/minio/cli v1.24.2
|
||||
github.com/minio/console v1.7.4
|
||||
github.com/minio/console v1.7.5
|
||||
github.com/minio/csvparser v1.0.0
|
||||
github.com/minio/dnscache v0.1.1
|
||||
github.com/minio/dperf v0.6.0
|
||||
|
4
go.sum
4
go.sum
@ -432,8 +432,8 @@ github.com/minio/cli v1.24.2 h1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=
|
||||
github.com/minio/cli v1.24.2/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||
github.com/minio/colorjson v1.0.8 h1:AS6gEQ1dTRYHmC4xuoodPDRILHP/9Wz5wYUGDQfPLpg=
|
||||
github.com/minio/colorjson v1.0.8/go.mod h1:wrs39G/4kqNlGjwqHvPlAnXuc2tlPszo6JKdSBCLN8w=
|
||||
github.com/minio/console v1.7.4 h1:4ho6GBGJ6ncTZpHqc/LBjEzaL5e0eHQ8T7sH30sxM2Y=
|
||||
github.com/minio/console v1.7.4/go.mod h1:qbj8JxTq2XgbzunpcQEM3fJ7Sgf5gKXW3b6Zs7bbsf8=
|
||||
github.com/minio/console v1.7.5 h1:dnPP++SfV93b/uhUqggoeOEhF9E+bLSOQrkh1CiH8+U=
|
||||
github.com/minio/console v1.7.5/go.mod h1:gHeX9FFJlJjSOxfp/cjDCk8rkP7q8hNxARkzhM3wK9M=
|
||||
github.com/minio/csvparser v1.0.0 h1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=
|
||||
github.com/minio/csvparser v1.0.0/go.mod h1:lKXskSLzPgC5WQyzP7maKH7Sl1cqvANXo9YCto8zbtM=
|
||||
github.com/minio/dnscache v0.1.1 h1:AMYLqomzskpORiUA1ciN9k7bZT1oB3YZN4cEIi88W5o=
|
||||
|
@ -115,6 +115,7 @@ const (
|
||||
HandlerCheckParts2
|
||||
HandlerRenamePart
|
||||
HandlerClearUploadID
|
||||
HandlerCheckParts3
|
||||
|
||||
// Add more above here ^^^
|
||||
// If all handlers are used, the type of Handler can be changed.
|
||||
@ -196,6 +197,7 @@ var handlerPrefixes = [handlerLast]string{
|
||||
HandlerRenameDataInline: storagePrefix,
|
||||
HandlerRenameData2: storagePrefix,
|
||||
HandlerCheckParts2: storagePrefix,
|
||||
HandlerCheckParts3: storagePrefix,
|
||||
HandlerRenamePart: storagePrefix,
|
||||
HandlerClearUploadID: peerPrefix,
|
||||
}
|
||||
|
@ -85,14 +85,15 @@ func _() {
|
||||
_ = x[HandlerCheckParts2-74]
|
||||
_ = x[HandlerRenamePart-75]
|
||||
_ = x[HandlerClearUploadID-76]
|
||||
_ = x[handlerTest-77]
|
||||
_ = x[handlerTest2-78]
|
||||
_ = x[handlerLast-79]
|
||||
_ = x[HandlerCheckParts3-77]
|
||||
_ = x[handlerTest-78]
|
||||
_ = x[handlerTest2-79]
|
||||
_ = x[handlerLast-80]
|
||||
}
|
||||
|
||||
const _HandlerID_name = "handlerInvalidLockLockLockRLockLockUnlockLockRUnlockLockRefreshLockForceUnlockWalkDirStatVolDiskInfoNSScannerReadXLReadVersionDeleteFileDeleteVersionUpdateMetadataWriteMetadataCheckPartsRenameDataRenameFileReadAllServerVerifyTraceListenDeleteBucketMetadataLoadBucketMetadataReloadSiteReplicationConfigReloadPoolMetaStopRebalanceLoadRebalanceMetaLoadTransitionTierConfigDeletePolicyLoadPolicyLoadPolicyMappingDeleteServiceAccountLoadServiceAccountDeleteUserLoadUserLoadGroupHealBucketMakeBucketHeadBucketDeleteBucketGetMetricsGetResourceMetricsGetMemInfoGetProcInfoGetOSInfoGetPartitionsGetNetInfoGetCPUsServerInfoGetSysConfigGetSysServicesGetSysErrorsGetAllBucketStatsGetBucketStatsGetSRMetricsGetPeerMetricsGetMetacacheListingUpdateMetacacheListingGetPeerBucketMetricsStorageInfoConsoleLogListDirGetLocksBackgroundHealStatusGetLastDayTierStatsSignalServiceGetBandwidthWriteAllListBucketsRenameDataInlineRenameData2CheckParts2RenamePartClearUploadIDhandlerTesthandlerTest2handlerLast"
|
||||
const _HandlerID_name = "handlerInvalidLockLockLockRLockLockUnlockLockRUnlockLockRefreshLockForceUnlockWalkDirStatVolDiskInfoNSScannerReadXLReadVersionDeleteFileDeleteVersionUpdateMetadataWriteMetadataCheckPartsRenameDataRenameFileReadAllServerVerifyTraceListenDeleteBucketMetadataLoadBucketMetadataReloadSiteReplicationConfigReloadPoolMetaStopRebalanceLoadRebalanceMetaLoadTransitionTierConfigDeletePolicyLoadPolicyLoadPolicyMappingDeleteServiceAccountLoadServiceAccountDeleteUserLoadUserLoadGroupHealBucketMakeBucketHeadBucketDeleteBucketGetMetricsGetResourceMetricsGetMemInfoGetProcInfoGetOSInfoGetPartitionsGetNetInfoGetCPUsServerInfoGetSysConfigGetSysServicesGetSysErrorsGetAllBucketStatsGetBucketStatsGetSRMetricsGetPeerMetricsGetMetacacheListingUpdateMetacacheListingGetPeerBucketMetricsStorageInfoConsoleLogListDirGetLocksBackgroundHealStatusGetLastDayTierStatsSignalServiceGetBandwidthWriteAllListBucketsRenameDataInlineRenameData2CheckParts2RenamePartClearUploadIDCheckParts3handlerTesthandlerTest2handlerLast"
|
||||
|
||||
var _HandlerID_index = [...]uint16{0, 14, 22, 31, 41, 52, 63, 78, 85, 92, 100, 109, 115, 126, 136, 149, 163, 176, 186, 196, 206, 213, 225, 230, 236, 256, 274, 301, 315, 328, 345, 369, 381, 391, 408, 428, 446, 456, 464, 473, 483, 493, 503, 515, 525, 543, 553, 564, 573, 586, 596, 603, 613, 625, 639, 651, 668, 682, 694, 708, 727, 749, 769, 780, 790, 797, 805, 825, 844, 857, 869, 877, 888, 904, 915, 926, 936, 949, 960, 972, 983}
|
||||
var _HandlerID_index = [...]uint16{0, 14, 22, 31, 41, 52, 63, 78, 85, 92, 100, 109, 115, 126, 136, 149, 163, 176, 186, 196, 206, 213, 225, 230, 236, 256, 274, 301, 315, 328, 345, 369, 381, 391, 408, 428, 446, 456, 464, 473, 483, 493, 503, 515, 525, 543, 553, 564, 573, 586, 596, 603, 613, 625, 639, 651, 668, 682, 694, 708, 727, 749, 769, 780, 790, 797, 805, 825, 844, 857, 869, 877, 888, 904, 915, 926, 936, 949, 960, 971, 983, 994}
|
||||
|
||||
func (i HandlerID) String() string {
|
||||
if i >= HandlerID(len(_HandlerID_index)-1) {
|
||||
|
@ -257,6 +257,26 @@ func (r *Reader) Read(p []byte) (int, error) {
|
||||
r.contentHasher.Write(p[:n])
|
||||
}
|
||||
|
||||
// If we have reached our expected size,
|
||||
// do one more read to ensure we are at EOF
|
||||
// and that any trailers have been read.
|
||||
attempts := 0
|
||||
for err == nil && r.size >= 0 && r.bytesRead >= r.size {
|
||||
attempts++
|
||||
if r.bytesRead > r.size {
|
||||
return 0, SizeTooLarge{Want: r.size, Got: r.bytesRead}
|
||||
}
|
||||
var tmp [1]byte
|
||||
var n2 int
|
||||
n2, err = r.src.Read(tmp[:])
|
||||
if n2 > 0 {
|
||||
return 0, SizeTooLarge{Want: r.size, Got: r.bytesRead}
|
||||
}
|
||||
if attempts == 100 {
|
||||
return 0, io.ErrNoProgress
|
||||
}
|
||||
}
|
||||
|
||||
if err == io.EOF { // Verify content SHA256, if set.
|
||||
if r.expectedMin > 0 {
|
||||
if r.bytesRead < r.expectedMin {
|
||||
|
@ -177,6 +177,10 @@ const (
|
||||
AmzChecksumSHA256 = "x-amz-checksum-sha256"
|
||||
AmzChecksumMode = "x-amz-checksum-mode"
|
||||
|
||||
// Post Policy related
|
||||
AmzMetaUUID = "X-Amz-Meta-Uuid"
|
||||
AmzMetaName = "X-Amz-Meta-Name"
|
||||
|
||||
// Delete special flag to force delete a bucket or a prefix
|
||||
MinIOForceDelete = "x-minio-force-delete"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user