feat: implementes GetBucketPolicyStatus s3 action

Closes #1454

Adds the implementation of [S3 GetBucketPolicyStatus action](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicyStatus.html). The implementation goes to front-end. Front-End loads the bucket policy and checks if it grants public access to all users.

A bucket policy document `is public` only when `Principal` contains `*`(all users): only when it grants access to `ALL` users.
This commit is contained in:
niksis02
2025-08-25 21:48:06 +04:00
parent 9992e341da
commit d90944afd1
9 changed files with 331 additions and 16 deletions
+54
View File
@@ -238,6 +238,60 @@ func (c S3ApiController) GetBucketPolicy(ctx *fiber.Ctx) (*Response, error) {
}, err
}
func (c S3ApiController) GetBucketPolicyStatus(ctx *fiber.Ctx) (*Response, error) {
bucket := ctx.Params("bucket")
acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx)
parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
Readonly: c.readonly,
Acl: parsedAcl,
AclPermission: auth.PermissionRead,
IsRoot: isRoot,
Acc: acct,
Bucket: bucket,
Action: auth.GetBucketPolicyStatusAction,
IsBucketPublic: isPublicBucket,
})
if err != nil {
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, err
}
policyRaw, err := c.be.GetBucketPolicy(ctx.Context(), bucket)
if err != nil {
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, err
}
policy, err := auth.ParsePolicyDocument(policyRaw)
if err != nil {
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, err
}
isPublic := policy.IsPublic()
return &Response{
Data: types.PolicyStatus{
IsPublic: &isPublic,
},
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, nil
}
func (c S3ApiController) ListObjectVersions(ctx *fiber.Ctx) (*Response, error) {
// url values
bucket := ctx.Params("bucket")
+116
View File
@@ -505,6 +505,122 @@ func TestS3ApiController_GetBucketPolicy(t *testing.T) {
}
}
func TestS3ApiController_GetBucketPolicyStatus(t *testing.T) {
mockResp := `
{
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::test/*"
}
]
}
`
isPublic := true
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
beRes: []byte{},
beErr: s3err.GetAPIError(s3err.ErrAccessDenied),
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "fails to get bucket policy",
input: testInput{
locals: defaultLocals,
beRes: []byte{},
beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
},
{
name: "fails to parse bucket policy",
input: testInput{
locals: defaultLocals,
beRes: []byte("invalid policy"),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.APIError{
Code: "MalformedPolicy",
Description: "This policy contains invalid Json",
HTTPStatusCode: http.StatusBadRequest,
},
},
},
{
name: "successful response",
input: testInput{
locals: defaultLocals,
beRes: []byte(mockResp),
},
output: testOutput{
response: &Response{
Data: types.PolicyStatus{
IsPublic: &isPublic,
},
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return tt.input.beRes.([]byte), tt.input.beErr
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.GetBucketPolicyStatus,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
body: tt.input.body,
})
})
}
}
func TestS3ApiController_ListObjectVersions(t *testing.T) {
listVersionsResult := s3response.ListVersionsResult{
Name: utils.GetStringPtr("name"),
+14
View File
@@ -737,6 +737,20 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
middlewares.ApplyBucketCORS(be),
middlewares.ParseAcl(be),
))
bucketRouter.Get("",
middlewares.MatchQueryArgs("policyStatus"),
controllers.ProcessHandlers(
ctrl.GetBucketPolicyStatus,
metrics.ActionGetBucketPolicyStatus,
services,
middlewares.BucketObjectNameValidator(),
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetBucketPolicyStatus, auth.GetBucketPolicyStatusAction, auth.PermissionRead),
middlewares.VerifyPresignedV4Signature(root, iam, region, debug),
middlewares.VerifyV4Signature(root, iam, region, debug),
middlewares.VerifyMD5Body(),
middlewares.ApplyBucketCORS(be),
middlewares.ParseAcl(be),
))
bucketRouter.Get("",
middlewares.MatchQueryArgs("analytics", "id"),
controllers.ProcessHandlers(