From d469a72213f66a356acdd74107f7dd733acab8f4 Mon Sep 17 00:00:00 2001 From: jonaustin09 Date: Fri, 15 Mar 2024 15:47:10 -0400 Subject: [PATCH] feat: Implemented Put/Get/DeletBucketPolicy s3 actions in posix backend. Implemented policy document validation function --- auth/bucket_policy.go | 90 +++++++++++ auth/bucket_policy_actions.go | 212 ++++++++++++++++++++++++++ auth/bucket_policy_effect.go | 34 +++++ auth/bucket_policy_principals.go | 86 +++++++++++ auth/bucket_policy_resources.go | 126 +++++++++++++++ backend/backend.go | 5 + backend/posix/posix.go | 49 ++++++ s3api/controllers/backend_moq_test.go | 52 ++++++- s3api/controllers/base.go | 32 ++++ 9 files changed, 685 insertions(+), 1 deletion(-) create mode 100644 auth/bucket_policy.go create mode 100644 auth/bucket_policy_actions.go create mode 100644 auth/bucket_policy_effect.go create mode 100644 auth/bucket_policy_principals.go create mode 100644 auth/bucket_policy_resources.go diff --git a/auth/bucket_policy.go b/auth/bucket_policy.go new file mode 100644 index 00000000..02a5c482 --- /dev/null +++ b/auth/bucket_policy.go @@ -0,0 +1,90 @@ +// 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 ( + "encoding/json" + "fmt" + "net/http" + + "github.com/versity/versitygw/s3err" +) + +type BucketPolicy struct { + Statement []BucketPolicyItem `json:"Statement"` +} + +func (bp *BucketPolicy) Validate(bucket string, iam IAMService) error { + for _, statement := range bp.Statement { + err := statement.Validate(bucket, iam) + if err != nil { + return err + } + } + + return nil +} + +type BucketPolicyItem struct { + Effect BucketPolicyAccessType `json:"Effect"` + Principals Principals `json:"Principal"` + Actions Actions `json:"Action"` + Resources Resources `json:"Resource"` +} + +func (bpi *BucketPolicyItem) Validate(bucket string, iam IAMService) error { + if err := bpi.Principals.Validate(iam); err != nil { + return err + } + if err := bpi.Effect.Validate(); err != nil { + return err + } + + containsObjectAction := bpi.Resources.ContainsObjectPattern() + containsBucketAction := bpi.Resources.ContainsBucketPattern() + + for action := range bpi.Actions { + isObjectAction := action.IsObjectAction() + if isObjectAction && !containsObjectAction { + return fmt.Errorf("unsupported object action '%v' on the specified resources", action) + } + if !isObjectAction && !containsBucketAction { + return fmt.Errorf("unsupported bucket action '%v', on the specified resources", action) + } + } + + return nil +} + +func getMalformedPolicyError(err error) error { + return s3err.APIError{ + Code: "MalformedPolicy", + Description: err.Error(), + HTTPStatusCode: http.StatusBadRequest, + } +} + +func ValidatePolicyDocument(policyBin []byte, bucket string, iam IAMService) error { + var policy BucketPolicy + if err := json.Unmarshal(policyBin, &policy); err != nil { + return getMalformedPolicyError(err) + } + + if err := policy.Validate(bucket, iam); err != nil { + return getMalformedPolicyError(err) + } + + return nil +} diff --git a/auth/bucket_policy_actions.go b/auth/bucket_policy_actions.go new file mode 100644 index 00000000..ab80a383 --- /dev/null +++ b/auth/bucket_policy_actions.go @@ -0,0 +1,212 @@ +// 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 ( + "encoding/json" + "fmt" + "strings" +) + +type Action string + +const ( + ListBuckets Action = "s3:ListBuckets" + HeadBucketAction Action = "s3:HeadBucket" + GetBucketAclAction Action = "s3:GetBucketAcl" + CreateBucketAction Action = "s3:CreateBucket" + PutBucketAclAction Action = "s3:PutBucketAcl" + DeleteBucketAction Action = "s3:DeleteBucket" + PutBucketVersioningAction Action = "s3:PutBucketVersioning" + GetBucketVersioningAction Action = "s3:GetBucketVersioning" + PutBucketPolicyAction Action = "s3:PutBucketPolicy" + GetBucketPolicyAction Action = "s3:GetBucketPolicy" + CreateMultipartUploadAction Action = "s3:CreateMultipartUpload" + CompleteMultipartUploadAction Action = "s3:CompleteMultipartUpload" + AbortMultipartUploadAction Action = "s3:AbortMultipartUpload" + ListMultipartUploadsAction Action = "s3:ListMultipartUploads" + ListPartsAction Action = "s3:ListParts" + UploadPartAction Action = "s3:UploadPart" + UploadPartCopyAction Action = "s3:UploadPartCopy" + PutObjectAction Action = "s3:PutObject" + HeadObjectAction Action = "s3:HeadObject" + GetObjectAction Action = "s3:GetObject" + GetObjectAclAction Action = "s3:GetObjectAcl" + GetObjectAttributesAction Action = "s3:GetObjectAttributes" + CopyObjectAction Action = "s3:CopyObject" + ListObjectsAction Action = "s3:ListObjects" + ListObjectsV2Action Action = "s3:ListObjectsV2" + DeleteObjectAction Action = "s3:DeleteObject" + DeleteObjectsAction Action = "s3:DeleteObjects" + PutObjectAclAction Action = "s3:PutObjectAcl" + ListObjectVersionsAction Action = "s3:ListObjectVersions" + RestoreObjectAction Action = "s3:RestoreObject" + SelectObjectContentAction Action = "s3:SelectObjectContent" + GetBucketTaggingAction Action = "s3:GetBucketTagging" + PutBucketTaggingAction Action = "s3:PutBucketTagging" + DeleteBucketTaggingAction Action = "s3:DeleteBucketTagging" + GetObjectTaggingAction Action = "s3:GetObjectTagging" + PutObjectTaggingAction Action = "s3:PutObjectTagging" + DeleteObjectTaggingAction Action = "s3:DeleteObjectTagging" + AllActions Action = "s3:*" +) + +var supportedActionList = map[Action]struct{}{ + ListBuckets: {}, + HeadBucketAction: {}, + GetBucketAclAction: {}, + CreateBucketAction: {}, + PutBucketAclAction: {}, + DeleteBucketAction: {}, + PutBucketVersioningAction: {}, + GetBucketVersioningAction: {}, + PutBucketPolicyAction: {}, + GetBucketPolicyAction: {}, + CreateMultipartUploadAction: {}, + CompleteMultipartUploadAction: {}, + AbortMultipartUploadAction: {}, + ListMultipartUploadsAction: {}, + ListPartsAction: {}, + UploadPartAction: {}, + UploadPartCopyAction: {}, + PutObjectAction: {}, + HeadObjectAction: {}, + GetObjectAction: {}, + GetObjectAclAction: {}, + GetObjectAttributesAction: {}, + CopyObjectAction: {}, + ListObjectsAction: {}, + ListObjectsV2Action: {}, + DeleteObjectAction: {}, + DeleteObjectsAction: {}, + PutObjectAclAction: {}, + ListObjectVersionsAction: {}, + RestoreObjectAction: {}, + SelectObjectContentAction: {}, + GetBucketTaggingAction: {}, + PutBucketTaggingAction: {}, + DeleteBucketTaggingAction: {}, + GetObjectTaggingAction: {}, + PutObjectTaggingAction: {}, + DeleteObjectTaggingAction: {}, + AllActions: {}, +} + +var supportedObjectActionList = map[Action]struct{}{ + CreateMultipartUploadAction: {}, + CompleteMultipartUploadAction: {}, + AbortMultipartUploadAction: {}, + ListMultipartUploadsAction: {}, + ListPartsAction: {}, + UploadPartAction: {}, + UploadPartCopyAction: {}, + PutObjectAction: {}, + HeadObjectAction: {}, + GetObjectAction: {}, + GetObjectAclAction: {}, + GetObjectAttributesAction: {}, + CopyObjectAction: {}, + ListObjectsAction: {}, + ListObjectsV2Action: {}, + DeleteObjectAction: {}, + DeleteObjectsAction: {}, + PutObjectAclAction: {}, + ListObjectVersionsAction: {}, + RestoreObjectAction: {}, + SelectObjectContentAction: {}, + GetObjectTaggingAction: {}, + PutObjectTaggingAction: {}, + DeleteObjectTaggingAction: {}, + AllActions: {}, +} + +// Validates Action: it should either wildcard match with supported actions list or be in it +func (a Action) IsValid() bool { + if a[len(a)-1] == '*' { + pattern := strings.TrimSuffix(string(a), "*") + for act := range supportedActionList { + if strings.HasPrefix(string(act), pattern) { + return true + } + } + + return false + } + + _, found := supportedActionList[a] + return found +} + +// Checks if the action is object action +func (a Action) IsObjectAction() bool { + if a[len(a)-1] == '*' { + pattern := strings.TrimSuffix(string(a), "*") + for act := range supportedObjectActionList { + if strings.HasPrefix(string(act), pattern) { + return true + } + } + + return false + } + + _, found := supportedObjectActionList[a] + return found +} + +type Actions map[Action]struct{} + +// Override UnmarshalJSON method to decode both []string and string properties +func (a *Actions) UnmarshalJSON(data []byte) error { + ss := []string{} + var err error + if err = json.Unmarshal(data, &ss); err == nil { + if len(ss) == 0 { + return fmt.Errorf("actions can't be empty") + } + *a = make(Actions) + for _, s := range ss { + err = a.Add(s) + if err != nil { + return err + } + } + } else { + var s string + if err = json.Unmarshal(data, &s); err == nil { + if s == "" { + return fmt.Errorf("actions can't be empty") + } + *a = make(Actions) + err = a.Add(s) + if err != nil { + return err + } + } + } + + return err +} + +// Validates and adds a new Action to Actions map +func (a Actions) Add(str string) error { + action := Action(str) + if !action.IsValid() { + return fmt.Errorf("invalid action") + } + + a[action] = struct{}{} + return nil +} diff --git a/auth/bucket_policy_effect.go b/auth/bucket_policy_effect.go new file mode 100644 index 00000000..c2ccc31d --- /dev/null +++ b/auth/bucket_policy_effect.go @@ -0,0 +1,34 @@ +// 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 "fmt" + +type BucketPolicyAccessType string + +const ( + BucketPolicyAccessTypeDeny BucketPolicyAccessType = "Deny" + BucketPolicyAccessTypeAllow BucketPolicyAccessType = "Allow" +) + +// Checks policy statement Effect to be valid ("Deny", "Allow") +func (bpat BucketPolicyAccessType) Validate() error { + switch bpat { + case BucketPolicyAccessTypeAllow, BucketPolicyAccessTypeDeny: + return nil + } + + return fmt.Errorf("invalid effect: %v", bpat) +} diff --git a/auth/bucket_policy_principals.go b/auth/bucket_policy_principals.go new file mode 100644 index 00000000..7abe49e9 --- /dev/null +++ b/auth/bucket_policy_principals.go @@ -0,0 +1,86 @@ +// 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 ( + "encoding/json" + "fmt" +) + +type Principals map[string]struct{} + +func (p Principals) Add(key string) { + p[key] = struct{}{} +} + +// Override UnmarshalJSON method to decode both []string and string properties +func (p *Principals) UnmarshalJSON(data []byte) error { + ss := []string{} + var err error + if err = json.Unmarshal(data, &ss); err == nil { + if len(ss) == 0 { + return fmt.Errorf("principals can't be empty") + } + *p = make(Principals) + for _, s := range ss { + p.Add(s) + } + } else { + var s string + if err = json.Unmarshal(data, &s); err == nil { + if s == "" { + return fmt.Errorf("principals can't be empty") + } + *p = make(Principals) + p.Add(s) + } + } + + return err +} + +// Converts Principals map to a slice, by omitting "*" +func (p Principals) ToSlice() []string { + principals := []string{} + for p := range p { + if p == "*" { + continue + } + principals = append(principals, p) + } + + return principals +} + +// Validates Principals by checking user account access keys existence +func (p Principals) Validate(iam IAMService) error { + _, containsWildCard := p["*"] + if containsWildCard { + if len(p) == 1 { + return nil + } + return fmt.Errorf("principals should either contain * or user access keys") + } + + accs, err := CheckIfAccountsExist(p.ToSlice(), iam) + if err != nil { + return err + } + if len(accs) > 0 { + return fmt.Errorf("users doesn't exist: %v", accs) + } + + return nil +} diff --git a/auth/bucket_policy_resources.go b/auth/bucket_policy_resources.go new file mode 100644 index 00000000..be02d132 --- /dev/null +++ b/auth/bucket_policy_resources.go @@ -0,0 +1,126 @@ +// 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 ( + "encoding/json" + "fmt" + "strings" +) + +type Resources map[string]struct{} + +const ResourceArnPrefix = "arn:aws:s3:::" + +// Override UnmarshalJSON method to decode both []string and string properties +func (r *Resources) UnmarshalJSON(data []byte) error { + ss := []string{} + var err error + if err = json.Unmarshal(data, &ss); err == nil { + if len(ss) == 0 { + return fmt.Errorf("resources can't be empty") + } + *r = make(Resources) + for _, s := range ss { + err = r.Add(s) + if err != nil { + return err + } + } + } else { + var s string + if err = json.Unmarshal(data, &s); err == nil { + if s == "" { + return fmt.Errorf("resources can't be empty") + } + *r = make(Resources) + err = r.Add(s) + if err != nil { + return err + } + } + } + + return err +} + +// Adds and validates a new resource to Resources map +func (r Resources) Add(rc string) error { + _, found := r[rc] + if found { + return fmt.Errorf("duplicate resource") + } + + ok, pattern := isValidResource(rc) + + if !ok { + return fmt.Errorf("invalid resource") + } + + r[pattern] = struct{}{} + + return nil +} + +// Checks if the resources contain object pattern +func (r Resources) ContainsObjectPattern() bool { + for resource := range r { + if resource == "*" || strings.Contains(resource, "/") { + return true + } + } + + return false +} + +// Checks if the resources contain bucket pattern +func (r Resources) ContainsBucketPattern() bool { + for resource := range r { + if resource == "*" || !strings.Contains(resource, "/") { + return true + } + } + + return false +} + +// Bucket resources should start with bucket name: arn:aws:s3:::MyBucket/* +func (r Resources) Validate(bucket string) error { + for resource := range r { + if !strings.HasPrefix(resource, bucket) { + return fmt.Errorf("invalid bucket resource") + } + } + + return nil +} + +// Checks the resource to have arn prefix and not starting with / +func isValidResource(rc string) (isValid bool, pattern string) { + if !strings.HasPrefix(rc, ResourceArnPrefix) { + return false, "" + } + + res := strings.TrimPrefix(rc, ResourceArnPrefix) + if res == "" { + return false, "" + } + // The resource can't start with / (bucket name comes first) + if res == "/" { + return false, "" + } + + return true, res +} diff --git a/backend/backend.go b/backend/backend.go index 64ab197d..b56e654a 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -42,6 +42,8 @@ type Backend interface { GetBucketVersioning(_ context.Context, bucket string) (*s3.GetBucketVersioningOutput, error) PutBucketPolicy(_ context.Context, bucket string, policy []byte) error GetBucketPolicy(_ context.Context, bucket string) ([]byte, error) + DeleteBucketPolicy(_ context.Context, bucket string) error + // multipart operations CreateMultipartUpload(context.Context, *s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) CompleteMultipartUpload(context.Context, *s3.CompleteMultipartUploadInput) (*s3.CompleteMultipartUploadOutput, error) @@ -125,6 +127,9 @@ func (BackendUnsupported) PutBucketPolicy(_ context.Context, bucket string, poli func (BackendUnsupported) GetBucketPolicy(_ context.Context, bucket string) ([]byte, error) { return nil, s3err.GetAPIError(s3err.ErrNotImplemented) } +func (BackendUnsupported) DeleteBucketPolicy(_ context.Context, bucket string) error { + return s3err.GetAPIError(s3err.ErrNotImplemented) +} func (BackendUnsupported) CreateMultipartUpload(context.Context, *s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) { return nil, s3err.GetAPIError(s3err.ErrNotImplemented) diff --git a/backend/posix/posix.go b/backend/posix/posix.go index a3f53a96..19106444 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -61,6 +61,7 @@ const ( emptyMD5 = "d41d8cd98f00b204e9800998ecf8427e" aclkey = "user.acl" etagkey = "user.etag" + policykey = "user.policy" ) func New(rootdir string) (*Posix, error) { @@ -1851,6 +1852,54 @@ func (p *Posix) DeleteObjectTagging(ctx context.Context, bucket, object string) return p.PutObjectTagging(ctx, bucket, object, nil) } +func (p *Posix) PutBucketPolicy(ctx context.Context, bucket string, policy []byte) error { + _, err := os.Stat(bucket) + if errors.Is(err, fs.ErrNotExist) { + return s3err.GetAPIError(s3err.ErrNoSuchBucket) + } + if err != nil { + return fmt.Errorf("stat bucket: %w", err) + } + + if policy == nil { + if err := xattr.Remove(bucket, policykey); err != nil { + return fmt.Errorf("remove policy: %w", err) + } + + return nil + } + + if err := xattr.Set(bucket, policykey, policy); err != nil { + return fmt.Errorf("set policy: %w", err) + } + + return nil +} + +func (p *Posix) GetBucketPolicy(ctx context.Context, bucket string) ([]byte, error) { + _, err := os.Stat(bucket) + if errors.Is(err, fs.ErrNotExist) { + return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket) + } + if err != nil { + return nil, fmt.Errorf("stat bucket: %w", err) + } + + policy, err := xattr.Get(bucket, policykey) + if isNoAttr(err) { + return []byte{}, nil + } + if err != nil { + return nil, fmt.Errorf("get bucket policy: %w", err) + } + + return policy, nil +} + +func (p *Posix) DeleteBucketPolicy(ctx context.Context, bucket string) error { + return p.PutBucketPolicy(ctx, bucket, nil) +} + func (p *Posix) ChangeBucketOwner(ctx context.Context, bucket, newOwner string) error { _, err := os.Stat(bucket) if errors.Is(err, fs.ErrNotExist) { diff --git a/s3api/controllers/backend_moq_test.go b/s3api/controllers/backend_moq_test.go index 60fbc8c9..d8b97cb0 100644 --- a/s3api/controllers/backend_moq_test.go +++ b/s3api/controllers/backend_moq_test.go @@ -44,6 +44,9 @@ var _ backend.Backend = &BackendMock{} // DeleteBucketFunc: func(contextMoqParam context.Context, deleteBucketInput *s3.DeleteBucketInput) error { // panic("mock out the DeleteBucket method") // }, +// DeleteBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) error { +// panic("mock out the DeleteBucketPolicy method") +// }, // DeleteBucketTaggingFunc: func(contextMoqParam context.Context, bucket string) error { // panic("mock out the DeleteBucketTagging method") // }, @@ -53,7 +56,7 @@ var _ backend.Backend = &BackendMock{} // DeleteObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string) error { // panic("mock out the DeleteObjectTagging method") // }, -// DeleteObjectsFunc: func(contextMoqParam context.Context, deleteObjectsInput *s3.DeleteObjectsInput) (s3response.DeleteObjectsResult, error) { +// DeleteObjectsFunc: func(contextMoqParam context.Context, deleteObjectsInput *s3.DeleteObjectsInput) (s3response.DeleteResult, error) { // panic("mock out the DeleteObjects method") // }, // GetBucketAclFunc: func(contextMoqParam context.Context, getBucketAclInput *s3.GetBucketAclInput) ([]byte, error) { @@ -174,6 +177,9 @@ type BackendMock struct { // DeleteBucketFunc mocks the DeleteBucket method. DeleteBucketFunc func(contextMoqParam context.Context, deleteBucketInput *s3.DeleteBucketInput) error + // DeleteBucketPolicyFunc mocks the DeleteBucketPolicy method. + DeleteBucketPolicyFunc func(contextMoqParam context.Context, bucket string) error + // DeleteBucketTaggingFunc mocks the DeleteBucketTagging method. DeleteBucketTaggingFunc func(contextMoqParam context.Context, bucket string) error @@ -331,6 +337,13 @@ type BackendMock struct { // DeleteBucketInput is the deleteBucketInput argument value. DeleteBucketInput *s3.DeleteBucketInput } + // DeleteBucketPolicy holds details about calls to the DeleteBucketPolicy method. + DeleteBucketPolicy []struct { + // ContextMoqParam is the contextMoqParam argument value. + ContextMoqParam context.Context + // Bucket is the bucket argument value. + Bucket string + } // DeleteBucketTagging holds details about calls to the DeleteBucketTagging method. DeleteBucketTagging []struct { // ContextMoqParam is the contextMoqParam argument value. @@ -585,6 +598,7 @@ type BackendMock struct { lockCreateBucket sync.RWMutex lockCreateMultipartUpload sync.RWMutex lockDeleteBucket sync.RWMutex + lockDeleteBucketPolicy sync.RWMutex lockDeleteBucketTagging sync.RWMutex lockDeleteObject sync.RWMutex lockDeleteObjectTagging sync.RWMutex @@ -881,6 +895,42 @@ func (mock *BackendMock) DeleteBucketCalls() []struct { return calls } +// DeleteBucketPolicy calls DeleteBucketPolicyFunc. +func (mock *BackendMock) DeleteBucketPolicy(contextMoqParam context.Context, bucket string) error { + if mock.DeleteBucketPolicyFunc == nil { + panic("BackendMock.DeleteBucketPolicyFunc: method is nil but Backend.DeleteBucketPolicy was just called") + } + callInfo := struct { + ContextMoqParam context.Context + Bucket string + }{ + ContextMoqParam: contextMoqParam, + Bucket: bucket, + } + mock.lockDeleteBucketPolicy.Lock() + mock.calls.DeleteBucketPolicy = append(mock.calls.DeleteBucketPolicy, callInfo) + mock.lockDeleteBucketPolicy.Unlock() + return mock.DeleteBucketPolicyFunc(contextMoqParam, bucket) +} + +// DeleteBucketPolicyCalls gets all the calls that were made to DeleteBucketPolicy. +// Check the length with: +// +// len(mockedBackend.DeleteBucketPolicyCalls()) +func (mock *BackendMock) DeleteBucketPolicyCalls() []struct { + ContextMoqParam context.Context + Bucket string +} { + var calls []struct { + ContextMoqParam context.Context + Bucket string + } + mock.lockDeleteBucketPolicy.RLock() + calls = mock.calls.DeleteBucketPolicy + mock.lockDeleteBucketPolicy.RUnlock() + return calls +} + // DeleteBucketTagging calls DeleteBucketTaggingFunc. func (mock *BackendMock) DeleteBucketTagging(contextMoqParam context.Context, bucket string) error { if mock.DeleteBucketTaggingFunc == nil { diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index 7332c2c4..4a55b30b 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -708,6 +708,17 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { }) } + err = auth.ValidatePolicyDocument(ctx.Body(), bucket, c.iam) + if err != nil { + return SendResponse(ctx, err, + &MetaOpts{ + Logger: c.logger, + Action: "PutBucketPolicy", + BucketOwner: parsedAcl.Owner, + }, + ) + } + err = c.be.PutBucketPolicy(ctx.Context(), bucket, ctx.Body()) return SendResponse(ctx, err, &MetaOpts{ @@ -1298,6 +1309,27 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error { }) } + if ctx.Request().URI().QueryArgs().Has("policy") { + err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot) + if err != nil { + return SendResponse(ctx, err, + &MetaOpts{ + Logger: c.logger, + Action: "DeleteBucketPolicy", + BucketOwner: parsedAcl.Owner, + }) + } + + err = c.be.DeleteBucketPolicy(ctx.Context(), bucket) + return SendResponse(ctx, err, + &MetaOpts{ + Logger: c.logger, + Action: "DeleteBucketPolicy", + BucketOwner: parsedAcl.Owner, + Status: http.StatusNoContent, + }) + } + err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot) if err != nil { return SendResponse(ctx, err,