Files
versitygw/tests/integration/Access_Control.go
niksis02 8d5b2be0b2 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.
2026-04-28 01:05:34 +04:00

1011 lines
30 KiB
Go

// Copyright 2023 Versity Software
// This file is licensed under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
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"
"github.com/versity/versitygw/s3err"
)
// Access control tests (with bucket ACLs and Policies)
func AccessControl_default_ACL_user_access_denied(s *S3Conf) error {
testName := "AccessControl_default_ACL_user_access_denied"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser := getUser("user")
err := createUsers(s, []user{testuser})
if err != nil {
return err
}
userClient := s.getUserClient(testuser)
_, err = putObjects(userClient, []string{"my-obj"}, bucket)
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
return nil
})
}
func AccessControl_default_ACL_userplus_access_denied(s *S3Conf) error {
testName := "AccessControl_default_ACL_userplus_access_denied"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser := getUser("userplus")
err := createUsers(s, []user{testuser})
if err != nil {
return err
}
client := s.getUserClient(testuser)
_, err = putObjects(client, []string{"my-obj"}, bucket)
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
return nil
})
}
func AccessControl_default_ACL_admin_successful_access(s *S3Conf) error {
testName := "AccessControl_default_ACL_admin_successful_access"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser := getUser("admin")
err := createUsers(s, []user{testuser})
if err != nil {
return err
}
adminClient := s.getUserClient(testuser)
_, err = putObjects(adminClient, []string{"my-obj"}, bucket)
if err != nil {
return err
}
return nil
})
}
func AccessControl_bucket_resource_single_action(s *S3Conf) error {
testName := "AccessControl_bucket_resource_single_action"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser1, testuser2 := getUser("user"), getUser("user")
err := createUsers(s, []user{testuser1, testuser2})
if err != nil {
return err
}
doc := genPolicyDoc("Allow", fmt.Sprintf(`["%s"]`, testuser1.access), `"s3:PutBucketTagging"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket))
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
Bucket: &bucket,
Policy: &doc,
})
cancel()
if err != nil {
return err
}
testuser1Client := s.getUserClient(testuser1)
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = testuser1Client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = testuser1Client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{
Bucket: &bucket,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
user2Client := s.getUserClient(testuser2)
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = user2Client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{
Bucket: &bucket,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
return nil
})
}
func AccessControl_bucket_resource_all_action(s *S3Conf) error {
testName := "AccessControl_bucket_resource_all_action"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser1, testuser2 := getUser("user"), getUser("user")
err := createUsers(s, []user{testuser1, testuser2})
if err != nil {
return err
}
bucketResource := fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)
objectResource := fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket)
doc := genPolicyDoc("Allow", fmt.Sprintf(`["%s"]`, testuser1.access), `"s3:*"`, fmt.Sprintf(`[%v, %v]`, bucketResource, objectResource))
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
Bucket: &bucket,
Policy: &doc,
})
cancel()
if err != nil {
return err
}
testuser1Client := s.getUserClient(testuser1)
_, err = putObjects(testuser1Client, []string{"my-obj"}, bucket)
if err != nil {
return err
}
user2Client := s.getUserClient(testuser2)
_, err = putObjects(user2Client, []string{"my-obj"}, bucket)
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
return nil
})
}
func AccessControl_single_object_resource_actions(s *S3Conf) error {
testName := "AccessControl_single_object_resource_actions"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj/nested-obj"
_, err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
testuser := getUser("user")
err = createUsers(s, []user{testuser})
if err != nil {
return err
}
doc := genPolicyDoc("Allow", fmt.Sprintf(`["%s"]`, testuser.access), `"s3:*"`, fmt.Sprintf(`"arn:aws:s3:::%v/%v"`, bucket, obj))
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
Bucket: &bucket,
Policy: &doc,
})
cancel()
if err != nil {
return err
}
testuser1Client := s.getUserClient(testuser)
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = testuser1Client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = testuser1Client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{
Bucket: &bucket,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
return nil
})
}
func AccessControl_multi_statement_policy(s *S3Conf) error {
testName := "AccessControl_multi_statement_policy"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser := getUser("user")
err := createUsers(s, []user{testuser})
if err != nil {
return err
}
policy := fmt.Sprintf(`{
"Statement": [
{
"Effect": "Deny",
"Principal": ["%s"],
"Action": "s3:DeleteBucket",
"Resource": "arn:aws:s3:::%s"
},
{
"Effect": "Allow",
"Principal": "%s",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::%s", "arn:aws:s3:::%s/*"]
}
]
}`, testuser.access, bucket, testuser.access, bucket, bucket)
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
Bucket: &bucket,
Policy: &policy,
})
cancel()
if err != nil {
return err
}
userClient := s.getUserClient(testuser)
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = userClient.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = userClient.DeleteBucket(ctx, &s3.DeleteBucketInput{
Bucket: &bucket,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
return nil
})
}
func AccessControl_bucket_ownership_to_user(s *S3Conf) error {
testName := "AccessControl_bucket_ownership_to_user"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser := getUser("user")
if err := createUsers(s, []user{testuser}); err != nil {
return err
}
if err := changeBucketsOwner(s, []string{bucket}, testuser.access); err != nil {
return err
}
userClient := s.getUserClient(testuser)
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := userClient.HeadBucket(ctx, &s3.HeadBucketInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
return nil
})
}
func AccessControl_root_PutBucketAcl(s *S3Conf) error {
testName := "AccessControl_root_PutBucketAcl"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser := getUser("user")
if err := createUsers(s, []user{testuser}); err != nil {
return err
}
if err := changeBucketsOwner(s, []string{bucket}, testuser.access); err != nil {
return err
}
userClient := s.getUserClient(testuser)
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := userClient.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
ACL: types.BucketCannedACLPrivate,
})
cancel()
if err != nil {
return err
}
return nil
}, withOwnership(types.ObjectOwnershipBucketOwnerPreferred))
}
func AccessControl_user_PutBucketAcl_with_policy_access(s *S3Conf) error {
testName := "AccessControl_user_PutBucketAcl_with_policy_access"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser := getUser("user")
if err := createUsers(s, []user{testuser}); err != nil {
return err
}
policy := genPolicyDoc("Allow", fmt.Sprintf(`"%v"`, testuser.access), `"s3:PutBucketAcl"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket))
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
Bucket: &bucket,
Policy: &policy,
})
cancel()
if err != nil {
return err
}
userClient := s.getUserClient(testuser)
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = userClient.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
ACL: types.BucketCannedACLPublicRead,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
expectedGrants := []types.Grant{
{
Grantee: &types.Grantee{
ID: &s.awsID,
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionFullControl,
},
{
Grantee: &types.Grantee{
ID: getPtr("all-users"),
Type: types.TypeGroup,
},
Permission: types.PermissionRead,
},
}
if !compareGrants(res.Grants, expectedGrants) {
return fmt.Errorf("expected the resulting grants to be %v, instead got %v",
expectedGrants, res.Grants)
}
return nil
}, withOwnership(types.ObjectOwnershipBucketOwnerPreferred))
}
func AccessControl_copy_object_with_starting_slash_for_user(s *S3Conf) error {
testName := "AccessControl_copy_object_with_starting_slash_for_user"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
_, err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
testuser := getUser("user")
err = createUsers(s, []user{testuser})
if err != nil {
return err
}
if err := changeBucketsOwner(s, []string{bucket}, testuser.access); err != nil {
return err
}
copySource := fmt.Sprintf("/%v/%v", bucket, obj)
meta := map[string]string{
"key1": "val1",
}
userClient := s.getUserClient(testuser)
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{
Bucket: &bucket,
Key: &obj,
CopySource: &copySource,
Metadata: meta,
MetadataDirective: types.MetadataDirectiveReplace,
})
cancel()
if err != nil {
return err
}
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())
}