From 8d5b2be0b2f4ac7f0edcbe943d1df1708b430f8f Mon Sep 17 00:00:00 2001 From: niksis02 Date: Fri, 3 Apr 2026 00:08:13 +0400 Subject: [PATCH] fix: check PutObjectTagging/LegalHold/Retention permissions on PutObject,CopyObject and CreateMultipartUpload Fixes #1986 When a client includes tagging, legal hold, or retention headers in a PutObject, CopyObject or CreateMultipartUpload request, the corresponding bucket policy permissions must be verified in addition to s3:PutObject: `X-Amz-Tagging` - `s3:PutObjectTagging` `X-Amz-Object-Lock-Legal-Hold` - `s3:PutObjectLegalHold` `X-Amz-Object-Lock-Mode` - `s3:PutObjectRetention` Previously, only s3:PutObject was checked, allowing users to set tagging, legal hold, and retention without having the required permissions. Now each action permission is check, if user tries to add them. For CopyObject these permissions are checked on destination object. --- auth/access-control.go | 6 +- auth/bucket_policy.go | 12 +- s3api/controllers/bucket-delete.go | 10 +- s3api/controllers/bucket-get.go | 26 +- s3api/controllers/bucket-head.go | 2 +- s3api/controllers/bucket-post.go | 4 +- s3api/controllers/bucket-put.go | 14 +- s3api/controllers/object-delete.go | 6 +- s3api/controllers/object-get.go | 14 +- s3api/controllers/object-head.go | 2 +- s3api/controllers/object-post.go | 23 +- s3api/controllers/object-put.go | 44 ++- tests/integration/Access_Control.go | 555 ++++++++++++++++++++++++++++ tests/integration/group-tests.go | 18 + 14 files changed, 679 insertions(+), 57 deletions(-) diff --git a/auth/access-control.go b/auth/access-control.go index 53daae36..e4fc53c8 100644 --- a/auth/access-control.go +++ b/auth/access-control.go @@ -61,7 +61,7 @@ func VerifyObjectCopyAccess(ctx context.Context, be backend.Backend, copySource Acc: opts.Acc, Bucket: srcBucket, Object: srcObject, - Action: GetObjectAction, + Actions: []Action{GetObjectAction}, }); err != nil { return err } @@ -76,7 +76,7 @@ type AccessOptions struct { Acc Account Bucket string Object string - Action Action + Actions []Action Readonly bool IsPublicRequest bool DisableACL bool @@ -105,7 +105,7 @@ func VerifyAccess(ctx context.Context, be backend.Backend, opts AccessOptions) e return policyErr } } else { - return VerifyBucketPolicy(policy, opts.Acc.Access, opts.Bucket, opts.Object, opts.Action) + return VerifyBucketPolicy(policy, opts.Acc.Access, opts.Bucket, opts.Object, opts.Actions...) } if err := verifyACL(opts.Acl, opts.Acc.Access, opts.AclPermission, opts.DisableACL); err != nil { diff --git a/auth/bucket_policy.go b/auth/bucket_policy.go index 717c5e13..f30188b0 100644 --- a/auth/bucket_policy.go +++ b/auth/bucket_policy.go @@ -234,7 +234,11 @@ func ValidatePolicyDocument(policyBin []byte, bucket string, iam IAMService) err return nil } -func VerifyBucketPolicy(policy []byte, access, bucket, object string, action Action) error { +func VerifyBucketPolicy(policy []byte, access, bucket, object string, actions ...Action) error { + if len(actions) == 0 { + return s3err.GetAPIError(s3err.ErrAccessDenied) + } + var bucketPolicy BucketPolicy if err := json.Unmarshal(policy, &bucketPolicy); err != nil { return fmt.Errorf("failed to parse the bucket policy: %w", err) @@ -245,8 +249,10 @@ func VerifyBucketPolicy(policy []byte, access, bucket, object string, action Act resource += "/" + object } - if !bucketPolicy.isAllowed(access, action, resource) { - return s3err.GetAPIError(s3err.ErrAccessDenied) + for _, action := range actions { + if !bucketPolicy.isAllowed(access, action, resource) { + return s3err.GetAPIError(s3err.ErrAccessDenied) + } } return nil diff --git a/s3api/controllers/bucket-delete.go b/s3api/controllers/bucket-delete.go index 20f89cc1..564f293c 100644 --- a/s3api/controllers/bucket-delete.go +++ b/s3api/controllers/bucket-delete.go @@ -37,7 +37,7 @@ func (c S3ApiController) DeleteBucketTagging(ctx *fiber.Ctx) (*Response, error) IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketTaggingAction, + Actions: []auth.Action{auth.PutBucketTaggingAction}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) @@ -72,7 +72,7 @@ func (c S3ApiController) DeleteBucketOwnershipControls(ctx *fiber.Ctx) (*Respons IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketOwnershipControlsAction, + Actions: []auth.Action{auth.PutBucketOwnershipControlsAction}, DisableACL: c.disableACL, }) if err != nil { @@ -106,7 +106,7 @@ func (c S3ApiController) DeleteBucketPolicy(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.DeleteBucketPolicyAction, + Actions: []auth.Action{auth.DeleteBucketPolicyAction}, DisableACL: c.disableACL, }) if err != nil { @@ -141,7 +141,7 @@ func (c S3ApiController) DeleteBucketCors(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketCorsAction, + Actions: []auth.Action{auth.PutBucketCorsAction}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) @@ -177,7 +177,7 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.DeleteBucketAction, + Actions: []auth.Action{auth.DeleteBucketAction}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) diff --git a/s3api/controllers/bucket-get.go b/s3api/controllers/bucket-get.go index 4e648d2f..199333f8 100644 --- a/s3api/controllers/bucket-get.go +++ b/s3api/controllers/bucket-get.go @@ -39,7 +39,7 @@ func (c S3ApiController) GetBucketTagging(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.GetBucketTaggingAction, + Actions: []auth.Action{auth.GetBucketTaggingAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -92,7 +92,7 @@ func (c S3ApiController) GetBucketOwnershipControls(ctx *fiber.Ctx) (*Response, IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.GetBucketOwnershipControlsAction, + Actions: []auth.Action{auth.GetBucketOwnershipControlsAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -133,7 +133,7 @@ func (c S3ApiController) GetBucketVersioning(ctx *fiber.Ctx) (*Response, error) IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.GetBucketVersioningAction, + Actions: []auth.Action{auth.GetBucketVersioningAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -176,7 +176,7 @@ func (c S3ApiController) GetBucketCors(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.GetBucketCorsAction, + Actions: []auth.Action{auth.GetBucketCorsAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -220,7 +220,7 @@ func (c S3ApiController) GetBucketPolicy(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.GetBucketPolicyAction, + Actions: []auth.Action{auth.GetBucketPolicyAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -255,7 +255,7 @@ func (c S3ApiController) GetBucketPolicyStatus(ctx *fiber.Ctx) (*Response, error IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.GetBucketPolicyStatusAction, + Actions: []auth.Action{auth.GetBucketPolicyStatusAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -317,7 +317,7 @@ func (c S3ApiController) ListObjectVersions(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.ListBucketVersionsAction, + Actions: []auth.Action{auth.ListBucketVersionsAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -371,7 +371,7 @@ func (c S3ApiController) GetObjectLockConfiguration(ctx *fiber.Ctx) (*Response, IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.GetBucketObjectLockConfigurationAction, + Actions: []auth.Action{auth.GetBucketObjectLockConfigurationAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -417,7 +417,7 @@ func (c S3ApiController) GetBucketAcl(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.GetBucketAclAction, + Actions: []auth.Action{auth.GetBucketAclAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -469,7 +469,7 @@ func (c S3ApiController) ListMultipartUploads(ctx *fiber.Ctx) (*Response, error) IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.ListBucketMultipartUploadsAction, + Actions: []auth.Action{auth.ListBucketMultipartUploadsAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -531,7 +531,7 @@ func (c S3ApiController) ListObjectsV2(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.ListBucketAction, + Actions: []auth.Action{auth.ListBucketAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -604,7 +604,7 @@ func (c S3ApiController) ListObjects(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.ListBucketAction, + Actions: []auth.Action{auth.ListBucketAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -667,7 +667,7 @@ func (c S3ApiController) GetBucketLocation(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.GetBucketLocationAction, + Actions: []auth.Action{auth.GetBucketLocationAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) diff --git a/s3api/controllers/bucket-head.go b/s3api/controllers/bucket-head.go index 4861d507..b016cde3 100644 --- a/s3api/controllers/bucket-head.go +++ b/s3api/controllers/bucket-head.go @@ -40,7 +40,7 @@ func (c S3ApiController) HeadBucket(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.ListBucketAction, + Actions: []auth.Action{auth.ListBucketAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) diff --git a/s3api/controllers/bucket-post.go b/s3api/controllers/bucket-post.go index 89f0ba6b..4857fddd 100644 --- a/s3api/controllers/bucket-post.go +++ b/s3api/controllers/bucket-post.go @@ -48,7 +48,7 @@ func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.DeleteObjectAction, + Actions: []auth.Action{auth.DeleteObjectAction}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) @@ -130,7 +130,7 @@ func (c S3ApiController) POSTObject(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutObjectAction, + Actions: []auth.Action{auth.PutObjectAction}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) diff --git a/s3api/controllers/bucket-put.go b/s3api/controllers/bucket-put.go index 47521ba1..b6d8f762 100644 --- a/s3api/controllers/bucket-put.go +++ b/s3api/controllers/bucket-put.go @@ -45,7 +45,7 @@ func (c S3ApiController) PutBucketTagging(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketTaggingAction, + Actions: []auth.Action{auth.PutBucketTaggingAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -88,7 +88,7 @@ func (c S3ApiController) PutBucketOwnershipControls(ctx *fiber.Ctx) (*Response, IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketOwnershipControlsAction, + Actions: []auth.Action{auth.PutBucketOwnershipControlsAction}, DisableACL: c.disableACL, }); err != nil { return &Response{ @@ -143,7 +143,7 @@ func (c S3ApiController) PutBucketVersioning(ctx *fiber.Ctx) (*Response, error) IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketVersioningAction, + Actions: []auth.Action{auth.PutBucketVersioningAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -198,7 +198,7 @@ func (c S3ApiController) PutObjectLockConfiguration(ctx *fiber.Ctx) (*Response, IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketObjectLockConfigurationAction, + Actions: []auth.Action{auth.PutBucketObjectLockConfigurationAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }); err != nil { @@ -240,7 +240,7 @@ func (c S3ApiController) PutBucketCors(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketCorsAction, + Actions: []auth.Action{auth.PutBucketCorsAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -296,7 +296,7 @@ func (c S3ApiController) PutBucketPolicy(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketPolicyAction, + Actions: []auth.Action{auth.PutBucketPolicyAction}, DisableACL: c.disableACL, }) if err != nil { @@ -349,7 +349,7 @@ func (c S3ApiController) PutBucketAcl(ctx *fiber.Ctx) (*Response, error) { IsRoot: isRoot, Acc: acct, Bucket: bucket, - Action: auth.PutBucketAclAction, + Actions: []auth.Action{auth.PutBucketAclAction}, DisableACL: c.disableACL, }) if err != nil { diff --git a/s3api/controllers/object-delete.go b/s3api/controllers/object-delete.go index 43678d2d..cfad6b12 100644 --- a/s3api/controllers/object-delete.go +++ b/s3api/controllers/object-delete.go @@ -50,7 +50,7 @@ func (c S3ApiController) DeleteObjectTagging(ctx *fiber.Ctx) (*Response, error) Acc: acct, Bucket: bucket, Object: key, - Action: action, + Actions: []auth.Action{action}, IsPublicRequest: isBucketPublic, DisableACL: c.disableACL, }) @@ -94,7 +94,7 @@ func (c S3ApiController) AbortMultipartUpload(ctx *fiber.Ctx) (*Response, error) Acc: acct, Bucket: bucket, Object: key, - Action: auth.AbortMultipartUploadAction, + Actions: []auth.Action{auth.AbortMultipartUploadAction}, IsPublicRequest: isBucketPublic, DisableACL: c.disableACL, }) @@ -149,7 +149,7 @@ func (c S3ApiController) DeleteObject(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: action, + Actions: []auth.Action{action}, IsPublicRequest: isBucketPublic, DisableACL: c.disableACL, }) diff --git a/s3api/controllers/object-get.go b/s3api/controllers/object-get.go index bae2e96e..ca31bb91 100644 --- a/s3api/controllers/object-get.go +++ b/s3api/controllers/object-get.go @@ -53,7 +53,7 @@ func (c S3ApiController) GetObjectTagging(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: action, + Actions: []auth.Action{action}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -111,7 +111,7 @@ func (c S3ApiController) GetObjectRetention(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.GetObjectRetentionAction, + Actions: []auth.Action{auth.GetObjectRetentionAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -159,7 +159,7 @@ func (c S3ApiController) GetObjectLegalHold(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.GetObjectLegalHoldAction, + Actions: []auth.Action{auth.GetObjectLegalHoldAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -197,7 +197,7 @@ func (c S3ApiController) GetObjectAcl(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.GetObjectAclAction, + Actions: []auth.Action{auth.GetObjectAclAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -240,7 +240,7 @@ func (c S3ApiController) ListParts(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.ListMultipartUploadPartsAction, + Actions: []auth.Action{auth.ListMultipartUploadPartsAction}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -312,7 +312,7 @@ func (c S3ApiController) GetObjectAttributes(ctx *fiber.Ctx) (*Response, error) Acc: acct, Bucket: bucket, Object: key, - Action: action, + Actions: []auth.Action{action}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) @@ -437,7 +437,7 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: action, + Actions: []auth.Action{action}, IsPublicRequest: isPublicBucketRequest, DisableACL: c.disableACL, }) diff --git a/s3api/controllers/object-head.go b/s3api/controllers/object-head.go index 755cc1cf..ef89cdbc 100644 --- a/s3api/controllers/object-head.go +++ b/s3api/controllers/object-head.go @@ -85,7 +85,7 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: action, + Actions: []auth.Action{action}, IsPublicRequest: isPublicBucket, DisableACL: c.disableACL, }) diff --git a/s3api/controllers/object-post.go b/s3api/controllers/object-post.go index 1d5f456f..098ff187 100644 --- a/s3api/controllers/object-post.go +++ b/s3api/controllers/object-post.go @@ -48,7 +48,7 @@ func (c S3ApiController) RestoreObject(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.RestoreObjectAction, + Actions: []auth.Action{auth.RestoreObjectAction}, IsPublicRequest: isBucketPublic, DisableACL: c.disableACL, }) @@ -100,7 +100,7 @@ func (c S3ApiController) SelectObjectContent(ctx *fiber.Ctx) (*Response, error) Acc: acct, Bucket: bucket, Object: key, - Action: auth.GetObjectAction, + Actions: []auth.Action{auth.GetObjectAction}, IsPublicRequest: isBucketPublic, DisableACL: c.disableACL, }) @@ -154,11 +154,26 @@ func (c S3ApiController) CreateMultipartUpload(ctx *fiber.Ctx) (*Response, error contentEncoding := ctx.Get("Content-Encoding") tagging := ctx.Get("X-Amz-Tagging") expires := ctx.Get("Expires") + legalHoldHdr := ctx.Get("X-Amz-Object-Lock-Legal-Hold") + lockModeHdr := ctx.Get("X-Amz-Object-Lock-Mode") + objLockDate := ctx.Get("X-Amz-Object-Lock-Retain-Until-Date") + // context locals acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + actions := []auth.Action{auth.PutObjectAction} + if tagging != "" { + actions = append(actions, auth.PutObjectTaggingAction) + } + if legalHoldHdr != "" { + actions = append(actions, auth.PutObjectLegalHoldAction) + } + if lockModeHdr != "" || objLockDate != "" { + actions = append(actions, auth.PutObjectRetentionAction) + } + err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ Readonly: c.readonly, @@ -168,7 +183,7 @@ func (c S3ApiController) CreateMultipartUpload(ctx *fiber.Ctx) (*Response, error Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectAction, + Actions: actions, DisableACL: c.disableACL, }) if err != nil { @@ -261,7 +276,7 @@ func (c S3ApiController) CompleteMultipartUpload(ctx *fiber.Ctx) (*Response, err Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectAction, + Actions: []auth.Action{auth.PutObjectAction}, IsPublicRequest: isBucketPublic, DisableACL: c.disableACL, }) diff --git a/s3api/controllers/object-put.go b/s3api/controllers/object-put.go index 0685c220..1228559f 100644 --- a/s3api/controllers/object-put.go +++ b/s3api/controllers/object-put.go @@ -55,7 +55,7 @@ func (c S3ApiController) PutObjectTagging(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: action, + Actions: []auth.Action{action}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) @@ -106,7 +106,7 @@ func (c S3ApiController) PutObjectRetention(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectRetentionAction, + Actions: []auth.Action{auth.PutObjectRetentionAction}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) @@ -173,7 +173,7 @@ func (c S3ApiController) PutObjectLegalHold(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectLegalHoldAction, + Actions: []auth.Action{auth.PutObjectLegalHoldAction}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) @@ -243,7 +243,7 @@ func (c S3ApiController) UploadPart(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectAction, + Actions: []auth.Action{auth.PutObjectAction}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) @@ -359,7 +359,7 @@ func (c S3ApiController) UploadPartCopy(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectAction, + Actions: []auth.Action{auth.PutObjectAction}, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) @@ -443,7 +443,7 @@ func (c S3ApiController) PutObjectAcl(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectAclAction, + Actions: []auth.Action{auth.PutObjectAclAction}, }) if err != nil { return &Response{ @@ -486,6 +486,9 @@ func (c S3ApiController) CopyObject(ctx *fiber.Ctx) (*Response, error) { expires := ctx.Get("Expires") tagging := ctx.Get("x-amz-tagging") storageClass := ctx.Get("X-Amz-Storage-Class") + legalHoldHdr := ctx.Get("X-Amz-Object-Lock-Legal-Hold") + lockModeHdr := ctx.Get("X-Amz-Object-Lock-Mode") + objLockDate := ctx.Get("X-Amz-Object-Lock-Retain-Until-Date") // context locals acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) @@ -500,6 +503,17 @@ func (c S3ApiController) CopyObject(ctx *fiber.Ctx) (*Response, error) { }, err } + actions := []auth.Action{auth.PutObjectAction} + if tagging != "" { + actions = append(actions, auth.PutObjectTaggingAction) + } + if legalHoldHdr != "" { + actions = append(actions, auth.PutObjectLegalHoldAction) + } + if lockModeHdr != "" || objLockDate != "" { + actions = append(actions, auth.PutObjectRetentionAction) + } + err = auth.VerifyObjectCopyAccess(ctx.Context(), c.be, copySource, auth.AccessOptions{ Acl: parsedAcl, @@ -508,7 +522,7 @@ func (c S3ApiController) CopyObject(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectAction, + Actions: actions, }) if err != nil { return &Response{ @@ -642,6 +656,9 @@ func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) { cacheControl := ctx.Get("Cache-Control") expires := ctx.Get("Expires") tagging := ctx.Get("x-amz-tagging") + legalHoldHdr := ctx.Get("X-Amz-Object-Lock-Legal-Hold") + lockModeHdr := ctx.Get("X-Amz-Object-Lock-Mode") + objLockDate := ctx.Get("X-Amz-Object-Lock-Retain-Until-Date") // context locals acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) @@ -660,6 +677,17 @@ func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) { contentLengthStr = decodedLength } + actions := []auth.Action{auth.PutObjectAction} + if tagging != "" { + actions = append(actions, auth.PutObjectTaggingAction) + } + if legalHoldHdr != "" { + actions = append(actions, auth.PutObjectLegalHoldAction) + } + if lockModeHdr != "" || objLockDate != "" { + actions = append(actions, auth.PutObjectRetentionAction) + } + err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ Readonly: c.readonly, @@ -669,7 +697,7 @@ func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectAction, + Actions: actions, IsPublicRequest: IsBucketPublic, DisableACL: c.disableACL, }) diff --git a/tests/integration/Access_Control.go b/tests/integration/Access_Control.go index 973e91b9..f5f1962c 100644 --- a/tests/integration/Access_Control.go +++ b/tests/integration/Access_Control.go @@ -17,6 +17,7 @@ package integration import ( "context" "fmt" + "time" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" @@ -453,3 +454,557 @@ func AccessControl_copy_object_with_starting_slash_for_user(s *S3Conf) error { return nil }) } + +func AccessControl_PutObject_with_tagging_policy(s *S3Conf) error { + testName := "AccessControl_PutObject_with_tagging_policy" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + obj := "my-obj" + testuser := getUser("user") + if err := createUsers(s, []user{testuser}); err != nil { + return err + } + + objectResource := fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket) + + // Error path: user has s3:PutObject but not s3:PutObjectTagging + policy := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:PutObject"`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + userClient := s.getUserClient(testuser) + tagging := "key=value" + _, err := putObjectWithData(0, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj, + Tagging: &tagging, + }, userClient) + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // Happy path: user has s3:PutObject and s3:PutObjectTagging + policy = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `["s3:PutObject","s3:PutObjectTagging"]`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + _, err = putObjectWithData(0, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj, + Tagging: &tagging, + }, userClient) + return err + }) +} + +func AccessControl_PutObject_with_legal_hold_policy(s *S3Conf) error { + testName := "AccessControl_PutObject_with_legal_hold_policy" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + obj := "my-obj" + testuser := getUser("user") + if err := createUsers(s, []user{testuser}); err != nil { + return err + } + + objectResource := fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket) + + // Error path: user has s3:PutObject but not s3:PutObjectLegalHold + policy := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:PutObject"`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + userClient := s.getUserClient(testuser) + _, err := putObjectWithData(0, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj, + ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, + }, userClient) + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // Happy path: user has s3:PutObject and s3:PutObjectLegalHold + policy = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `["s3:PutObject","s3:PutObjectLegalHold"]`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + _, err = putObjectWithData(0, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj, + ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, + }, userClient) + if err != nil { + return err + } + + return cleanupLockedObjects(s3client, bucket, []objToDelete{{key: obj, removeLegalHold: true}}) + }, withLock()) +} + +func AccessControl_PutObject_with_retention_policy(s *S3Conf) error { + testName := "AccessControl_PutObject_with_retention_policy" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + obj := "my-obj" + testuser := getUser("user") + if err := createUsers(s, []user{testuser}); err != nil { + return err + } + + objectResource := fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket) + date := time.Now().Add(time.Hour) + + // Error path: user has s3:PutObject but not s3:PutObjectRetention + policy := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:PutObject"`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + userClient := s.getUserClient(testuser) + _, err := putObjectWithData(0, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj, + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: &date, + }, userClient) + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // Happy path: user has s3:PutObject and s3:PutObjectRetention + policy = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `["s3:PutObject","s3:PutObjectRetention"]`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + _, err = putObjectWithData(0, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj, + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: &date, + }, userClient) + if err != nil { + return err + } + + return cleanupLockedObjects(s3client, bucket, []objToDelete{{key: obj}}) + }, withLock()) +} + +func AccessControl_CreateMultipartUpload_with_tagging_policy(s *S3Conf) error { + testName := "AccessControl_CreateMultipartUpload_with_tagging_policy" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + obj := "my-obj" + testuser := getUser("user") + if err := createUsers(s, []user{testuser}); err != nil { + return err + } + + objectResource := fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket) + + // Error path: user has s3:PutObject but not s3:PutObjectTagging + policy := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:PutObject"`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + userClient := s.getUserClient(testuser) + tagging := "key=value" + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := userClient.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ + Bucket: &bucket, + Key: &obj, + Tagging: &tagging, + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // Happy path: user has s3:PutObject and s3:PutObjectTagging + policy = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `["s3:PutObject","s3:PutObjectTagging"]`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ + Bucket: &bucket, + Key: &obj, + Tagging: &tagging, + }) + cancel() + return err + }) +} + +func AccessControl_CreateMultipartUpload_with_legal_hold_policy(s *S3Conf) error { + testName := "AccessControl_CreateMultipartUpload_with_legal_hold_policy" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + obj := "my-obj" + testuser := getUser("user") + if err := createUsers(s, []user{testuser}); err != nil { + return err + } + + objectResource := fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket) + + // Error path: user has s3:PutObject but not s3:PutObjectLegalHold + policy := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:PutObject"`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + userClient := s.getUserClient(testuser) + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := userClient.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ + Bucket: &bucket, + Key: &obj, + ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // Happy path: user has s3:PutObject and s3:PutObjectLegalHold + policy = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `["s3:PutObject","s3:PutObjectLegalHold"]`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ + Bucket: &bucket, + Key: &obj, + ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, + }) + cancel() + return err + }, withLock()) +} + +func AccessControl_CreateMultipartUpload_with_retention_policy(s *S3Conf) error { + testName := "AccessControl_CreateMultipartUpload_with_retention_policy" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + obj := "my-obj" + testuser := getUser("user") + if err := createUsers(s, []user{testuser}); err != nil { + return err + } + + objectResource := fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket) + date := time.Now().Add(time.Hour) + + // Error path: user has s3:PutObject but not s3:PutObjectRetention + policy := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:PutObject"`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + userClient := s.getUserClient(testuser) + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err := userClient.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ + Bucket: &bucket, + Key: &obj, + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: &date, + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // Happy path: user has s3:PutObject and s3:PutObjectRetention + policy = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `["s3:PutObject","s3:PutObjectRetention"]`, objectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ + Bucket: &bucket, + Key: &obj, + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: &date, + }) + cancel() + return err + }, withLock()) +} + +func AccessControl_CopyObject_with_tagging_policy(s *S3Conf) error { + testName := "AccessControl_CopyObject_with_tagging_policy" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + testuser := getUser("user") + if err := createUsers(s, []user{testuser}); err != nil { + return err + } + + srcObj, dstObj := "source-object", "dst-object" + _, err := putObjectWithData(0, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &srcObj, + }, s3client) + if err != nil { + return err + } + + dstObjectResource := fmt.Sprintf(`"arn:aws:s3:::%s/%s"`, bucket, dstObj) + srcObjectResource := fmt.Sprintf(`"arn:aws:s3:::%s/%s"`, bucket, srcObj) + + // Error path: user has s3:PutObject, s3:GetObject but not s3:PutObjectTagging + policy := fmt.Sprintf(`{ + "Statement": [ + { + "Effect": "Allow", + "Principal": "%s", + "Action": "s3:GetObject", + "Resource": %s + }, + { + "Effect": "Allow", + "Principal": "%s", + "Action": "s3:PutObject", + "Resource": %s + } + ] + }`, testuser.access, srcObjectResource, testuser.access, dstObjectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + userClient := s.getUserClient(testuser) + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: &dstObj, + CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)), + Tagging: getPtr("key=value"), + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // Happy path: user has s3:GetObject, s3:PutObject and s3:PutObjectTagging + policy = fmt.Sprintf(`{ + "Statement": [ + { + "Effect": "Allow", + "Principal": "%s", + "Action": "s3:GetObject", + "Resource": %s + }, + { + "Effect": "Allow", + "Principal": "%s", + "Action": ["s3:PutObject","s3:PutObjectTagging"], + "Resource": %s + } + ] + }`, testuser.access, srcObjectResource, testuser.access, dstObjectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: &dstObj, + CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)), + Tagging: getPtr("key=value"), + }) + cancel() + return err + }) +} + +func AccessControl_CopyObject_with_legal_hold_policy(s *S3Conf) error { + testName := "AccessControl_CopyObject_with_legal_hold_policy" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + testuser := getUser("user") + if err := createUsers(s, []user{testuser}); err != nil { + return err + } + + srcObj, dstObj := "source-object", "dst-object" + _, err := putObjectWithData(0, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &srcObj, + }, s3client) + if err != nil { + return err + } + + dstObjectResource := fmt.Sprintf(`"arn:aws:s3:::%s/%s"`, bucket, dstObj) + srcObjectResource := fmt.Sprintf(`"arn:aws:s3:::%s/%s"`, bucket, srcObj) + + // Error path: user has s3:PutObject, s3:GetObject but not s3:PutObjectLegalHold + policy := fmt.Sprintf(`{ + "Statement": [ + { + "Effect": "Allow", + "Principal": "%s", + "Action": "s3:GetObject", + "Resource": %s + }, + { + "Effect": "Allow", + "Principal": "%s", + "Action": "s3:PutObject", + "Resource": %s + } + ] + }`, testuser.access, srcObjectResource, testuser.access, dstObjectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + userClient := s.getUserClient(testuser) + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: &dstObj, + CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)), + ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // Happy path: user has s3:GetObject, s3:PutObject and s3:PutObjectLegalHold + policy = fmt.Sprintf(`{ + "Statement": [ + { + "Effect": "Allow", + "Principal": "%s", + "Action": "s3:GetObject", + "Resource": %s + }, + { + "Effect": "Allow", + "Principal": "%s", + "Action": ["s3:PutObject","s3:PutObjectLegalHold"], + "Resource": %s + } + ] + }`, testuser.access, srcObjectResource, testuser.access, dstObjectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: &dstObj, + CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)), + ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, + }) + cancel() + if err != nil { + return err + } + + return cleanupLockedObjects(s3client, bucket, []objToDelete{{key: dstObj, removeLegalHold: true}}) + }, withLock()) +} + +func AccessControl_CopyObject_with_retention_policy(s *S3Conf) error { + testName := "AccessControl_CopyObject_with_retention_policy" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + testuser := getUser("user") + if err := createUsers(s, []user{testuser}); err != nil { + return err + } + + srcObj, dstObj := "source-object", "dst-object" + _, err := putObjectWithData(0, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &srcObj, + }, s3client) + if err != nil { + return err + } + + dstObjectResource := fmt.Sprintf(`"arn:aws:s3:::%s/%s"`, bucket, dstObj) + srcObjectResource := fmt.Sprintf(`"arn:aws:s3:::%s/%s"`, bucket, srcObj) + + // Error path: user has s3:PutObject, s3:GetObject but not s3:PutObjectRetention + policy := fmt.Sprintf(`{ + "Statement": [ + { + "Effect": "Allow", + "Principal": "%s", + "Action": "s3:GetObject", + "Resource": %s + }, + { + "Effect": "Allow", + "Principal": "%s", + "Action": "s3:PutObject", + "Resource": %s + } + ] + }`, testuser.access, srcObjectResource, testuser.access, dstObjectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + userClient := s.getUserClient(testuser) + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: &dstObj, + CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)), + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: getPtr(time.Now().AddDate(1, 0, 0)), + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // Happy path: user has s3:GetObject, s3:PutObject and s3:PutObjectRetention + policy = fmt.Sprintf(`{ + "Statement": [ + { + "Effect": "Allow", + "Principal": "%s", + "Action": "s3:GetObject", + "Resource": %s + }, + { + "Effect": "Allow", + "Principal": "%s", + "Action": ["s3:PutObject","s3:PutObjectRetention"], + "Resource": %s + } + ] + }`, testuser.access, srcObjectResource, testuser.access, dstObjectResource) + if err := putBucketPolicy(s3client, bucket, policy); err != nil { + return err + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: &dstObj, + CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)), + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: getPtr(time.Now().AddDate(1, 0, 0)), + }) + cancel() + if err != nil { + return err + } + + return cleanupLockedObjects(s3client, bucket, []objToDelete{{key: dstObj}}) + }, withLock()) +} diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index beb8399e..0b324677 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -1021,6 +1021,15 @@ func TestAccessControl(ts *TestState) { ts.Run(AccessControl_root_PutBucketAcl) ts.Run(AccessControl_user_PutBucketAcl_with_policy_access) ts.Run(AccessControl_copy_object_with_starting_slash_for_user) + ts.Run(AccessControl_PutObject_with_tagging_policy) + ts.Run(AccessControl_PutObject_with_legal_hold_policy) + ts.Run(AccessControl_PutObject_with_retention_policy) + ts.Run(AccessControl_CreateMultipartUpload_with_tagging_policy) + ts.Run(AccessControl_CreateMultipartUpload_with_legal_hold_policy) + ts.Run(AccessControl_CreateMultipartUpload_with_retention_policy) + ts.Run(AccessControl_CopyObject_with_tagging_policy) + ts.Run(AccessControl_CopyObject_with_legal_hold_policy) + ts.Run(AccessControl_CopyObject_with_retention_policy) } func TestPublicBuckets(ts *TestState) { @@ -1861,6 +1870,15 @@ func GetIntTests() IntTests { "AccessControl_root_PutBucketAcl": AccessControl_root_PutBucketAcl, "AccessControl_user_PutBucketAcl_with_policy_access": AccessControl_user_PutBucketAcl_with_policy_access, "AccessControl_copy_object_with_starting_slash_for_user": AccessControl_copy_object_with_starting_slash_for_user, + "AccessControl_PutObject_with_tagging_policy": AccessControl_PutObject_with_tagging_policy, + "AccessControl_PutObject_with_legal_hold_policy": AccessControl_PutObject_with_legal_hold_policy, + "AccessControl_PutObject_with_retention_policy": AccessControl_PutObject_with_retention_policy, + "AccessControl_CreateMultipartUpload_with_tagging_policy": AccessControl_CreateMultipartUpload_with_tagging_policy, + "AccessControl_CreateMultipartUpload_with_legal_hold_policy": AccessControl_CreateMultipartUpload_with_legal_hold_policy, + "AccessControl_CreateMultipartUpload_with_retention_policy": AccessControl_CreateMultipartUpload_with_retention_policy, + "AccessControl_CopyObject_with_tagging_policy": AccessControl_CopyObject_with_tagging_policy, + "AccessControl_CopyObject_with_legal_hold_policy": AccessControl_CopyObject_with_legal_hold_policy, + "AccessControl_CopyObject_with_retention_policy": AccessControl_CopyObject_with_retention_policy, "PublicBucket_default_private_bucket": PublicBucket_default_private_bucket, "PublicBucket_public_bucket_policy": PublicBucket_public_bucket_policy, "PublicBucket_public_object_policy": PublicBucket_public_object_policy,