diff --git a/auth/access-control.go b/auth/access-control.go index 53daae36f..e4fc53c80 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 60ee8fbbc..7badf0840 100644 --- a/auth/bucket_policy.go +++ b/auth/bucket_policy.go @@ -234,7 +234,7 @@ 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 { var bucketPolicy BucketPolicy if err := json.Unmarshal(policy, &bucketPolicy); err != nil { return fmt.Errorf("failed to parse the bucket policy: %w", err) @@ -245,8 +245,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 20f89cc17..564f293cf 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 4e648d2f5..199333f8d 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 4861d5078..b016cde36 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 89f0ba6b7..4857fddd7 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 47521ba15..b6d8f762d 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 a374e5ab3..0227adaff 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, }) @@ -103,7 +103,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, }) @@ -158,7 +158,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 4d2121109..e7504b694 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, }) @@ -120,7 +120,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, }) @@ -177,7 +177,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, }) @@ -224,7 +224,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, }) @@ -267,7 +267,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, }) @@ -339,7 +339,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, }) @@ -473,7 +473,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 62baf3fa1..8d5926937 100644 --- a/s3api/controllers/object-head.go +++ b/s3api/controllers/object-head.go @@ -84,7 +84,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 1d5f456f1..3e3eb7977 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,25 @@ 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") + // 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 != "" { + actions = append(actions, auth.PutObjectRetentionAction) + } + err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ Readonly: c.readonly, @@ -168,7 +182,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 +275,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 e489ec001..ec8822094 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, }) @@ -115,7 +115,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, }) @@ -191,7 +191,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, }) @@ -270,7 +270,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, }) @@ -385,7 +385,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, }) @@ -468,7 +468,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{ @@ -532,7 +532,7 @@ func (c S3ApiController) CopyObject(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.PutObjectAction, + Actions: []auth.Action{auth.PutObjectAction}, }) if err != nil { return &Response{ @@ -665,6 +665,8 @@ 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") // context locals acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) @@ -683,6 +685,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 != "" { + actions = append(actions, auth.PutObjectRetentionAction) + } + err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ Readonly: c.readonly, @@ -692,7 +705,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 973e91b95..2f931fd0d 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,289 @@ 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() + if err != nil { + return err + } + return err + }, withLock()) +} diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index 588643fd3..020a0789f 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -998,6 +998,12 @@ 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) } func TestPublicBuckets(ts *TestState) { @@ -1810,6 +1816,12 @@ 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, "PublicBucket_default_private_bucket": PublicBucket_default_private_bucket, "PublicBucket_public_bucket_policy": PublicBucket_public_bucket_policy, "PublicBucket_public_object_policy": PublicBucket_public_object_policy,