// 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" "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: ©Source, Metadata: meta, MetadataDirective: types.MetadataDirectiveReplace, }) cancel() if err != nil { return err } return nil }) }