From 891672bf7eed6693fc7fbc67b3e14e3b2e1c33b8 Mon Sep 17 00:00:00 2001 From: niksis02 Date: Thu, 24 Jul 2025 01:04:45 +0400 Subject: [PATCH] fix: fixes the HeadObject version access control with policies. Fixes #1385 When accessing a specific object version, the user must have the `s3:GetObjectVersion` permission in the bucket policy. The `s3:GetObject` permission alone is not sufficient for a regular user to query object versions using `HeadObject`. This PR fixes the issue and adds integration tests for both `HeadObject` and `GetObject`. It also includes cleanup in the integration tests by refactoring the creation of user S3 clients, and moves some test user data to the package level to avoid repetition across tests. --- s3api/controllers/object-get.go | 2 +- s3api/controllers/object-head.go | 7 +- s3api/controllers/object-head_test.go | 1 + tests/integration/group-tests.go | 4 + tests/integration/s3conf.go | 8 + tests/integration/tests.go | 510 ++++++++++++-------------- tests/integration/utils.go | 43 ++- 7 files changed, 284 insertions(+), 291 deletions(-) diff --git a/s3api/controllers/object-get.go b/s3api/controllers/object-get.go index 9fd7104c..422d5788 100644 --- a/s3api/controllers/object-get.go +++ b/s3api/controllers/object-get.go @@ -388,7 +388,7 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) { utils.ContextKeySkipResBodyLog.Set(ctx, true) action := auth.GetObjectAction - if versionId != "" { + if ctx.Request().URI().QueryArgs().Has("versionId") { action = auth.GetObjectVersionAction } diff --git a/s3api/controllers/object-head.go b/s3api/controllers/object-head.go index 57c749b0..55995740 100644 --- a/s3api/controllers/object-head.go +++ b/s3api/controllers/object-head.go @@ -41,6 +41,11 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) { objRange := ctx.Get("Range") key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket)) + action := auth.GetObjectAction + if ctx.Request().URI().QueryArgs().Has("versionId") { + action = auth.GetObjectVersionAction + } + err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ Readonly: c.readonly, @@ -50,7 +55,7 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) { Acc: acct, Bucket: bucket, Object: key, - Action: auth.GetObjectAction, + Action: action, IsBucketPublic: isPublicBucket, }) if err != nil { diff --git a/s3api/controllers/object-head_test.go b/s3api/controllers/object-head_test.go index 9d243e33..5467097c 100644 --- a/s3api/controllers/object-head_test.go +++ b/s3api/controllers/object-head_test.go @@ -57,6 +57,7 @@ func TestS3ApiController_HeadObject(t *testing.T) { locals: defaultLocals, queries: map[string]string{ "partNumber": "-4", + "versionId": "id", }, }, output: testOutput{ diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index f838ded5..458f5d34 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -869,6 +869,8 @@ func TestVersioning(s *S3Conf) { Versioning_WORM_obj_version_locked_with_compliance_retention(s) // Concurrent requests //Versioninig_concurrent_upload_object(s) + Versioning_AccessControl_GetObjectVersion(s) + Versioning_AccessControl_HeadObjectVersion(s) } func TestVersioningDisabled(s *S3Conf) { @@ -1366,6 +1368,8 @@ func GetIntTests() IntTests { "Versioning_WORM_obj_version_locked_with_legal_hold": Versioning_WORM_obj_version_locked_with_legal_hold, "Versioning_WORM_obj_version_locked_with_governance_retention": Versioning_WORM_obj_version_locked_with_governance_retention, "Versioning_WORM_obj_version_locked_with_compliance_retention": Versioning_WORM_obj_version_locked_with_compliance_retention, + "Versioning_AccessControl_GetObjectVersion": Versioning_AccessControl_GetObjectVersion, + "Versioning_AccessControl_HeadObjectVersion": Versioning_AccessControl_HeadObjectVersion, "Versioning_concurrent_upload_object": Versioning_concurrent_upload_object, "RouterPutPartNumberWithoutUploadId": RouterPutPartNumberWithoutUploadId, "RouterPostRoot": RouterPostRoot, diff --git a/tests/integration/s3conf.go b/tests/integration/s3conf.go index 9b965b77..8600d91b 100644 --- a/tests/integration/s3conf.go +++ b/tests/integration/s3conf.go @@ -145,6 +145,14 @@ func (c *S3Conf) GetAnonymousClient() *s3.Client { }) } +func (cfg *S3Conf) getUserClient(usr user) *s3.Client { + config := *cfg + config.awsID = usr.access + config.awsSecret = usr.secret + + return config.GetClient() +} + func (c *S3Conf) Config() aws.Config { creds := c.getCreds() diff --git a/tests/integration/tests.go b/tests/integration/tests.go index e5b99d47..1380bbb5 100644 --- a/tests/integration/tests.go +++ b/tests/integration/tests.go @@ -1383,15 +1383,10 @@ func CreateBucket_invalid_bucket_name(s *S3Conf) error { func CreateBucket_as_user(s *S3Conf) error { testName := "CreateBucket_as_user" runF(testName) - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } cfg := *s - cfg.awsID = usr.access - cfg.awsSecret = usr.secret - err := createUsers(s, []user{usr}) + cfg.awsID = testuser1.access + cfg.awsSecret = testuser1.secret + err := createUsers(s, []user{testuser1}) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) @@ -1411,19 +1406,14 @@ func CreateBucket_existing_bucket(s *S3Conf) error { testName := "CreateBucket_existing_bucket" runF(testName) bucket := getBucketName() - admin := user{ - access: "admin1", - secret: "admin1secret", - role: "admin", - } - if err := createUsers(s, []user{admin}); err != nil { + if err := createUsers(s, []user{testadmin}); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } adminCfg := *s - adminCfg.awsID = admin.access - adminCfg.awsSecret = admin.secret + adminCfg.awsID = testadmin.access + adminCfg.awsSecret = testadmin.secret err := setup(&adminCfg, bucket) if err != nil { @@ -1724,32 +1714,23 @@ func ListBuckets_as_user(s *S3Conf) error { Name: &bckt, }) } - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - err := createUsers(s, []user{usr}) + err := createUsers(s, []user{testuser1}) if err != nil { return err } - cfg := *s - cfg.awsID = usr.access - cfg.awsSecret = usr.secret - bckts := []string{} for i := range 3 { bckts = append(bckts, *buckets[i].Name) } - err = changeBucketsOwner(s, bckts, usr.access) + err = changeBucketsOwner(s, bckts, testuser1.access) if err != nil { return err } - userClient := cfg.GetClient() + userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := userClient.ListBuckets(ctx, &s3.ListBucketsInput{}) @@ -1758,9 +1739,9 @@ func ListBuckets_as_user(s *S3Conf) error { return err } - if getString(out.Owner.ID) != usr.access { + if getString(out.Owner.ID) != testuser1.access { return fmt.Errorf("expected buckets owner to be %v, instead got %v", - usr.access, getString(out.Owner.ID)) + testuser1.access, getString(out.Owner.ID)) } if !compareBuckets(out.Buckets, buckets[:3]) { return fmt.Errorf("expected list buckets result to be %v, instead got %v", @@ -1794,37 +1775,23 @@ func ListBuckets_as_admin(s *S3Conf) error { Name: &bckt, }) } - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - admin := user{ - access: "admin1", - secret: "admin1secret", - role: "admin", - } - err := createUsers(s, []user{usr, admin}) + err := createUsers(s, []user{testuser1, testadmin}) if err != nil { return err } - cfg := *s - cfg.awsID = admin.access - cfg.awsSecret = admin.secret - bckts := []string{} for i := range 3 { bckts = append(bckts, *buckets[i].Name) } - err = changeBucketsOwner(s, bckts, usr.access) + err = changeBucketsOwner(s, bckts, testuser1.access) if err != nil { return err } - adminClient := cfg.GetClient() + adminClient := s.getUserClient(testadmin) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := adminClient.ListBuckets(ctx, &s3.ListBucketsInput{}) @@ -1833,9 +1800,9 @@ func ListBuckets_as_admin(s *S3Conf) error { return err } - if getString(out.Owner.ID) != admin.access { + if getString(out.Owner.ID) != testadmin.access { return fmt.Errorf("expected buckets owner to be %v, instead got %v", - admin.access, getString(out.Owner.ID)) + testadmin.access, getString(out.Owner.ID)) } if !compareBuckets(out.Buckets, buckets) { return fmt.Errorf("expected list buckets result to be %v, instead got %v", @@ -6252,19 +6219,9 @@ func CopyObject_not_owned_source_bucket(s *S3Conf) error { return err } - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } + userClient := s.getUserClient(testuser1) - cfg := *s - cfg.awsID = usr.access - cfg.awsSecret = usr.secret - - userS3Client := cfg.GetClient() - - err = createUsers(s, []user{usr}) + err = createUsers(s, []user{testuser1}) if err != nil { return err } @@ -6275,13 +6232,13 @@ func CopyObject_not_owned_source_bucket(s *S3Conf) error { return err } - err = changeBucketsOwner(s, []string{bucket}, usr.access) + err = changeBucketsOwner(s, []string{bucket}, testuser1.access) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) - _, err = userS3Client.CopyObject(ctx, &s3.CopyObjectInput{ + _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &dstBucket, Key: getPtr("obj-1"), CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), @@ -11816,7 +11773,7 @@ func PutBucketAcl_invalid_acl_canned_and_acp(s *S3Conf) error { _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPrivate, - GrantRead: getPtr("user1"), + GrantRead: getPtr("testuser1"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBothCannedAndHeaderGrants)); err != nil { @@ -11892,21 +11849,16 @@ func PutBucketAcl_invalid_acl_acp_and_grants(s *S3Conf) error { func PutBucketAcl_invalid_owner(s *S3Conf) error { testName := "PutBucketAcl_invalid_owner" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - if err := createUsers(s, []user{usr}); err != nil { + if err := createUsers(s, []user{testuser1}); err != nil { return err } - if err := changeBucketsOwner(s, []string{bucket}, usr.access); err != nil { + if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } - userClient := getUserS3Client(usr, s) + userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := userClient.PutBucketAcl(ctx, &s3.PutBucketAclInput{ @@ -11915,7 +11867,7 @@ func PutBucketAcl_invalid_owner(s *S3Conf) error { Grants: []types.Grant{ { Grantee: &types.Grantee{ - ID: getPtr(usr.access), + ID: getPtr(testuser1.access), Type: types.TypeCanonicalUser, }, Permission: types.PermissionRead, @@ -12087,7 +12039,7 @@ func PutBucketAcl_empty_grantee_ID_in_body(s *S3Conf) error { func PutBucketAcl_success_access_denied(s *S3Conf) error { testName := "PutBucketAcl_success_access_denied" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - err := createUsers(s, []user{{"grt1", "grt1secret", "user"}}) + err := createUsers(s, []user{testuser1}) if err != nil { return err } @@ -12099,7 +12051,7 @@ func PutBucketAcl_success_access_denied(s *S3Conf) error { Grants: []types.Grant{ { Grantee: &types.Grantee{ - ID: getPtr("grt1"), + ID: getPtr(testuser1.access), Type: types.TypeCanonicalUser, }, Permission: types.PermissionRead, @@ -12115,10 +12067,7 @@ func PutBucketAcl_success_access_denied(s *S3Conf) error { return err } - newConf := *s - newConf.awsID = "grt1" - newConf.awsSecret = "grt1secret" - userClient := newConf.GetClient() + userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { @@ -12132,7 +12081,7 @@ func PutBucketAcl_success_access_denied(s *S3Conf) error { func PutBucketAcl_success_canned_acl(s *S3Conf) error { testName := "PutBucketAcl_success_canned_acl" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - err := createUsers(s, []user{{"grt1", "grt1secret", "user"}}) + err := createUsers(s, []user{testuser1}) if err != nil { return err } @@ -12147,10 +12096,7 @@ func PutBucketAcl_success_canned_acl(s *S3Conf) error { return err } - newConf := *s - newConf.awsID = "grt1" - newConf.awsSecret = "grt1secret" - userClient := newConf.GetClient() + userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err != nil { @@ -12164,7 +12110,7 @@ func PutBucketAcl_success_canned_acl(s *S3Conf) error { func PutBucketAcl_success_acp(s *S3Conf) error { testName := "PutBucketAcl_success_acp" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - err := createUsers(s, []user{{"grt1", "grt1secret", "user"}}) + err := createUsers(s, []user{testuser1}) if err != nil { return err } @@ -12172,17 +12118,14 @@ func PutBucketAcl_success_acp(s *S3Conf) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, - GrantRead: getPtr("grt1"), + GrantRead: &testuser1.access, }) cancel() if err != nil { return err } - newConf := *s - newConf.awsID = "grt1" - newConf.awsSecret = "grt1secret" - userClient := newConf.GetClient() + userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { @@ -12205,7 +12148,7 @@ func PutBucketAcl_success_acp(s *S3Conf) error { func PutBucketAcl_success_grants(s *S3Conf) error { testName := "PutBucketAcl_success_grants" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - err := createUsers(s, []user{{"grt1", "grt1secret", "user"}}) + err := createUsers(s, []user{testuser1}) if err != nil { return err } @@ -12217,7 +12160,7 @@ func PutBucketAcl_success_grants(s *S3Conf) error { Grants: []types.Grant{ { Grantee: &types.Grantee{ - ID: getPtr("grt1"), + ID: &testuser1.access, Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, @@ -12233,10 +12176,7 @@ func PutBucketAcl_success_grants(s *S3Conf) error { return err } - newConf := *s - newConf.awsID = "grt1" - newConf.awsSecret = "grt1secret" - userClient := newConf.GetClient() + userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err != nil { @@ -12422,15 +12362,12 @@ func GetBucketAcl_translation_canned_private(s *S3Conf) error { func GetBucketAcl_access_denied(s *S3Conf) error { testName := "GetBucketAcl_access_denied" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - err := createUsers(s, []user{{"grt1", "grt1secret", "user"}}) + err := createUsers(s, []user{testuser1}) if err != nil { return err } - newConf := *s - newConf.awsID = "grt1" - newConf.awsSecret = "grt1secret" - userClient := newConf.GetClient() + userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.GetBucketAcl(ctx, &s3.GetBucketAclInput{ @@ -13011,11 +12948,7 @@ func PutBucketPolicy_bucket_action_on_object_resource(s *S3Conf) error { func PutBucketPolicy_explicit_deny(s *S3Conf) error { testName := "PutBucketPolicy_explicit_deny" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - user2 := user{"grt2", "grt2secret", "user"} - err := createUsers(s, []user{ - {"grt1", "grt1secret", "user"}, - user2, - }) + err := createUsers(s, []user{testuser1, testuser2}) if err != nil { return err } @@ -13075,7 +13008,7 @@ func PutBucketPolicy_explicit_deny(s *S3Conf) error { return err } - userClient := getUserS3Client(user2, s) + userClient := s.getUserClient(testuser2) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.PutObject(ctx, &s3.PutObjectInput{ @@ -13094,13 +13027,12 @@ func PutBucketPolicy_explicit_deny(s *S3Conf) error { func PutBucketPolicy_multi_wildcard_resource(s *S3Conf) error { testName := "PutBucketPolicy_multi_wildcard_resource" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - usr := user{"grt1", "grt1secret", "user"} - if err := createUsers(s, []user{usr}); err != nil { + if err := createUsers(s, []user{testuser1}); err != nil { return err } resource := fmt.Sprintf(`["arn:aws:s3:::%v/*/*", "arn:aws:s3:::%v"]`, bucket, bucket) - principal := fmt.Sprintf("\"%v\"", usr.access) + principal := fmt.Sprintf("\"%v\"", testuser1.access) doc := genPolicyDoc("Allow", principal, `"s3:*"`, resource) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) @@ -13113,7 +13045,7 @@ func PutBucketPolicy_multi_wildcard_resource(s *S3Conf) error { return err } - userClient := getUserS3Client(usr, s) + userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"foo"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err @@ -13131,13 +13063,12 @@ func PutBucketPolicy_multi_wildcard_resource(s *S3Conf) error { func PutBucketPolicy_any_char_match(s *S3Conf) error { testName := "PutBucketPolicy_any_char_match" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - usr := user{"grt1", "grt1secret", "user"} - if err := createUsers(s, []user{usr}); err != nil { + if err := createUsers(s, []user{testuser1}); err != nil { return err } resource := fmt.Sprintf(`["arn:aws:s3:::%v/m?-obj/*"]`, bucket) - principal := fmt.Sprintf("\"%v\"", usr.access) + principal := fmt.Sprintf("\"%v\"", testuser1.access) doc := genPolicyDoc("Allow", principal, `"s3:*"`, resource) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) @@ -13150,7 +13081,7 @@ func PutBucketPolicy_any_char_match(s *S3Conf) error { return err } - userClient := getUserS3Client(usr, s) + userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"myy-obj/hello", "rand/foo", "my-objj/bar"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err @@ -13168,10 +13099,7 @@ func PutBucketPolicy_any_char_match(s *S3Conf) error { func PutBucketPolicy_success(s *S3Conf) error { testName := "PutBucketPolicy_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - err := createUsers(s, []user{ - {"grt1", "grt1secret", "user"}, - {"grt2", "grt2secret", "user"}, - }) + err := createUsers(s, []user{testuser1, testuser2}) if err != nil { return err } @@ -14993,21 +14921,14 @@ func WORMProtection_root_bypass_governance_retention_delete_object(s *S3Conf) er 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 { - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - err := createUsers(s, []user{usr}) + err := createUsers(s, []user{testuser1}) if err != nil { return err } - cfg := *s - cfg.awsID = usr.access - cfg.awsSecret = usr.secret + userClient := s.getUserClient(testuser1) - _, err = putObjects(cfg.GetClient(), []string{"my-obj"}, bucket) + _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } @@ -15019,21 +14940,14 @@ func AccessControl_default_ACL_user_access_denied(s *S3Conf) error { 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 { - usr := user{ - access: "userplus1", - secret: "userplus1secret", - role: "userplus", - } - err := createUsers(s, []user{usr}) + err := createUsers(s, []user{testuserplus}) if err != nil { return err } - cfg := *s - cfg.awsID = usr.access - cfg.awsSecret = usr.secret + client := s.getUserClient(testuserplus) - _, err = putObjects(cfg.GetClient(), []string{"my-obj"}, bucket) + _, err = putObjects(client, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } @@ -15045,21 +14959,14 @@ func AccessControl_default_ACL_userplus_access_denied(s *S3Conf) error { 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 { - admin := user{ - access: "admin1", - secret: "admin1secret", - role: "admin", - } - err := createUsers(s, []user{admin}) + err := createUsers(s, []user{testadmin}) if err != nil { return err } - cfg := *s - cfg.awsID = admin.access - cfg.awsSecret = admin.secret + adminClient := s.getUserClient(testadmin) - _, err = putObjects(cfg.GetClient(), []string{"my-obj"}, bucket) + _, err = putObjects(adminClient, []string{"my-obj"}, bucket) if err != nil { return err } @@ -15071,17 +14978,7 @@ func AccessControl_default_ACL_admin_successful_access(s *S3Conf) error { 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 { - usr1 := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - usr2 := user{ - access: "grt2", - secret: "grt2secret", - role: "user", - } - err := createUsers(s, []user{usr1, usr2}) + err := createUsers(s, []user{testuser1, testuser2}) if err != nil { return err } @@ -15097,10 +14994,10 @@ func AccessControl_bucket_resource_single_action(s *S3Conf) error { return err } - user1Client := getUserS3Client(usr1, s) + testuser1Client := s.getUserClient(testuser1) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) - _, err = user1Client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ + _, err = testuser1Client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ Bucket: &bucket, }) cancel() @@ -15109,7 +15006,7 @@ func AccessControl_bucket_resource_single_action(s *S3Conf) error { } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) - _, err = user1Client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ + _, err = testuser1Client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) cancel() @@ -15117,7 +15014,7 @@ func AccessControl_bucket_resource_single_action(s *S3Conf) error { return err } - user2Client := getUserS3Client(usr2, s) + user2Client := s.getUserClient(testuser2) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = user2Client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ @@ -15135,17 +15032,7 @@ func AccessControl_bucket_resource_single_action(s *S3Conf) error { 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 { - usr1 := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - usr2 := user{ - access: "grt2", - secret: "grt2secret", - role: "user", - } - err := createUsers(s, []user{usr1, usr2}) + err := createUsers(s, []user{testuser1, testuser2}) if err != nil { return err } @@ -15164,13 +15051,13 @@ func AccessControl_bucket_resource_all_action(s *S3Conf) error { return err } - user1Client := getUserS3Client(usr1, s) - _, err = putObjects(user1Client, []string{"my-obj"}, bucket) + testuser1Client := s.getUserClient(testuser1) + _, err = putObjects(testuser1Client, []string{"my-obj"}, bucket) if err != nil { return err } - user2Client := getUserS3Client(usr2, s) + user2Client := s.getUserClient(testuser2) _, err = putObjects(user2Client, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { @@ -15190,12 +15077,7 @@ func AccessControl_single_object_resource_actions(s *S3Conf) error { return err } - usr1 := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - err = createUsers(s, []user{usr1}) + err = createUsers(s, []user{testuser1}) if err != nil { return err } @@ -15212,10 +15094,10 @@ func AccessControl_single_object_resource_actions(s *S3Conf) error { return err } - user1Client := getUserS3Client(usr1, s) + testuser1Client := s.getUserClient(testuser1) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) - _, err = user1Client.GetObject(ctx, &s3.GetObjectInput{ + _, err = testuser1Client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) @@ -15225,7 +15107,7 @@ func AccessControl_single_object_resource_actions(s *S3Conf) error { } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) - _, err = user1Client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ + _, err = testuser1Client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) cancel() @@ -15257,12 +15139,7 @@ func AccessControl_multi_statement_policy(s *S3Conf) error { ] }`, bucket, bucket, bucket) - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - err := createUsers(s, []user{usr}) + err := createUsers(s, []user{testuser1}) if err != nil { return err } @@ -15277,7 +15154,7 @@ func AccessControl_multi_statement_policy(s *S3Conf) error { return err } - userClient := getUserS3Client(usr, s) + userClient := s.getUserClient(testuser1) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.ListObjects(ctx, &s3.ListObjectsInput{ @@ -15304,21 +15181,15 @@ func AccessControl_multi_statement_policy(s *S3Conf) error { 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 { - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - - if err := createUsers(s, []user{usr}); err != nil { + if err := createUsers(s, []user{testuser1}); err != nil { return err } - if err := changeBucketsOwner(s, []string{bucket}, usr.access); err != nil { + if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } - userClient := getUserS3Client(usr, s) + userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := userClient.HeadBucket(ctx, &s3.HeadBucketInput{ @@ -15336,21 +15207,16 @@ func AccessControl_bucket_ownership_to_user(s *S3Conf) error { func AccessControl_root_PutBucketAcl(s *S3Conf) error { testName := "AccessControl_root_PutBucketAcl" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - if err := createUsers(s, []user{usr}); err != nil { + if err := createUsers(s, []user{testuser1}); err != nil { return err } - if err := changeBucketsOwner(s, []string{bucket}, usr.access); err != nil { + if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } - userClient := getUserS3Client(usr, s) + userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := userClient.PutBucketAcl(ctx, &s3.PutBucketAclInput{ @@ -15369,17 +15235,11 @@ func AccessControl_root_PutBucketAcl(s *S3Conf) error { 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 { - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - - if err := createUsers(s, []user{usr}); err != nil { + if err := createUsers(s, []user{testuser1}); err != nil { return err } - policy := genPolicyDoc("Allow", fmt.Sprintf(`"%v"`, usr.access), `"s3:PutBucketAcl"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)) + policy := genPolicyDoc("Allow", fmt.Sprintf(`"%v"`, testuser1.access), `"s3:PutBucketAcl"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ @@ -15391,7 +15251,7 @@ func AccessControl_user_PutBucketAcl_with_policy_access(s *S3Conf) error { return err } - userClient := getUserS3Client(usr, s) + userClient := s.getUserClient(testuser1) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, @@ -15446,13 +15306,7 @@ func AccessControl_copy_object_with_starting_slash_for_user(s *S3Conf) error { return err } - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - - if err := changeBucketsOwner(s, []string{bucket}, usr.access); err != nil { + if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } @@ -15461,7 +15315,7 @@ func AccessControl_copy_object_with_starting_slash_for_user(s *S3Conf) error { "key1": "val1", } - userClient := getUserS3Client(usr, s) + userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, @@ -17899,19 +17753,13 @@ func IAM_user_access_denied(s *S3Conf) error { testName := "IAM_user_access_denied" runF(testName) - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - - err := createUsers(s, []user{usr}) + err := createUsers(s, []user{testuser1}) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } - out, err := execCommand(s.getAdminCommand("-a", usr.access, "-s", usr.secret, "-er", s.endpoint, "delete-user", "-a", "random_access")...) + out, err := execCommand(s.getAdminCommand("-a", testuser1.access, "-s", testuser1.secret, "-er", s.endpoint, "delete-user", "-a", "random_access")...) if err == nil { failF("%v: expected cmd error", testName) return fmt.Errorf("%v: expected cmd error", testName) @@ -17932,19 +17780,13 @@ func IAM_userplus_access_denied(s *S3Conf) error { testName := "IAM_userplus_access_denied" runF(testName) - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "userplus", - } - - err := createUsers(s, []user{usr}) + err := createUsers(s, []user{testuserplus}) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } - out, err := execCommand(s.getAdminCommand("-a", usr.access, "-s", usr.secret, "-er", s.endpoint, "delete-user", "-a", "random_access")...) + out, err := execCommand(s.getAdminCommand("-a", testuserplus.access, "-s", testuserplus.secret, "-er", s.endpoint, "delete-user", "-a", "random_access")...) if err == nil { failF("%v: expected cmd error", testName) return fmt.Errorf("%v: expected cmd error", testName) @@ -17964,20 +17806,14 @@ func IAM_userplus_access_denied(s *S3Conf) error { func IAM_userplus_CreateBucket(s *S3Conf) error { testName := "IAM_userplus_CreateBucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "userplus", - } - - err := createUsers(s, []user{usr}) + err := createUsers(s, []user{testuserplus}) if err != nil { return err } cfg := *s - cfg.awsID = usr.access - cfg.awsSecret = usr.secret + cfg.awsID = testuserplus.access + cfg.awsSecret = testuserplus.secret bckt := getBucketName() err = setup(&cfg, bckt) @@ -18004,22 +17840,12 @@ func IAM_userplus_CreateBucket(s *S3Conf) error { func IAM_admin_ChangeBucketOwner(s *S3Conf) error { testName := "IAM_admin_ChangeBucketOwner" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - admin := user{ - access: "admin1", - secret: "admin1secret", - role: "admin", - } - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - err := createUsers(s, []user{admin, usr}) + err := createUsers(s, []user{testadmin, testuser1}) if err != nil { return err } - err = changeBucketsOwner(s, []string{bucket}, usr.access) + err = changeBucketsOwner(s, []string{bucket}, testuser1.access) if err != nil { return err } @@ -18031,9 +17857,9 @@ func IAM_admin_ChangeBucketOwner(s *S3Conf) error { return err } - if getString(resp.Owner.ID) != usr.access { + if getString(resp.Owner.ID) != testuser1.access { return fmt.Errorf("expected the bucket owner to be %v, instead got %v", - usr.access, getString(resp.Owner.ID)) + testuser1.access, getString(resp.Owner.ID)) } return nil @@ -18043,18 +17869,12 @@ func IAM_admin_ChangeBucketOwner(s *S3Conf) error { func IAM_ChangeBucketOwner_back_to_root(s *S3Conf) error { testName := "IAM_ChangeBucketOwner_back_to_root" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - usr := user{ - access: "grt1", - secret: "grt1secret", - role: "user", - } - - if err := createUsers(s, []user{usr}); err != nil { + if err := createUsers(s, []user{testuser1}); err != nil { return err } // Change the bucket ownership to a random user - if err := changeBucketsOwner(s, []string{bucket}, usr.access); err != nil { + if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } @@ -20961,6 +20781,146 @@ func Versioning_WORM_obj_version_locked_with_compliance_retention(s *S3Conf) err }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } +func Versioning_AccessControl_GetObjectVersion(s *S3Conf) error { + testName := "Versioning_AccessControl_GetObjectVersion" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + obj := "my-obj" + objData, err := putObjectWithData(10, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj, + }, s3client) + if err != nil { + return err + } + + err = createUsers(s, []user{testuser1}) + if err != nil { + return err + } + + doc := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser1.access), `"s3:GetObject"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket)) + fmt.Println(doc) + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ + Bucket: &bucket, + Policy: &doc, + }) + cancel() + if err != nil { + return err + } + + userClient := s.getUserClient(testuser1) + + // querying with versionId should return access denied + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &obj, + VersionId: objData.res.VersionId, + }) + defer cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { + return err + } + + // grant the user s3:GetObjectVersion + doc = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser1.access), `"s3:GetObjectVersion"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket)) + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ + Bucket: &bucket, + Policy: &doc, + }) + cancel() + if err != nil { + return err + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &obj, + VersionId: objData.res.VersionId, + }) + defer cancel() + if err != nil { + return err + } + + return nil + }, withVersioning(types.BucketVersioningStatusEnabled)) +} + +func Versioning_AccessControl_HeadObjectVersion(s *S3Conf) error { + testName := "Versioning_AccessControl_HeadObjectVersion" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + obj := "my-obj" + objData, err := putObjectWithData(10, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &obj, + }, s3client) + if err != nil { + return err + } + + err = createUsers(s, []user{testuser1}) + if err != nil { + return err + } + + doc := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser1.access), `"s3:GetObject"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket)) + fmt.Println(doc) + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ + Bucket: &bucket, + Policy: &doc, + }) + cancel() + if err != nil { + return err + } + + userClient := s.getUserClient(testuser1) + + // querying with versionId should return access denied + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.HeadObject(ctx, &s3.HeadObjectInput{ + Bucket: &bucket, + Key: &obj, + VersionId: objData.res.VersionId, + }) + cancel() + if err := checkSdkApiErr(err, http.StatusText(http.StatusForbidden)); err != nil { + return err + } + + // grant the user s3:GetObjectVersion + doc = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser1.access), `"s3:GetObjectVersion"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket)) + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ + Bucket: &bucket, + Policy: &doc, + }) + cancel() + if err != nil { + return err + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = userClient.HeadObject(ctx, &s3.HeadObjectInput{ + Bucket: &bucket, + Key: &obj, + VersionId: objData.res.VersionId, + }) + cancel() + if err != nil { + return err + } + + return nil + }, withVersioning(types.BucketVersioningStatusEnabled)) +} + func VersioningDisabled_GetBucketVersioning_not_configured(s *S3Conf) error { testName := "VersioningDisabled_GetBucketVersioning_not_configured" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { diff --git a/tests/integration/utils.go b/tests/integration/utils.go index 1ff2a1aa..33a9bb17 100644 --- a/tests/integration/utils.go +++ b/tests/integration/utils.go @@ -53,6 +53,35 @@ var ( adminErrorPrefix = "XAdmin" ) +type user struct { + access string + secret string + role string +} + +var ( + testuser1 user = user{ + access: "grt1", + secret: "grt1secret", + role: "user", + } + testuser2 user = user{ + access: "grt2", + secret: "grt2secret", + role: "user", + } + testuserplus user = user{ + access: "grtplus", + secret: "grt1plussecret", + role: "userplus", + } + testadmin user = user{ + access: "admin", + secret: "adminsecret", + role: "admin", + } +) + func getBucketName() string { bcktCount++ return fmt.Sprintf("test-bucket-%v", bcktCount) @@ -899,12 +928,6 @@ func uploadParts(client *s3.Client, size, partCount int64, bucket, key, uploadId return parts, csum, err } -type user struct { - access string - secret string - role string -} - func createUsers(s *S3Conf, users []user) error { for _, usr := range users { err := deleteUser(s, usr.access) @@ -1065,14 +1088,6 @@ func getMalformedPolicyError(msg string) s3err.APIError { } } -func getUserS3Client(usr user, cfg *S3Conf) *s3.Client { - config := *cfg - config.awsID = usr.access - config.awsSecret = usr.secret - - return config.GetClient() -} - // if true enables, otherwise disables func changeBucketObjectLockStatus(client *s3.Client, bucket string, status bool) error { cfg := types.ObjectLockConfiguration{}