Skip to content

Commit 43a3778

Browse files
fix: support object-remaining-retention-days policy condition (minio#9259)
This PR also tries to simplify the approach taken in object-locking implementation by preferential treatment given towards full validation. This in-turn has fixed couple of bugs related to how policy should have been honored when ByPassGovernance is provided. Simplifies code a bit, but also duplicates code intentionally for clarity due to complex nature of object locking implementation.
1 parent b9b1bfe commit 43a3778

File tree

13 files changed

+674
-306
lines changed

13 files changed

+674
-306
lines changed

buildscripts/gateway-tests.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ function main()
4646
gw_pid="$(start_minio_gateway_s3)"
4747

4848
SERVER_ENDPOINT=127.0.0.1:24240 ENABLE_HTTPS=0 ACCESS_KEY=minio \
49-
SECRET_KEY=minio123 MINT_MODE="full" /mint/entrypoint.sh
49+
SECRET_KEY=minio123 MINT_MODE="full" /mint/entrypoint.sh \
50+
aws-sdk-go aws-sdk-java aws-sdk-php aws-sdk-ruby awscli \
51+
healthcheck mc minio-dotnet minio-java minio-js \
52+
minio-py s3cmd s3select security
5053
rv=$?
5154

5255
kill "$sr_pid"

cmd/auth-handler.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ import (
2626
"io"
2727
"io/ioutil"
2828
"net/http"
29+
"strconv"
2930
"strings"
31+
"time"
3032

3133
xhttp "github.com/minio/minio/cmd/http"
3234
xjwt "github.com/minio/minio/cmd/jwt"
3335
"github.com/minio/minio/cmd/logger"
3436
"github.com/minio/minio/pkg/auth"
37+
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
3538
"github.com/minio/minio/pkg/bucket/policy"
3639
"github.com/minio/minio/pkg/hash"
3740
iampolicy "github.com/minio/minio/pkg/iam/policy"
@@ -464,6 +467,106 @@ func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
464467
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL, guessIsBrowserReq(r))
465468
}
466469

470+
func validateSignature(atype authType, r *http.Request) (auth.Credentials, bool, map[string]interface{}, APIErrorCode) {
471+
var cred auth.Credentials
472+
var owner bool
473+
var s3Err APIErrorCode
474+
switch atype {
475+
case authTypeUnknown, authTypeStreamingSigned:
476+
return cred, owner, nil, ErrSignatureVersionNotSupported
477+
case authTypeSignedV2, authTypePresignedV2:
478+
if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone {
479+
return cred, owner, nil, s3Err
480+
}
481+
cred, owner, s3Err = getReqAccessKeyV2(r)
482+
case authTypePresigned, authTypeSigned:
483+
region := globalServerRegion
484+
if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone {
485+
return cred, owner, nil, s3Err
486+
}
487+
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
488+
}
489+
if s3Err != ErrNone {
490+
return cred, owner, nil, s3Err
491+
}
492+
493+
claims, s3Err := checkClaimsFromToken(r, cred)
494+
if s3Err != ErrNone {
495+
return cred, owner, nil, s3Err
496+
}
497+
498+
return cred, owner, claims, ErrNone
499+
}
500+
501+
func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate time.Time, retMode objectlock.RetMode, byPassSet bool, r *http.Request, cred auth.Credentials, owner bool, claims map[string]interface{}) (s3Err APIErrorCode) {
502+
var retSet bool
503+
if cred.AccessKey == "" {
504+
conditions := getConditionValues(r, "", "", nil)
505+
conditions["object-lock-mode"] = []string{string(retMode)}
506+
conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)}
507+
if retDays > 0 {
508+
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
509+
}
510+
if retMode == objectlock.RetGovernance && byPassSet {
511+
byPassSet = globalPolicySys.IsAllowed(policy.Args{
512+
AccountName: cred.AccessKey,
513+
Action: policy.Action(policy.BypassGovernanceRetentionAction),
514+
BucketName: bucketName,
515+
ConditionValues: conditions,
516+
IsOwner: false,
517+
ObjectName: objectName,
518+
})
519+
}
520+
if globalPolicySys.IsAllowed(policy.Args{
521+
AccountName: cred.AccessKey,
522+
Action: policy.Action(policy.PutObjectRetentionAction),
523+
BucketName: bucketName,
524+
ConditionValues: conditions,
525+
IsOwner: false,
526+
ObjectName: objectName,
527+
}) {
528+
retSet = true
529+
}
530+
if byPassSet || retSet {
531+
return ErrNone
532+
}
533+
return ErrAccessDenied
534+
}
535+
536+
conditions := getConditionValues(r, "", cred.AccessKey, claims)
537+
conditions["object-lock-mode"] = []string{string(retMode)}
538+
conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)}
539+
if retDays > 0 {
540+
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
541+
}
542+
if retMode == objectlock.RetGovernance && byPassSet {
543+
byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{
544+
AccountName: cred.AccessKey,
545+
Action: policy.BypassGovernanceRetentionAction,
546+
BucketName: bucketName,
547+
ObjectName: objectName,
548+
ConditionValues: conditions,
549+
IsOwner: owner,
550+
Claims: claims,
551+
})
552+
}
553+
if globalIAMSys.IsAllowed(iampolicy.Args{
554+
AccountName: cred.AccessKey,
555+
Action: policy.PutObjectRetentionAction,
556+
BucketName: bucketName,
557+
ConditionValues: conditions,
558+
ObjectName: objectName,
559+
IsOwner: owner,
560+
Claims: claims,
561+
}) {
562+
retSet = true
563+
}
564+
if byPassSet || retSet {
565+
return ErrNone
566+
}
567+
return ErrAccessDenied
568+
}
569+
467570
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
468571
// call verifies bucket policies and IAM policies, supports multi user
469572
// checks etc.

cmd/bucket-handlers.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -399,11 +399,14 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
399399
}
400400
continue
401401
}
402-
govBypassPerms := checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName)
403-
if _, err := enforceRetentionBypassForDelete(ctx, r, bucket, object.ObjectName, getObjectInfoFn, govBypassPerms); err != ErrNone {
404-
dErrs[index] = err
405-
continue
402+
403+
if _, ok := globalBucketObjectLockConfig.Get(bucket); ok {
404+
if err := enforceRetentionBypassForDelete(ctx, r, bucket, object.ObjectName, getObjectInfoFn); err != ErrNone {
405+
dErrs[index] = err
406+
continue
407+
}
406408
}
409+
407410
// Avoid duplicate objects, we use map to filter them out.
408411
if _, ok := objectsToDelete[object.ObjectName]; !ok {
409412
objectsToDelete[object.ObjectName] = index

cmd/disk-cache.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
239239
// skip cache for objects with locks
240240
objRetention := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
241241
legalHold := objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
242-
if objRetention.Mode != objectlock.Invalid || legalHold.Status != "" {
242+
if objRetention.Mode.Valid() || legalHold.Status.Valid() {
243243
c.cacheStats.incMiss()
244244
return c.GetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
245245
}
@@ -614,7 +614,7 @@ func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *
614614
// skip cache for objects with locks
615615
objRetention := objectlock.GetObjectRetentionMeta(opts.UserDefined)
616616
legalHold := objectlock.GetObjectLegalHoldMeta(opts.UserDefined)
617-
if objRetention.Mode != objectlock.Invalid || legalHold.Status != "" {
617+
if objRetention.Mode.Valid() || legalHold.Status.Valid() {
618618
dcache.Delete(ctx, bucket, object)
619619
return putObjectFn(ctx, bucket, object, r, opts)
620620
}

0 commit comments

Comments
 (0)