Upstream versity/versitygw state of the touched files before 3d4c24e2e1

This commit is contained in:
upstream committed 2026-10-14 19:40:59 +02:00
1 parent deab0fc412
commit 3fee7f49c9
4 files changed
+1787

No files matched your search

+517
View File
@@ -0,0 +1,517 @@
// 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 auth
import (
"context"
"encoding/json"
"encoding/xml"
"fmt"
"slices"
"strings"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/debuglogger"
"github.com/versity/versitygw/s3err"
)
type ACL struct {
Owner string
Grantees []Grantee
}
// IsPublic specifies if the acl grants public read access
func (acl *ACL) IsPublic(permission Permission) bool {
for _, grt := range acl.Grantees {
if grt.Permission == permission && grt.Type == types.TypeGroup && grt.Access == "all-users" {
return true
}
}
return false
}
type Grantee struct {
Permission Permission
Access string
Type types.Type
}
type GetBucketAclOutput struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ AccessControlPolicy"`
Owner *types.Owner
AccessControlList AccessControlList
}
type PutBucketAclInput struct {
Bucket *string
ACL types.BucketCannedACL
AccessControlPolicy *AccessControlPolicy
GrantFullControl *string
GrantRead *string
GrantReadACP *string
GrantWrite *string
GrantWriteACP *string
}
type AccessControlPolicy struct {
AccessControlList AccessControlList `xml:"AccessControlList"`
Owner *types.Owner
}
func (acp *AccessControlPolicy) Validate() error {
if !acp.AccessControlList.isValid() {
return s3err.GetAPIError(s3err.ErrMalformedACL)
}
// The Owner can't be nil
if acp.Owner == nil {
return s3err.GetAPIError(s3err.ErrMalformedACL)
}
// The Owner ID can't be empty
if acp.Owner.ID == nil || *acp.Owner.ID == "" {
return s3err.GetAPIError(s3err.ErrMalformedACL)
}
return nil
}
type AccessControlList struct {
Grants []Grant `xml:"Grant"`
}
// Validates the AccessControlList
func (acl *AccessControlList) isValid() bool {
for _, el := range acl.Grants {
if !el.isValid() {
return false
}
}
return true
}
type Permission string
const (
PermissionFullControl Permission = "FULL_CONTROL"
PermissionWrite Permission = "WRITE"
PermissionWriteAcp Permission = "WRITE_ACP"
PermissionRead Permission = "READ"
PermissionReadAcp Permission = "READ_ACP"
)
// Check if the permission is valid
func (p Permission) isValid() bool {
return p == PermissionFullControl ||
p == PermissionRead ||
p == PermissionReadAcp ||
p == PermissionWrite ||
p == PermissionWriteAcp
}
type Grant struct {
Grantee *Grt `xml:"Grantee"`
Permission Permission `xml:"Permission"`
}
// Checks if Grant is valid
func (g *Grant) isValid() bool {
return g.Permission.isValid() && g.Grantee.isValid()
}
type Grt struct {
XMLNS string `xml:"xmlns:xsi,attr"`
Type types.Type `xml:"xsi:type,attr"`
ID string `xml:"ID"`
}
// Custom Unmarshalling for Grt to parse xsi:type properly
func (g *Grt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
// Iterate through the XML tokens to process the attributes
for _, attr := range start.Attr {
// Check if the attribute is xsi:type and belongs to the xsi namespace
if attr.Name.Space == "http://www.w3.org/2001/XMLSchema-instance" && attr.Name.Local == "type" {
g.Type = types.Type(attr.Value)
}
// Handle xmlns:xsi
if attr.Name.Local == "xmlns:xsi" {
g.XMLNS = attr.Value
}
}
// Decode the inner XML elements like ID
for {
t, err := d.Token()
if err != nil {
return err
}
switch se := t.(type) {
case xml.StartElement:
if se.Name.Local == "ID" {
if err := d.DecodeElement(&g.ID, &se); err != nil {
return err
}
}
case xml.EndElement:
if se.Name.Local == start.Name.Local {
return nil
}
}
}
}
// Validates Grt
func (g *Grt) isValid() bool {
// Validate the Type
// Only these 2 types are supported in the gateway
if g.Type != types.TypeCanonicalUser && g.Type != types.TypeGroup {
return false
}
// The ID prop shouldn't be empty
if g.ID == "" {
return false
}
return true
}
func ParseACL(data []byte) (ACL, error) {
if len(data) == 0 {
return ACL{}, nil
}
var acl ACL
if err := json.Unmarshal(data, &acl); err != nil {
return acl, fmt.Errorf("parse acl: %w", err)
}
return acl, nil
}
func ParseACLOutput(data []byte, owner string) (GetBucketAclOutput, error) {
grants := []Grant{}
if len(data) == 0 {
return GetBucketAclOutput{
Owner: &types.Owner{
ID: &owner,
},
AccessControlList: AccessControlList{
Grants: grants,
},
}, nil
}
var acl ACL
if err := json.Unmarshal(data, &acl); err != nil {
return GetBucketAclOutput{}, fmt.Errorf("parse acl: %w", err)
}
for _, elem := range acl.Grantees {
acs := elem.Access
grants = append(grants, Grant{
Grantee: &Grt{
XMLNS: "http://www.w3.org/2001/XMLSchema-instance",
ID: acs,
Type: elem.Type,
},
Permission: elem.Permission,
})
}
return GetBucketAclOutput{
Owner: &types.Owner{
ID: &acl.Owner,
},
AccessControlList: AccessControlList{
Grants: grants,
},
}, nil
}
func UpdateACL(input *PutBucketAclInput, acl ACL, iam IAMService) ([]byte, error) {
if input == nil {
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
}
defaultGrantees := []Grantee{
{
Permission: PermissionFullControl,
Access: acl.Owner,
Type: types.TypeCanonicalUser,
},
}
// if the ACL is specified, set the ACL, else replace the grantees
if input.ACL != "" {
switch input.ACL {
case types.BucketCannedACLPublicRead:
defaultGrantees = append(defaultGrantees, Grantee{
Permission: PermissionRead,
Access: "all-users",
Type: types.TypeGroup,
})
case types.BucketCannedACLPublicReadWrite:
defaultGrantees = append(defaultGrantees, []Grantee{
{
Permission: PermissionRead,
Access: "all-users",
Type: types.TypeGroup,
},
{
Permission: PermissionWrite,
Access: "all-users",
Type: types.TypeGroup,
},
}...)
}
} else {
accs := []string{}
if input.GrantRead != nil || input.GrantReadACP != nil || input.GrantFullControl != nil || input.GrantWrite != nil || input.GrantWriteACP != nil {
fullControlList, readList, readACPList, writeList, writeACPList := []string{}, []string{}, []string{}, []string{}, []string{}
if input.GrantFullControl != nil && *input.GrantFullControl != "" {
fullControlList = splitUnique(*input.GrantFullControl, ",")
for _, str := range fullControlList {
defaultGrantees = append(defaultGrantees, Grantee{
Access: str,
Permission: PermissionFullControl,
Type: types.TypeCanonicalUser,
})
}
}
if input.GrantRead != nil && *input.GrantRead != "" {
readList = splitUnique(*input.GrantRead, ",")
for _, str := range readList {
defaultGrantees = append(defaultGrantees, Grantee{
Access: str,
Permission: PermissionRead,
Type: types.TypeCanonicalUser,
})
}
}
if input.GrantReadACP != nil && *input.GrantReadACP != "" {
readACPList = splitUnique(*input.GrantReadACP, ",")
for _, str := range readACPList {
defaultGrantees = append(defaultGrantees, Grantee{
Access: str,
Permission: PermissionReadAcp,
Type: types.TypeCanonicalUser,
})
}
}
if input.GrantWrite != nil && *input.GrantWrite != "" {
writeList = splitUnique(*input.GrantWrite, ",")
for _, str := range writeList {
defaultGrantees = append(defaultGrantees, Grantee{
Access: str,
Permission: PermissionWrite,
Type: types.TypeCanonicalUser,
})
}
}
if input.GrantWriteACP != nil && *input.GrantWriteACP != "" {
writeACPList = splitUnique(*input.GrantWriteACP, ",")
for _, str := range writeACPList {
defaultGrantees = append(defaultGrantees, Grantee{
Access: str,
Permission: PermissionWriteAcp,
Type: types.TypeCanonicalUser,
})
}
}
accs = append(append(append(append(fullControlList, readList...), writeACPList...), readACPList...), writeList...)
} else {
cache := make(map[string]bool)
for _, grt := range input.AccessControlPolicy.AccessControlList.Grants {
if grt.Grantee == nil || grt.Grantee.ID == "" || grt.Permission == "" {
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
}
access := grt.Grantee.ID
defaultGrantees = append(defaultGrantees, Grantee{
Access: access,
Permission: grt.Permission,
Type: types.TypeCanonicalUser,
})
if _, ok := cache[access]; !ok {
cache[access] = true
accs = append(accs, access)
}
}
}
// Check if the specified accounts exist
accList, err := iam.ResolveAccounts(accs)
if err != nil {
return nil, err
}
if len(accList) > 0 {
return nil, fmt.Errorf("accounts does not exist: %s", strings.Join(accList, ", "))
}
}
acl.Grantees = defaultGrantees
result, err := json.Marshal(acl)
if err != nil {
return nil, err
}
return result, nil
}
func splitUnique(s, divider string) []string {
elements := strings.Split(s, divider)
uniqueElements := make(map[string]bool)
result := make([]string, 0, len(elements))
for _, element := range elements {
if _, ok := uniqueElements[element]; !ok {
result = append(result, element)
uniqueElements[element] = true
}
}
return result
}
// aclActionPermissions maps every action an ACL grant can authorize to the
// permission that grants it. The gateway keeps no object ACLs, so the
// bucket's grants stand in for its objects' as well: READ also reads
// objects, and READ_ACP and WRITE_ACP also cover object ACLs. A grant
// authorizes nothing else. Bucket configuration, the bucket policy, tagging
// and object lock settings are the bucket owner's, or whoever a policy
// grants them to. s3:DeleteObjectVersion is left out because WRITE grants
// it to the bucket owner alone. READ reads an object but not its
// attributes, so neither s3:GetObjectAttributes nor
// s3:GetObjectVersionAttributes is here.
var aclActionPermissions = map[Action]Permission{
ListBucketAction: PermissionRead,
ListBucketVersionsAction: PermissionRead,
ListBucketMultipartUploadsAction: PermissionRead,
GetObjectAction: PermissionRead,
GetObjectVersionAction: PermissionRead,
PutObjectAction: PermissionWrite,
DeleteObjectAction: PermissionWrite,
GetBucketAclAction: PermissionReadAcp,
GetObjectAclAction: PermissionReadAcp,
PutBucketAclAction: PermissionWriteAcp,
PutObjectAclAction: PermissionWriteAcp,
}
// aclAllows reports whether acl lets access perform action. The bucket
// owner may perform any action on its own bucket, as an AWS account may.
// Any other grantee may perform only the actions its grants map to, and
// none at all while ACLs are disabled.
func aclAllows(acl ACL, access string, action Action, disableACL bool) bool {
if access == acl.Owner {
return true
}
if disableACL {
return false
}
permission, ok := aclActionPermissions[action]
if !ok {
return false
}
grantee := Grantee{
Access: access,
Permission: permission,
Type: types.TypeCanonicalUser,
}
granteeFullCtrl := Grantee{
Access: access,
Permission: PermissionFullControl,
Type: types.TypeCanonicalUser,
}
granteeAllUsers := Grantee{
Access: "all-users",
Permission: permission,
Type: types.TypeGroup,
}
return slices.ContainsFunc(acl.Grantees, func(grt Grantee) bool {
return grt == grantee || grt == granteeFullCtrl || grt == granteeAllUsers
})
}
// Verifies if the bucket acl grants public access
func VerifyPublicBucketACL(ctx context.Context, be backend.Backend, bucket string, permission Permission) error {
aclBytes, err := be.GetBucketAcl(ctx, &s3.GetBucketAclInput{
Bucket: &bucket,
})
if err != nil {
return err
}
acl, err := ParseACL(aclBytes)
if err != nil {
return err
}
if !acl.IsPublic(permission) {
return errAccessDenied
}
return nil
}
// UpdateBucketACLOwner sets default ACL with new owner and removes
// any previous bucket policy that was in place
func UpdateBucketACLOwner(ctx context.Context, be backend.Backend, bucket, newOwner string) error {
acl := ACL{
Owner: newOwner,
Grantees: []Grantee{
{
Permission: PermissionFullControl,
Access: newOwner,
Type: types.TypeCanonicalUser,
},
},
}
result, err := json.Marshal(acl)
if err != nil {
return fmt.Errorf("marshal ACL: %w", err)
}
err = be.PutBucketAcl(ctx, bucket, result)
if err != nil {
return err
}
return be.DeleteBucketPolicy(ctx, bucket)
}
// ValidateCannedACL validates bucket canned acl value
func ValidateCannedACL(acl types.BucketCannedACL) error {
switch types.BucketCannedACL(acl) {
case types.BucketCannedACLPrivate, types.BucketCannedACLPublicRead, types.BucketCannedACLPublicReadWrite, "":
return nil
default:
debuglogger.Logf("invalid bucket canned acl: %v", acl)
return s3err.GetInvalidArgumentErr(s3err.InvalidArgCannedAcl, string(acl))
}
}
+663
View File
@@ -0,0 +1,663 @@
// 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"
"errors"
"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"
)
func CreateBucket_invalid_bucket_name(s *S3Conf) error {
testName := "CreateBucket_invalid_bucket_name"
runF(testName)
err := setup(s, "aa")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(s, ".gitignore")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(s, "my-bucket.")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(s, "bucket-%")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func CreateBucket_as_user(s *S3Conf) error {
testName := "CreateBucket_as_user"
runF(testName)
testuser := getUser("user")
cfg := *s
cfg.awsID = testuser.access
cfg.awsSecret = testuser.secret
err := createUsers(s, []user{testuser})
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(&cfg, getBucketName())
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func CreateBucket_existing_bucket(s *S3Conf) error {
testName := "CreateBucket_existing_bucket"
runF(testName)
bucket := getBucketName()
adminUser := getUser("admin")
if err := createUsers(s, []user{adminUser}); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
adminCfg := *s
adminCfg.awsID = adminUser.access
adminCfg.awsSecret = adminUser.secret
err := setup(&adminCfg, bucket)
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(s, bucket)
var bne *types.BucketAlreadyExists
if !errors.As(err, &bne) {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = teardown(s, bucket)
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func CreateBucket_owned_by_you(s *S3Conf) error {
testName := "CreateBucket_owned_by_you"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
})
cancel()
var bErr *types.BucketAlreadyOwnedByYou
if !errors.As(err, &bErr) {
return fmt.Errorf("expected error to be %w, instead got %w", s3err.GetAPIError(s3err.ErrBucketAlreadyOwnedByYou), err)
}
return nil
})
}
func CreateBucket_invalid_ownership(s *S3Conf) error {
testName := "CreateBucket_invalid_ownership"
runF(testName)
invalidOwnership := types.ObjectOwnership("invalid_ownership")
err := setup(s, getBucketName(), withOwnership(invalidOwnership))
if err := checkApiErr(err, s3err.GetInvalidArgObjectOwnership(string(invalidOwnership))); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func CreateBucket_ownership_with_acl(s *S3Conf) error {
testName := "CreateBucket_ownership_with_acl"
runF(testName)
client := s.GetClient()
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: getPtr(getBucketName()),
ObjectOwnership: types.ObjectOwnershipBucketOwnerEnforced,
ACL: types.BucketCannedACLPublicRead,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketAclWithObjectOwnership)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func CreateBucket_default_acl(s *S3Conf) error {
testName := "CreateBucket_default_acl"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket})
cancel()
if err != nil {
return err
}
if getString(out.Owner.ID) != s.awsID {
return fmt.Errorf("expected bucket owner to be %v, instead got %v",
s.awsID, getString(out.Owner.ID))
}
if len(out.Grants) != 1 {
return fmt.Errorf("expected grants length to be 1, instead got %v",
len(out.Grants))
}
grt := out.Grants[0]
if grt.Permission != types.PermissionFullControl {
return fmt.Errorf("expected the grantee to have full-control permission, instead got %v",
grt.Permission)
}
if getString(grt.Grantee.ID) != s.awsID {
return fmt.Errorf("expected the grantee id to be %v, instead got %v",
s.awsID, getString(grt.Grantee.ID))
}
return nil
})
}
func CreateBucket_non_default_acl(s *S3Conf) error {
testName := "CreateBucket_non_default_acl"
runF(testName)
testuser1, testuser2, testuser3 := getUser("user"), getUser("user"), getUser("user")
err := createUsers(s, []user{testuser1, testuser2, testuser3})
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
grants := []types.Grant{
{
Grantee: &types.Grantee{
ID: &s.awsID,
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionFullControl,
},
{
Grantee: &types.Grantee{
ID: &testuser1.access,
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionFullControl,
},
{
Grantee: &types.Grantee{
ID: &testuser2.access,
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionReadAcp,
},
{
Grantee: &types.Grantee{
ID: &testuser3.access,
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionWrite,
},
}
bucket := getBucketName()
client := s.GetClient()
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
GrantFullControl: &testuser1.access,
GrantReadACP: &testuser2.access,
GrantWrite: &testuser3.access,
ObjectOwnership: types.ObjectOwnershipBucketOwnerPreferred,
})
cancel()
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err := client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket})
cancel()
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
if !compareGrants(out.Grants, grants) {
failF("%v: expected bucket acl grants to be %v, instead got %v", testName, grants, out.Grants)
return fmt.Errorf("%v: expected bucket acl grants to be %v, instead got %v", testName, grants, out.Grants)
}
err = teardown(s, bucket)
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func CreateBucket_private_canned_acl(s *S3Conf) error {
testName := "CreateBucket_private_canned_acl"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
ACL: types.BucketCannedACLPrivate,
})
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
}
if getString(res.Owner.ID) != s.awsID {
return fmt.Errorf("expected bucket owner to be %v, instead got %v",
s.awsID, getString(res.Owner.ID))
}
if len(res.Grants) != 1 {
return fmt.Errorf("expected grants length to be 1, instead got %v",
len(res.Grants))
}
grt := res.Grants[0]
if grt.Permission != types.PermissionFullControl {
return fmt.Errorf("expected the grantee to have full-control permission, instead got %v",
grt.Permission)
}
if getString(grt.Grantee.ID) != s.awsID {
return fmt.Errorf("expected the grantee id to be %v, instead got %v",
s.awsID, getString(grt.Grantee.ID))
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if out.OwnershipControls == nil {
return fmt.Errorf("unexpected nil OwnershipControls")
}
if len(out.OwnershipControls.Rules) != 1 {
return fmt.Errorf("expected the OwnershipControls rules length to be 1, instead got %v", len(out.OwnershipControls.Rules))
}
if out.OwnershipControls.Rules[0].ObjectOwnership != types.ObjectOwnershipBucketOwnerPreferred {
return fmt.Errorf("expected the ObjectOwnership to be %s, instead got %s", types.ObjectOwnershipBucketOwnerPreferred, out.OwnershipControls.Rules[0].ObjectOwnership)
}
return teardown(s, bucket)
})
}
func CreateBucket_private_canned_acl_bucket_owner_enforced_ownership(s *S3Conf) error {
testName := "CreateBucket_private_canned_acl_bucket_owner_enforced_ownership"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
ACL: types.BucketCannedACLPrivate,
ObjectOwnership: types.ObjectOwnershipBucketOwnerEnforced,
})
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
}
if getString(res.Owner.ID) != s.awsID {
return fmt.Errorf("expected bucket owner to be %v, instead got %v",
s.awsID, getString(res.Owner.ID))
}
if len(res.Grants) != 1 {
return fmt.Errorf("expected grants length to be 1, instead got %v",
len(res.Grants))
}
grt := res.Grants[0]
if grt.Permission != types.PermissionFullControl {
return fmt.Errorf("expected the grantee to have full-control permission, instead got %v",
grt.Permission)
}
if getString(grt.Grantee.ID) != s.awsID {
return fmt.Errorf("expected the grantee id to be %v, instead got %v",
s.awsID, getString(grt.Grantee.ID))
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if out.OwnershipControls == nil {
return fmt.Errorf("unexpected nil OwnershipControls")
}
if len(out.OwnershipControls.Rules) != 1 {
return fmt.Errorf("expected the OwnershipControls rules length to be 1, instead got %v", len(out.OwnershipControls.Rules))
}
if out.OwnershipControls.Rules[0].ObjectOwnership != types.ObjectOwnershipBucketOwnerEnforced {
return fmt.Errorf("expected the ObjectOwnership to be %s, instead got %s", types.ObjectOwnershipBucketOwnerEnforced, out.OwnershipControls.Rules[0].ObjectOwnership)
}
return teardown(s, bucket)
})
}
func CreateBucket_default_object_lock(s *S3Conf) error {
testName := "CreateBucket_default_object_lock"
runF(testName)
bucket := getBucketName()
lockEnabled := true
client := s.GetClient()
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
ObjectLockEnabledForBucket: &lockEnabled,
})
cancel()
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
resp, err := client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{
Bucket: &bucket,
})
cancel()
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
if resp.ObjectLockConfiguration.ObjectLockEnabled != types.ObjectLockEnabledEnabled {
failF("%v: expected object lock to be enabled", testName)
return fmt.Errorf("%v: expected object lock to be enabled", testName)
}
err = teardown(s, bucket)
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func CreateBucket_invalid_location_constraint(s *S3Conf) error {
testName := "CreateBucket_invalid_location_constraint"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
var region types.BucketLocationConstraint
if types.BucketLocationConstraint(s.awsID) == types.BucketLocationConstraintUsWest1 {
region = types.BucketLocationConstraintUsWest2
} else {
region = types.BucketLocationConstraintUsWest1
}
createBucket := func(region types.BucketLocationConstraint) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
CreateBucketConfiguration: &types.CreateBucketConfiguration{
LocationConstraint: region,
},
})
cancel()
return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidLocationConstraint))
}
for _, lConstraint := range []types.BucketLocationConstraint{region, "us-east-1"} {
err := createBucket(lConstraint)
if err != nil {
return err
}
}
return nil
})
}
func CreateBucket_long_tags(s *S3Conf) error {
testName := "CreateBucket_long_tags"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
tagging := []types.Tag{{Key: getPtr(genRandString(200)), Value: getPtr("val")}}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
CreateBucketConfiguration: &types.CreateBucketConfiguration{
Tags: tagging,
},
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTagKey)); err != nil {
return err
}
tagging = []types.Tag{{Key: getPtr("key"), Value: getPtr(genRandString(300))}}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
CreateBucketConfiguration: &types.CreateBucketConfiguration{
Tags: tagging,
},
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTagValue)); err != nil {
return err
}
return nil
})
}
func CreateBucket_invalid_tags(s *S3Conf) error {
testName := "CreateBucket_invalid_tags"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
for i, test := range []struct {
tags []types.Tag
err s3err.APIError
}{
// invalid tag key tests
{[]types.Tag{{Key: getPtr("user!name"), Value: getPtr("value")}}, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
{[]types.Tag{{Key: getPtr("foo#bar"), Value: getPtr("value")}}, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
{[]types.Tag{
{Key: getPtr("validkey"), Value: getPtr("validvalue")},
{Key: getPtr("data%20"), Value: getPtr("value")},
}, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
{[]types.Tag{
{Key: getPtr("abcd"), Value: getPtr("xyz123")},
{Key: getPtr("a*b"), Value: getPtr("value")},
}, s3err.GetAPIError(s3err.ErrInvalidTagKey)},
// invalid tag value tests
{[]types.Tag{
{Key: getPtr("hello"), Value: getPtr("world")},
{Key: getPtr("key"), Value: getPtr("name?test")},
}, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
{[]types.Tag{
{Key: getPtr("foo"), Value: getPtr("bar")},
{Key: getPtr("key"), Value: getPtr("`path")},
}, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
{[]types.Tag{{Key: getPtr("valid"), Value: getPtr("comma,separated")}}, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
{[]types.Tag{{Key: getPtr("valid"), Value: getPtr("semicolon;test")}}, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
{[]types.Tag{{Key: getPtr("valid"), Value: getPtr("(parentheses)")}}, s3err.GetAPIError(s3err.ErrInvalidTagValue)},
} {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
CreateBucketConfiguration: &types.CreateBucketConfiguration{
Tags: test.tags,
},
})
cancel()
if err == nil {
return fmt.Errorf("test %v failed: expected err %w, instead got nil", i+1, test.err)
}
if err := checkApiErr(err, test.err); err != nil {
return fmt.Errorf("test %v failed: %w", i+1, err)
}
}
return nil
})
}
func CreateBucket_duplicate_keys(s *S3Conf) error {
testName := "CreateBucket_duplicate_keys"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
tagging := []types.Tag{
{Key: getPtr("key"), Value: getPtr("value")},
{Key: getPtr("key"), Value: getPtr("value-1")},
{Key: getPtr("key-1"), Value: getPtr("value-2")},
{Key: getPtr("key-2"), Value: getPtr("value-3")},
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
CreateBucketConfiguration: &types.CreateBucketConfiguration{
Tags: tagging,
},
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrDuplicateTagKey)); err != nil {
return err
}
return nil
})
}
func CreateBucket_tag_count_limit(s *S3Conf) error {
testName := "CreateBucket_tag_count_limit"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
tagSet := []types.Tag{}
for i := range 51 {
tagSet = append(tagSet, types.Tag{
Key: getPtr(fmt.Sprintf("key-%v", i)),
Value: getPtr(genRandString(10)),
})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
CreateBucketConfiguration: &types.CreateBucketConfiguration{
Tags: tagSet,
},
})
cancel()
return checkApiErr(err, s3err.GetAPIError(s3err.ErrBucketTaggingLimited))
})
}
func CreateBucket_invalid_canned_acl(s *S3Conf) error {
testName := "CreateBucket_invalid_canned_acl"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
ACL: types.BucketCannedACL("invalid_acl"),
})
cancel()
return checkSdkApiErr(err, "InvalidArgument")
})
}
func CreateBucket_success(s *S3Conf) error {
testName := "CreateBucket_success"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
expectedLocation := "/" + bucket
if getString(res.Location) != expectedLocation {
return fmt.Errorf("expected the bucket Location to be %s, instead got %s", expectedLocation, getString(res.Location))
}
expectedArn := "arn:aws:s3:::" + bucket
if getString(res.BucketArn) != expectedArn {
return fmt.Errorf("expected the bucket arn to be %s, instead got %s", expectedArn, getString(res.BucketArn))
}
return teardown(s, bucket)
})
}
+367
View File
@@ -0,0 +1,367 @@
// 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"
"encoding/xml"
"fmt"
"net/http"
"strings"
"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"
"github.com/versity/versitygw/s3response"
)
func IAM_user_access_denied(s *S3Conf) error {
testName := "IAM_user_access_denied"
runF(testName)
testuser := getUser("user")
err := createUsers(s, []user{testuser})
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
out, err := execCommand(s.getAdminCommand("-a", testuser.access, "-s", testuser.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)
}
if !strings.Contains(string(out), s3err.GetAPIError(s3err.ErrAdminAccessDenied).Code) {
failF("%v: expected response error message to be %v, instead got %s",
testName, s3err.GetAPIError(s3err.ErrAdminAccessDenied).Error(), out)
return fmt.Errorf("%v: expected response error message to be %v, instead got %s",
testName, s3err.GetAPIError(s3err.ErrAdminAccessDenied).Error(), out)
}
passF(testName)
return nil
}
func IAM_userplus_access_denied(s *S3Conf) error {
testName := "IAM_userplus_access_denied"
runF(testName)
testuser := getUser("userplus")
err := createUsers(s, []user{testuser})
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
out, err := execCommand(s.getAdminCommand("-a", testuser.access, "-s", testuser.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)
}
if !strings.Contains(string(out), s3err.GetAPIError(s3err.ErrAdminAccessDenied).Code) {
failF("%v: expected response error message to be %v, instead got %s",
testName, s3err.GetAPIError(s3err.ErrAdminAccessDenied).Error(), out)
return fmt.Errorf("%v: expected response error message to be %v, instead got %s",
testName, s3err.GetAPIError(s3err.ErrAdminAccessDenied).Error(), out)
}
passF(testName)
return nil
}
func IAM_userplus_CreateBucket(s *S3Conf) error {
testName := "IAM_userplus_CreateBucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser := getUser("userplus")
err := createUsers(s, []user{testuser})
if err != nil {
return err
}
cfg := *s
cfg.awsID = testuser.access
cfg.awsSecret = testuser.secret
bckt := getBucketName()
err = setup(&cfg, bckt)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bckt})
cancel()
if err != nil {
return err
}
err = teardown(&cfg, bckt)
if err != nil {
return err
}
return nil
})
}
func IAM_admin_ChangeBucketOwner(s *S3Conf) error {
testName := "IAM_admin_ChangeBucketOwner"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
testuser, adminuser := getUser("user"), getUser("admin")
err := createUsers(s, []user{adminuser, testuser})
if err != nil {
return err
}
err = changeBucketsOwner(s, []string{bucket}, testuser.access)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
resp, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket})
cancel()
if err != nil {
return err
}
if getString(resp.Owner.ID) != testuser.access {
return fmt.Errorf("expected the bucket owner to be %v, instead got %v",
testuser.access, getString(resp.Owner.ID))
}
return nil
})
}
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 {
testuser := getUser("user")
if err := createUsers(s, []user{testuser}); err != nil {
return err
}
// Change the bucket ownership to a random user
if err := changeBucketsOwner(s, []string{bucket}, testuser.access); err != nil {
return err
}
// Change the bucket ownership back to the root user
if err := changeBucketsOwner(s, []string{bucket}, s.awsID); err != nil {
return err
}
return nil
})
}
func IAM_ListBuckets(s *S3Conf) error {
testName := "IAM_ListBuckets"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
return listBuckets(s)
})
}
func IAM_CreateBucket_empty_owner_header(s *S3Conf) error {
testName := "IAM_CreateBucket_empty_owner_header"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
req, err := createSignedReq(
http.MethodPatch,
s.endpoint,
fmt.Sprintf("%s/create", bucket),
s.awsID,
s.awsSecret,
"s3",
s.awsRegion,
"",
nil,
time.Now(),
nil,
)
if err != nil {
return err
}
resp, err := s.httpClient.Do(req)
if err != nil {
return err
}
return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrAdminEmptyBucketOwnerHeader))
})
}
func IAM_CreateBucket_non_existing_user(s *S3Conf) error {
testName := "IAM_CreateBucket_non_existing_user"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
req, err := createSignedReq(
http.MethodPatch,
s.endpoint,
fmt.Sprintf("%s/create", bucket),
s.awsID,
s.awsSecret,
"s3",
s.awsRegion,
"",
nil,
time.Now(),
map[string]string{
"x-vgw-owner": "non-existing-user",
},
)
if err != nil {
return err
}
resp, err := s.httpClient.Do(req)
if err != nil {
return err
}
return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrAdminUserNotFound))
})
}
func IAM_CreateBucket_success(s *S3Conf) error {
testName := "IAM_CreateBucket_success"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
tagSet := []types.Tag{
{Key: getPtr("key"), Value: getPtr("value")},
}
body, err := xml.Marshal(s3response.CreateBucketConfiguration{
TagSet: tagSet,
})
if err != nil {
return err
}
testUser1, testUser2 := getUser("user"), getUser("user")
err = createUsers(s, []user{testUser1, testUser2})
if err != nil {
return err
}
req, err := createSignedReq(
http.MethodPatch,
s.endpoint,
fmt.Sprintf("%s/create", bucket),
s.awsID,
s.awsSecret,
"s3",
s.awsRegion,
"",
body,
time.Now(),
map[string]string{
"x-amz-bucket-object-lock-enabled": "true",
"x-amz-object-ownership": string(types.ObjectOwnershipBucketOwnerPreferred),
"x-amz-grant-read": testUser2.access,
"x-vgw-owner": testUser1.access,
},
)
if err != nil {
return err
}
resp, err := s.httpClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusCreated {
return fmt.Errorf("expected the response status code to be %v, instead got %v", http.StatusCreated, resp.StatusCode)
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
tagging, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if !areTagsSame(tagSet, tagging.TagSet) {
return fmt.Errorf("expected the bucket tagging to be %v, instead got %v", tagSet, tagging.TagSet)
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
ownership, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
var ownershipControls types.ObjectOwnership
if ownership.OwnershipControls != nil && len(ownership.OwnershipControls.Rules) == 1 {
ownershipControls = ownership.OwnershipControls.Rules[0].ObjectOwnership
}
if ownershipControls != types.ObjectOwnershipBucketOwnerPreferred {
return fmt.Errorf("expected the bucket ownership controls to be %s, instaed got %s", types.ObjectOwnershipBucketOwnerPreferred, ownershipControls)
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
acl, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if len(acl.Grants) != 2 {
return fmt.Errorf("expected the length of acl grants to be 2, instead got %v", len(acl.Grants))
}
var granteeChecked bool
var ownerChecked bool
for _, grant := range acl.Grants {
// owner
if getString(grant.Grantee.ID) == testUser1.access {
ownerChecked = true
if grant.Permission != types.PermissionFullControl {
return fmt.Errorf("expected the owner '%s' to have %s permission, instead got %s", testUser1.access, types.PermissionFullControl, grant.Permission)
}
continue
}
if getString(grant.Grantee.ID) != testUser2.access {
return fmt.Errorf("expected the grantee ID to be %v, instaed got %v", testUser2.access, getString(grant.Grantee.ID))
}
if grant.Permission != types.PermissionRead {
return fmt.Errorf("expected the %v user permission to be %s, instead got %s", testUser2.access, types.PermissionRead, grant.Permission)
}
granteeChecked = true
}
if !ownerChecked {
return fmt.Errorf("missing the owner '%s' full control acl", testUser1.access)
}
if !granteeChecked {
return fmt.Errorf("missing the user %s in read grantees acl", testUser2.access)
}
return teardown(s, bucket)
})
}
+240
View File
@@ -0,0 +1,240 @@
#!/usr/bin/env bats
# Copyright 2024 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.
load ./bats-support/load
load ./bats-assert/load
source ./tests/commands/put_object.sh
source ./tests/drivers/file.sh
source ./tests/drivers/create_bucket/create_bucket_rest.sh
source ./tests/drivers/get_bucket_acl/get_bucket_acl_rest.sh
source ./tests/logger.sh
source ./tests/setup.sh
source ./tests/util/util_object.sh
source ./tests/util/util_public_access_block.sh
export RUN_USERS=true
if [ "$SKIP_ACL_TESTING" == "true" ] || [ "$SKIP_USERS_TESTS" == "true" ]; then
skip "Skipping ACL tests"
exit 0
fi
# tags: curl,minimal-request,GetBucketAcl
@test "REST - get ACL" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name="$output"
run setup_bucket_v2 "$bucket_name"
assert_success
run get_and_check_acl_rest "$bucket_name"
assert_success
}
# tags: curl,minimal-request,PutBucketAcl
@test "REST - put ACL" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name="$output"
test_file="test_file"
run setup_bucket_and_file_v2 "$bucket_name" "$test_file"
assert_success
run put_bucket_ownership_controls "$bucket_name" "BucketOwnerPreferred"
assert_success
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file" "$bucket_name" "$test_file"
assert_success
run create_versitygw_acl_user_or_get_direct_user "$USERNAME_ONE" "$PASSWORD_ONE"
assert_success
canonical_id=${lines[0]}
user_canonical_id=${lines[1]}
username=${lines[2]}
password=${lines[3]}
run setup_acl "$TEST_FILE_FOLDER/acl-file.txt" "CanonicalUser" "$user_canonical_id" "READ" "$canonical_id"
assert_success
run list_objects_with_user_rest_verify_access_denied "$bucket_name" "$username" "$password"
assert_success
run put_bucket_acl_rest "$bucket_name" "$TEST_FILE_FOLDER/acl-file.txt"
assert_success
if [ "$DIRECT" == "true" ]; then
sleep 5
fi
run list_objects_with_user_rest_verify_success "$bucket_name" "$username" "$password" "$test_file"
assert_success
}
# tags: curl,PutBucketAcl,x-amz-acl
@test "REST - put public-read canned acl" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name="$output"
test_file="test_file"
run setup_bucket_and_file_v2 "$bucket_name" "$test_file"
assert_success
run put_bucket_ownership_controls "$bucket_name" "BucketOwnerPreferred"
assert_success
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file" "$bucket_name" "$test_file"
assert_success
run create_versitygw_acl_user_or_get_direct_user "$USERNAME_ONE" "$PASSWORD_ONE"
assert_success
canonical_id=${lines[0]}
user_canonical_id=${lines[1]}
username=${lines[2]}
password=${lines[3]}
run list_objects_with_user_rest_verify_access_denied "$bucket_name" "$username" "$password"
assert_success
if [ "$DIRECT" == "true" ]; then
run allow_public_access "$bucket_name"
assert_success
fi
run put_canned_acl_rest "$bucket_name" "public-read"
assert_success
run list_objects_with_user_rest_verify_success "$bucket_name" "$username" "$password" "$test_file"
assert_success
}
# tags: curl,minimal-request,PutBucketAcl
@test "REST - put invalid ACL" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name="$output"
run setup_bucket_v2 "$bucket_name"
assert_success
run put_bucket_ownership_controls "$bucket_name" "BucketOwnerPreferred"
assert_success
run create_versitygw_acl_user_or_get_direct_user "$USERNAME_ONE" "$PASSWORD_ONE"
assert_success
canonical_id=${lines[0]}
user_canonical_id=${lines[1]}
username=${lines[2]}
password=${lines[3]}
run setup_acl "$TEST_FILE_FOLDER/acl-file.txt" "CanonicalUser" "$user_canonical_id" "READD" "$canonical_id"
assert_success
if [ "$DIRECT" == "true" ]; then
run allow_public_access "$bucket_name"
assert_success
fi
run put_invalid_acl_rest_verify_failure "$bucket_name" "$TEST_FILE_FOLDER/acl-file.txt"
assert_success
}
# tags: curl,PutBucketAcl,x-amz-acl
@test "REST - put public-read-write canned acl" {
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name="$output"
test_file="test_file"
run setup_bucket_and_file_v2 "$bucket_name" "$test_file"
assert_success
run put_bucket_ownership_controls "$bucket_name" "BucketOwnerPreferred"
assert_success
run create_versitygw_acl_user_or_get_direct_user "$USERNAME_ONE" "$PASSWORD_ONE"
assert_success
canonical_id=${lines[0]}
user_canonical_id=${lines[1]}
username=${lines[2]}
password=${lines[3]}
run put_object_with_user "s3api" "$TEST_FILE_FOLDER/$test_file" "$bucket_name" "$test_file" "$username" "$password"
assert_failure
if [ "$DIRECT" == "true" ]; then
run allow_public_access "$bucket_name"
assert_success
fi
run put_canned_acl_rest "$bucket_name" "public-read-write"
assert_success
run put_object_with_user "s3api" "$TEST_FILE_FOLDER/$test_file" "$bucket_name" "$test_file" "$username" "$password"
assert_success
}
# tags: curl,PutBucketAcl,x-amz-acl
@test "REST - invalid canned acl" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/1367"
fi
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name="$output"
test_file="test_file"
run setup_bucket_and_file_v2 "$bucket_name" "$test_file"
assert_success
run put_bucket_ownership_controls "$bucket_name" "BucketOwnerPreferred"
assert_success
run put_bucket_acl_rest_canned_invalid "$bucket_name" "privatee"
assert_success
}
# tags: curl,GetBucketAcl,x-amz-grant-read-acp
@test "REST - FULL_CONTROL permission not returned for owner after CreateBucket with GRANT_READ_ACP" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/1407"
fi
run get_bucket_name "$BUCKET_ONE_NAME"
assert_success
bucket_name="$output"
run create_versitygw_acl_user_or_get_direct_user "$USERNAME_ONE" "$PASSWORD_ONE"
assert_success
canonical_id=${lines[0]}
user_canonical_id=${lines[1]}
username=${lines[2]}
password=${lines[3]}
run bucket_cleanup_if_bucket_exists "$bucket_name"
assert_success
if [ "$DIRECT" == "true" ]; then
id="id=$user_canonical_id"
else
id="$user_canonical_id"
fi
envs="GRANT_READ_ACP=$id OBJECT_OWNERSHIP=BucketOwnerPreferred"
run create_bucket_rest_expect_success "$bucket_name" "$envs"
assert_success
run get_bucket_acl_rest "$bucket_name" "" "check_that_acl_xml_does_not_have_owner_permission"
assert_success
}