mirror of
https://github.com/versity/versitygw.git
synced 2026-08-17 04:36:19 +00:00
Merge pull request #1875 from versity/sis/disable-acl-option
feat: configuration option to disable ACLs
This commit is contained in:
@@ -79,6 +79,7 @@ type AccessOptions struct {
|
||||
Action Action
|
||||
Readonly bool
|
||||
IsPublicRequest bool
|
||||
DisableACL bool
|
||||
}
|
||||
|
||||
func VerifyAccess(ctx context.Context, be backend.Backend, opts AccessOptions) error {
|
||||
@@ -107,7 +108,7 @@ func VerifyAccess(ctx context.Context, be backend.Backend, opts AccessOptions) e
|
||||
return VerifyBucketPolicy(policy, opts.Acc.Access, opts.Bucket, opts.Object, opts.Action)
|
||||
}
|
||||
|
||||
if err := verifyACL(opts.Acl, opts.Acc.Access, opts.AclPermission); err != nil {
|
||||
if err := verifyACL(opts.Acl, opts.Acc.Access, opts.AclPermission, opts.DisableACL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+11
-1
@@ -414,7 +414,17 @@ func splitUnique(s, divider string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func verifyACL(acl ACL, access string, permission Permission) error {
|
||||
func verifyACL(acl ACL, access string, permission Permission, disableACL bool) error {
|
||||
if disableACL {
|
||||
// only the bucket owner should have access to the bucket
|
||||
// as bucket ACLs are disabled and no grantee check is necessary
|
||||
if acl.Owner != access {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
grantee := Grantee{
|
||||
Access: access,
|
||||
Permission: permission,
|
||||
|
||||
@@ -100,6 +100,7 @@ var (
|
||||
webuiNoTLS bool
|
||||
webuiGateways []string
|
||||
webuiAdminGateways []string
|
||||
disableACLs bool
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -341,6 +342,13 @@ func initFlags() []cli.Flag {
|
||||
Destination: &virtualDomain,
|
||||
Aliases: []string{"vd"},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "disable-acl",
|
||||
Usage: "disables gateway ACLs, by ignoring all ACL headers",
|
||||
EnvVars: []string{"VGW_DISABLE_ACL"},
|
||||
Destination: &disableACLs,
|
||||
Aliases: []string{"noacl"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "access-log",
|
||||
Usage: "enable server access logging to specified file",
|
||||
@@ -833,6 +841,9 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
if keepAlive {
|
||||
opts = append(opts, s3api.WithKeepAlive())
|
||||
}
|
||||
if disableACLs {
|
||||
opts = append(opts, s3api.WithDisableACL())
|
||||
}
|
||||
if debug {
|
||||
debuglogger.SetDebugEnabled()
|
||||
}
|
||||
|
||||
@@ -152,6 +152,11 @@ func initTestCommands() []*cli.Command {
|
||||
Usage: "Tests gateway access control with bucket ACLs and Policies",
|
||||
Action: getAction(integration.TestAccessControl),
|
||||
},
|
||||
{
|
||||
Name: "noacl",
|
||||
Usage: "Tests gateway in ACL-disabled mode",
|
||||
Action: getAction(integration.TestNoAclMode),
|
||||
},
|
||||
{
|
||||
Name: "bench",
|
||||
Usage: "Runs download/upload performance test on the gateway",
|
||||
|
||||
@@ -111,6 +111,13 @@ ROOT_SECRET_ACCESS_KEY=
|
||||
# operations will be allowed.
|
||||
#VGW_READ_ONLY=false
|
||||
|
||||
# Disable ACL support at the gateway level. All ACL headers on requests are
|
||||
# ignored, and no access control is enforced using bucket ACLs.
|
||||
# Prefer using bucket policies instead of ACLs for access management.
|
||||
# PutBucketAcl returns an immediate AccessControlListNotSupported error.
|
||||
# GetBucketAcl returns a successful response containing the default bucket ACL.
|
||||
#VGW_DISABLE_ACL=false
|
||||
|
||||
# The VGW_VIRTUAL_DOMAIN option enables the virtual host style bucket
|
||||
# addressing. The path style addressing is the default, and remains enabled
|
||||
# even when virtual host style is enabled. The VGW_VIRTUAL_DOMAIN option
|
||||
|
||||
+77
-6
@@ -5,8 +5,15 @@ rm -rf /tmp/gw
|
||||
mkdir /tmp/gw
|
||||
rm -rf /tmp/covdata
|
||||
mkdir /tmp/covdata
|
||||
rm -rf /tmp/versioing.covdata
|
||||
rm -rf /tmp/https.covdata
|
||||
mkdir /tmp/https.covdata
|
||||
rm -rf /tmp/versioning.covdata
|
||||
mkdir /tmp/versioning.covdata
|
||||
rm -rf /tmp/versioning.https.covdata
|
||||
mkdir /tmp/versioning.https.covdata
|
||||
rm -rf /tmp/noacl.covdata
|
||||
mkdir /tmp/noacl.covdata
|
||||
|
||||
rm -rf /tmp/versioningdir
|
||||
mkdir /tmp/versioningdir
|
||||
|
||||
@@ -153,10 +160,74 @@ fi
|
||||
# kill off server
|
||||
kill $GW_VS_HTTPS_PID
|
||||
|
||||
ECHO "Running No ACL integration tests"
|
||||
# run server in background versioning-enabled
|
||||
# port: 7073
|
||||
GOCOVERDIR=/tmp/noacl.covdata ./versitygw -p :7074 -a user -s pass -noacl --iam-dir /tmp/gw posix /tmp/gw &
|
||||
GW_NO_ACL_PID=$!
|
||||
|
||||
# wait a second for server to start up
|
||||
sleep 1
|
||||
|
||||
# check if noacl gateway process is still running
|
||||
if ! kill -0 $GW_NO_ACL_PID; then
|
||||
echo "noacl server no longer running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! ./versitygw test --allow-insecure -a user -s pass -e http://127.0.0.1:7074 noacl; then
|
||||
echo "No ACL integration tests failed"
|
||||
kill $GW_NO_ACL_PID
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# kill off server
|
||||
kill $GW_NO_ACL_PID
|
||||
|
||||
exit 0
|
||||
|
||||
# if the above binary was built with -cover enabled (make testbin),
|
||||
# then the following can be used for code coverage reports:
|
||||
# go tool covdata percent -i=/tmp/covdata
|
||||
# go tool covdata textfmt -i=/tmp/covdata -o profile.txt
|
||||
# go tool cover -html=profile.txt
|
||||
# -----------------------------------------------------------------------------
|
||||
# Coverage Reports (Go 1.20+ Runtime Coverage)
|
||||
#
|
||||
# The servers above were started with GOCOVERDIR=<dir>, which causes Go to write
|
||||
# raw coverage artifacts into those directories (covmeta + covcounters.* files).
|
||||
# These raw files must be processed with "go tool covdata" to generate
|
||||
# human-readable coverage reports.
|
||||
#
|
||||
# You may generate *per-environment* coverage or a *merged full-suite* report.
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
# 1) INDIVIDUAL COVERAGE REPORTS
|
||||
#
|
||||
# Example for a single environment (e.g. /tmp/covdata):
|
||||
#
|
||||
# go tool covdata percent -i=/tmp/covdata
|
||||
# go tool covdata textfmt -i=/tmp/covdata -o /tmp/profile.txt
|
||||
# go tool cover -html=/tmp/profile.txt
|
||||
#
|
||||
# Repeat using:
|
||||
# /tmp/covdata
|
||||
# /tmp/https.covdata
|
||||
# /tmp/versioning.covdata
|
||||
# /tmp/versioning.https.covdata
|
||||
# /tmp/noacl.covdata
|
||||
#
|
||||
# This gives you coverage metrics isolated per test suite / server mode.
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2) MERGED COVERAGE REPORT (RECOMMENDED)
|
||||
#
|
||||
# If you want a unified report combining all environments:
|
||||
#
|
||||
# go tool covdata merge \
|
||||
# -i=/tmp/covdata,/tmp/https.covdata,/tmp/versioning.covdata,/tmp/versioning.https.covdata,/tmp/noacl.covdata \
|
||||
# -o /tmp/allcovdata
|
||||
#
|
||||
# go tool covdata percent -i=/tmp/allcovdata
|
||||
# go tool covdata textfmt -i=/tmp/allcovdata -o /tmp/all_profile.txt
|
||||
# go tool cover -html=/tmp/all_profile.txt
|
||||
#
|
||||
# This produces the full aggregate coverage across all HTTP/HTTPS,
|
||||
# versioning-enabled, non-versioning, and no-ACL test runs.
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -681,7 +681,7 @@ func TestAdminController_CreateBucket(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
s3api := New(be, iam, nil, nil, nil, false, "")
|
||||
s3api := New(be, iam, nil, nil, nil, false, false, "")
|
||||
|
||||
ctrl := AdminController{
|
||||
iam: iam,
|
||||
|
||||
@@ -39,6 +39,7 @@ type S3ApiController struct {
|
||||
evSender s3event.S3EventSender
|
||||
mm metrics.Manager
|
||||
readonly bool
|
||||
disableACL bool
|
||||
virtualDomain string
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ var (
|
||||
xmlhdr = []byte(`<?xml version="1.0" encoding="UTF-8"?>` + "\n")
|
||||
)
|
||||
|
||||
func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs s3event.S3EventSender, mm metrics.Manager, readonly bool, virtualDomain string) S3ApiController {
|
||||
func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs s3event.S3EventSender, mm metrics.Manager, readonly, disableACL bool, virtualDomain string) S3ApiController {
|
||||
return S3ApiController{
|
||||
be: be,
|
||||
iam: iam,
|
||||
@@ -67,10 +68,19 @@ func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs
|
||||
evSender: evs,
|
||||
readonly: readonly,
|
||||
mm: mm,
|
||||
disableACL: disableACL,
|
||||
virtualDomain: virtualDomain,
|
||||
}
|
||||
}
|
||||
|
||||
func (c S3ApiController) getAclHeaderValue(ctx *fiber.Ctx, key string, defaultValues ...string) string {
|
||||
if c.disableACL {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ctx.Get(key, defaultValues...)
|
||||
}
|
||||
|
||||
// Returns MethodNotAllowed for unmatched routes
|
||||
func (c S3ApiController) HandleErrorRoute(err error) Controller {
|
||||
return func(ctx *fiber.Ctx) (*Response, error) {
|
||||
|
||||
@@ -39,6 +39,7 @@ func (c S3ApiController) DeleteBucketTagging(ctx *fiber.Ctx) (*Response, error)
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketTaggingAction,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -72,6 +73,7 @@ func (c S3ApiController) DeleteBucketOwnershipControls(ctx *fiber.Ctx) (*Respons
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketOwnershipControlsAction,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -105,6 +107,7 @@ func (c S3ApiController) DeleteBucketPolicy(ctx *fiber.Ctx) (*Response, error) {
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Action: auth.DeleteBucketPolicyAction,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -140,6 +143,7 @@ func (c S3ApiController) DeleteBucketCors(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketCorsAction,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -175,6 +179,7 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.DeleteBucketAction,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
|
||||
@@ -41,6 +41,7 @@ func (c S3ApiController) GetBucketTagging(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketTaggingAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -93,6 +94,7 @@ func (c S3ApiController) GetBucketOwnershipControls(ctx *fiber.Ctx) (*Response,
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketOwnershipControlsAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -133,6 +135,7 @@ func (c S3ApiController) GetBucketVersioning(ctx *fiber.Ctx) (*Response, error)
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketVersioningAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -175,6 +178,7 @@ func (c S3ApiController) GetBucketCors(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketCorsAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -218,6 +222,7 @@ func (c S3ApiController) GetBucketPolicy(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketPolicyAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -252,6 +257,7 @@ func (c S3ApiController) GetBucketPolicyStatus(ctx *fiber.Ctx) (*Response, error
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketPolicyStatusAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -313,6 +319,7 @@ func (c S3ApiController) ListObjectVersions(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.ListBucketVersionsAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -366,6 +373,7 @@ func (c S3ApiController) GetObjectLockConfiguration(ctx *fiber.Ctx) (*Response,
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketObjectLockConfigurationAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -411,6 +419,7 @@ func (c S3ApiController) GetBucketAcl(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketAclAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -462,6 +471,7 @@ func (c S3ApiController) ListMultipartUploads(ctx *fiber.Ctx) (*Response, error)
|
||||
Bucket: bucket,
|
||||
Action: auth.ListBucketMultipartUploadsAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -519,6 +529,7 @@ func (c S3ApiController) ListObjectsV2(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.ListBucketAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -576,6 +587,7 @@ func (c S3ApiController) ListObjects(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.ListBucketAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -627,6 +639,7 @@ func (c S3ApiController) GetBucketLocation(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketLocationAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
|
||||
@@ -42,6 +42,7 @@ func (c S3ApiController) HeadBucket(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.ListBucketAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
|
||||
@@ -47,6 +47,7 @@ func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.DeleteObjectAction,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
|
||||
@@ -47,6 +47,7 @@ func (c S3ApiController) PutBucketTagging(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketTaggingAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -88,6 +89,7 @@ func (c S3ApiController) PutBucketOwnershipControls(ctx *fiber.Ctx) (*Response,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketOwnershipControlsAction,
|
||||
DisableACL: c.disableACL,
|
||||
}); err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
@@ -143,6 +145,7 @@ func (c S3ApiController) PutBucketVersioning(ctx *fiber.Ctx) (*Response, error)
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketVersioningAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -197,6 +200,7 @@ func (c S3ApiController) PutObjectLockConfiguration(ctx *fiber.Ctx) (*Response,
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketObjectLockConfigurationAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
}); err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
@@ -238,6 +242,7 @@ func (c S3ApiController) PutBucketCors(ctx *fiber.Ctx) (*Response, error) {
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketCorsAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -292,6 +297,7 @@ func (c S3ApiController) PutBucketPolicy(ctx *fiber.Ctx) (*Response, error) {
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketPolicyAction,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -344,6 +350,7 @@ func (c S3ApiController) PutBucketAcl(ctx *fiber.Ctx) (*Response, error) {
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketAclAction,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -353,6 +360,15 @@ func (c S3ApiController) PutBucketAcl(ctx *fiber.Ctx) (*Response, error) {
|
||||
}, err
|
||||
}
|
||||
|
||||
if c.disableACL {
|
||||
debuglogger.Logf("PutBucketAcl is not available, as ACLs are disabled at gateway level")
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
},
|
||||
}, s3err.GetAPIError(s3err.ErrACLsDisabled)
|
||||
}
|
||||
|
||||
err = auth.ValidateCannedACL(acl)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -480,12 +496,12 @@ func (c S3ApiController) PutBucketAcl(ctx *fiber.Ctx) (*Response, error) {
|
||||
|
||||
func (c S3ApiController) CreateBucket(ctx *fiber.Ctx) (*Response, error) {
|
||||
bucket := ctx.Params("bucket")
|
||||
acl := types.BucketCannedACL(ctx.Get("X-Amz-Acl"))
|
||||
grantFullControl := ctx.Get("X-Amz-Grant-Full-Control")
|
||||
grantRead := ctx.Get("X-Amz-Grant-Read")
|
||||
grantReadACP := ctx.Get("X-Amz-Grant-Read-Acp")
|
||||
grantWrite := ctx.Get("X-Amz-Grant-Write")
|
||||
grantWriteACP := ctx.Get("X-Amz-Grant-Write-Acp")
|
||||
acl := types.BucketCannedACL(c.getAclHeaderValue(ctx, "X-Amz-Acl"))
|
||||
grantFullControl := c.getAclHeaderValue(ctx, "X-Amz-Grant-Full-Control")
|
||||
grantRead := c.getAclHeaderValue(ctx, "X-Amz-Grant-Read")
|
||||
grantReadACP := c.getAclHeaderValue(ctx, "X-Amz-Grant-Read-Acp")
|
||||
grantWrite := c.getAclHeaderValue(ctx, "X-Amz-Grant-Write")
|
||||
grantWriteACP := c.getAclHeaderValue(ctx, "X-Amz-Grant-Write-Acp")
|
||||
lockEnabled := strings.EqualFold(ctx.Get("X-Amz-Bucket-Object-Lock-Enabled"), "true")
|
||||
grants := grantFullControl + grantRead + grantReadACP + grantWrite + grantWriteACP
|
||||
objectOwnership := types.ObjectOwnership(ctx.Get("X-Amz-Object-Ownership"))
|
||||
|
||||
@@ -52,6 +52,7 @@ func (c S3ApiController) DeleteObjectTagging(ctx *fiber.Ctx) (*Response, error)
|
||||
Object: key,
|
||||
Action: action,
|
||||
IsPublicRequest: isBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -104,6 +105,7 @@ func (c S3ApiController) AbortMultipartUpload(ctx *fiber.Ctx) (*Response, error)
|
||||
Object: key,
|
||||
Action: auth.AbortMultipartUploadAction,
|
||||
IsPublicRequest: isBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -158,6 +160,7 @@ func (c S3ApiController) DeleteObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: action,
|
||||
IsPublicRequest: isBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
|
||||
@@ -55,6 +55,7 @@ func (c S3ApiController) GetObjectTagging(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: action,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -121,6 +122,7 @@ func (c S3ApiController) GetObjectRetention(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.GetObjectRetentionAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -177,6 +179,7 @@ func (c S3ApiController) GetObjectLegalHold(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.GetObjectLegalHoldAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -223,6 +226,7 @@ func (c S3ApiController) GetObjectAcl(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.GetObjectAclAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -265,6 +269,7 @@ func (c S3ApiController) ListParts(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.ListMultipartUploadPartsAction,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -336,6 +341,7 @@ func (c S3ApiController) GetObjectAttributes(ctx *fiber.Ctx) (*Response, error)
|
||||
Object: key,
|
||||
Action: action,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -469,6 +475,7 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: action,
|
||||
IsPublicRequest: isPublicBucketRequest,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
|
||||
@@ -57,6 +57,7 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: action,
|
||||
IsPublicRequest: isPublicBucket,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
|
||||
@@ -50,6 +50,7 @@ func (c S3ApiController) RestoreObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.RestoreObjectAction,
|
||||
IsPublicRequest: isBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -101,6 +102,7 @@ func (c S3ApiController) SelectObjectContent(ctx *fiber.Ctx) (*Response, error)
|
||||
Object: key,
|
||||
Action: auth.GetObjectAction,
|
||||
IsPublicRequest: isBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -158,7 +160,7 @@ func (c S3ApiController) CreateMultipartUpload(ctx *fiber.Ctx) (*Response, error
|
||||
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
|
||||
parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
|
||||
|
||||
err := utils.ValidateNoACLHeaders(ctx)
|
||||
err := utils.ValidateNoACLHeaders(ctx, c.disableACL)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
@@ -177,6 +179,7 @@ func (c S3ApiController) CreateMultipartUpload(ctx *fiber.Ctx) (*Response, error
|
||||
Bucket: bucket,
|
||||
Object: key,
|
||||
Action: auth.PutObjectAction,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -261,6 +264,7 @@ func (c S3ApiController) CompleteMultipartUpload(ctx *fiber.Ctx) (*Response, err
|
||||
Object: key,
|
||||
Action: auth.PutObjectAction,
|
||||
IsPublicRequest: isBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
|
||||
@@ -57,6 +57,7 @@ func (c S3ApiController) PutObjectTagging(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: action,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -116,6 +117,7 @@ func (c S3ApiController) PutObjectRetention(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.PutObjectRetentionAction,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -191,6 +193,7 @@ func (c S3ApiController) PutObjectLegalHold(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.PutObjectLegalHoldAction,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -269,6 +272,7 @@ func (c S3ApiController) UploadPart(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.PutObjectAction,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -383,6 +387,7 @@ func (c S3ApiController) UploadPartCopy(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.PutObjectAction,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
@@ -510,7 +515,7 @@ func (c S3ApiController) CopyObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
|
||||
parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
|
||||
|
||||
err := utils.ValidateNoACLHeaders(ctx)
|
||||
err := utils.ValidateNoACLHeaders(ctx, c.disableACL)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
@@ -668,7 +673,7 @@ func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
|
||||
IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
|
||||
|
||||
err := utils.ValidateNoACLHeaders(ctx)
|
||||
err := utils.ValidateNoACLHeaders(ctx, c.disableACL)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
MetaOpts: &MetaOptions{
|
||||
@@ -703,6 +708,7 @@ func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
Object: key,
|
||||
Action: auth.PutObjectAction,
|
||||
IsPublicRequest: IsBucketPublic,
|
||||
DisableACL: c.disableACL,
|
||||
})
|
||||
if err != nil {
|
||||
return &Response{
|
||||
|
||||
+498
-480
File diff suppressed because it is too large
Load Diff
+49
-99
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/auth"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3api/middlewares"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
@@ -60,20 +59,13 @@ func TestS3ApiRouter_ListBuckets_DefaultCORSAllowOrigin(t *testing.T) {
|
||||
origin := "https://example.com"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backend.BackendUnsupported{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
(&S3ApiRouter{
|
||||
app: app,
|
||||
be: backend.BackendUnsupported{},
|
||||
iam: &auth.IAMServiceInternal{},
|
||||
region: "us-east-1",
|
||||
corsAllowOrigin: origin,
|
||||
}).Init()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||
if err != nil {
|
||||
@@ -97,20 +89,13 @@ func TestS3ApiRouter_ListBuckets_OptionsPreflight_DefaultCORS(t *testing.T) {
|
||||
origin := "https://example.com"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backend.BackendUnsupported{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
(&S3ApiRouter{
|
||||
app: app,
|
||||
be: backend.BackendUnsupported{},
|
||||
iam: &auth.IAMServiceInternal{},
|
||||
region: "us-east-1",
|
||||
corsAllowOrigin: origin,
|
||||
}).Init()
|
||||
|
||||
req, err := http.NewRequest(http.MethodOptions, "/", nil)
|
||||
if err != nil {
|
||||
@@ -137,20 +122,13 @@ func TestS3ApiRouter_PutBucketTagging_ErrorStillIncludesFallbackCORS(t *testing.
|
||||
origin := "http://127.0.0.1:9090"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backendWithCorsOnly{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
(&S3ApiRouter{
|
||||
app: app,
|
||||
be: backendWithCorsOnly{},
|
||||
iam: &auth.IAMServiceInternal{},
|
||||
region: "us-east-1",
|
||||
corsAllowOrigin: origin,
|
||||
}).Init()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, "/testing?tagging", nil)
|
||||
if err != nil {
|
||||
@@ -172,20 +150,13 @@ func TestS3ApiRouter_PutObjectTagging_ErrorStillIncludesFallbackCORS(t *testing.
|
||||
origin := "http://127.0.0.1:9090"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backendWithCorsOnly{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
(&S3ApiRouter{
|
||||
app: app,
|
||||
be: backendWithCorsOnly{},
|
||||
iam: &auth.IAMServiceInternal{},
|
||||
region: "us-east-1",
|
||||
corsAllowOrigin: origin,
|
||||
}).Init()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, "/testing/myobj?tagging", nil)
|
||||
if err != nil {
|
||||
@@ -207,20 +178,13 @@ func TestS3ApiRouter_CopyObject_ErrorStillIncludesFallbackCORS(t *testing.T) {
|
||||
origin := "http://127.0.0.1:9090"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backendWithCorsOnly{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
(&S3ApiRouter{
|
||||
app: app,
|
||||
be: backendWithCorsOnly{},
|
||||
iam: &auth.IAMServiceInternal{},
|
||||
region: "us-east-1",
|
||||
corsAllowOrigin: origin,
|
||||
}).Init()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, "/testing/myobj", nil)
|
||||
if err != nil {
|
||||
@@ -243,20 +207,13 @@ func TestS3ApiRouter_PutObject_ErrorStillIncludesFallbackCORS(t *testing.T) {
|
||||
origin := "http://127.0.0.1:9090"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backendWithCorsOnly{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
(&S3ApiRouter{
|
||||
app: app,
|
||||
be: backendWithCorsOnly{},
|
||||
iam: &auth.IAMServiceInternal{},
|
||||
region: "us-east-1",
|
||||
corsAllowOrigin: origin,
|
||||
}).Init()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, "/testing/myobj", nil)
|
||||
if err != nil {
|
||||
@@ -294,20 +251,13 @@ func TestS3ApiRouter_OptionsWithBucketCORS_NoDuplicateHeaders(t *testing.T) {
|
||||
</CORSConfiguration>`)
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backendWithBucketCors{corsConfig: corsConfig},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
fallbackOrigin,
|
||||
)
|
||||
(&S3ApiRouter{
|
||||
app: app,
|
||||
be: backendWithBucketCors{corsConfig: corsConfig},
|
||||
iam: &auth.IAMServiceInternal{},
|
||||
region: "us-east-1",
|
||||
corsAllowOrigin: fallbackOrigin,
|
||||
}).Init()
|
||||
|
||||
req, err := http.NewRequest(http.MethodOptions, "/xyz/upload/test.txt", nil)
|
||||
if err != nil {
|
||||
|
||||
+2
-10
@@ -20,24 +20,16 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/auth"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3api/middlewares"
|
||||
)
|
||||
|
||||
func TestS3ApiRouter_Init(t *testing.T) {
|
||||
type args struct {
|
||||
app *fiber.App
|
||||
be backend.Backend
|
||||
iam auth.IAMService
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
sa *S3ApiRouter
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "Initialize S3 api router",
|
||||
sa: &S3ApiRouter{},
|
||||
args: args{
|
||||
sa: &S3ApiRouter{
|
||||
app: fiber.New(),
|
||||
be: backend.BackendUnsupported{},
|
||||
iam: &auth.IAMServiceInternal{},
|
||||
@@ -46,7 +38,7 @@ func TestS3ApiRouter_Init(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.sa.Init(tt.args.app, tt.args.be, tt.args.iam, nil, nil, nil, nil, false, "us-east-1", "", middlewares.RootUserConfig{}, "")
|
||||
tt.sa.Init()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+32
-22
@@ -42,18 +42,15 @@ const (
|
||||
)
|
||||
|
||||
type S3ApiServer struct {
|
||||
Router *S3ApiRouter
|
||||
app *fiber.App
|
||||
backend backend.Backend
|
||||
CertStorage *utils.CertStorage
|
||||
quiet bool
|
||||
readonly bool
|
||||
keepAlive bool
|
||||
health string
|
||||
virtualDomain string
|
||||
corsAllowOrigin string
|
||||
maxConnections int
|
||||
maxRequests int
|
||||
Router *S3ApiRouter
|
||||
app *fiber.App
|
||||
backend backend.Backend
|
||||
CertStorage *utils.CertStorage
|
||||
quiet bool
|
||||
keepAlive bool
|
||||
health string
|
||||
maxConnections int
|
||||
maxRequests int
|
||||
}
|
||||
|
||||
func New(
|
||||
@@ -69,7 +66,16 @@ func New(
|
||||
) (*S3ApiServer, error) {
|
||||
server := &S3ApiServer{
|
||||
backend: be,
|
||||
Router: new(S3ApiRouter),
|
||||
Router: &S3ApiRouter{
|
||||
be: be,
|
||||
iam: iam,
|
||||
logger: l,
|
||||
aLogger: adminLogger,
|
||||
evs: evs,
|
||||
mm: mm,
|
||||
root: root,
|
||||
region: region,
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@@ -88,6 +94,7 @@ func New(
|
||||
})
|
||||
|
||||
server.app = app
|
||||
server.Router.app = app
|
||||
|
||||
// initialize the panic recovery middleware
|
||||
app.Use(recover.New(
|
||||
@@ -119,17 +126,12 @@ func New(
|
||||
// path unescapes the url
|
||||
app.Use(controllers.WrapMiddleware(middlewares.DecodeURL, l, mm))
|
||||
|
||||
// initialize host-style parser in virtual domain is specified
|
||||
if server.virtualDomain != "" {
|
||||
app.Use(middlewares.HostStyleParser(server.virtualDomain))
|
||||
}
|
||||
|
||||
// initialize the debug logger in debug mode
|
||||
if debuglogger.IsDebugEnabled() {
|
||||
app.Use(middlewares.DebugLogger())
|
||||
}
|
||||
|
||||
server.Router.Init(app, be, iam, l, adminLogger, evs, mm, server.readonly, region, server.virtualDomain, root, server.corsAllowOrigin)
|
||||
server.Router.Init()
|
||||
|
||||
return server, nil
|
||||
}
|
||||
@@ -158,12 +160,14 @@ func WithHealth(health string) Option {
|
||||
}
|
||||
|
||||
func WithReadOnly() Option {
|
||||
return func(s *S3ApiServer) { s.readonly = true }
|
||||
return func(s *S3ApiServer) { s.Router.readonly = true }
|
||||
}
|
||||
|
||||
// WithHostStyle enabled host-style bucket addressing on the server
|
||||
func WithHostStyle(virtualDomain string) Option {
|
||||
return func(s *S3ApiServer) { s.virtualDomain = virtualDomain }
|
||||
return func(s *S3ApiServer) {
|
||||
s.Router.virtualDomain = virtualDomain
|
||||
}
|
||||
}
|
||||
|
||||
// WithKeepAlive enables the server keep alive
|
||||
@@ -174,7 +178,7 @@ func WithKeepAlive() Option {
|
||||
// WithCORSAllowOrigin sets the default CORS Access-Control-Allow-Origin value.
|
||||
// This is applied when no bucket CORS configuration exists, and for admin APIs.
|
||||
func WithCORSAllowOrigin(origin string) Option {
|
||||
return func(s *S3ApiServer) { s.corsAllowOrigin = origin }
|
||||
return func(s *S3ApiServer) { s.Router.corsAllowOrigin = origin }
|
||||
}
|
||||
|
||||
// WithConcurrencyLimiter sets the server's maximum connection limit
|
||||
@@ -186,6 +190,12 @@ func WithConcurrencyLimiter(maxConnections, maxRequests int) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisableACL disables the s3 api server ACLs, by ignoring all
|
||||
// bucket/object ACL headers
|
||||
func WithDisableACL() Option {
|
||||
return func(s *S3ApiServer) { s.Router.disableACL = true }
|
||||
}
|
||||
|
||||
// ServeMultiPort creates listeners for multiple port specifications and serves
|
||||
// on all of them simultaneously. This supports listening on multiple ports and/or
|
||||
// addresses (e.g., [":7070", "localhost:8080", "0.0.0.0:9090"]).
|
||||
|
||||
@@ -1011,7 +1011,11 @@ func NewTLSListener(network string, address string, getCertificateFunc func(*tls
|
||||
// since ACL operations are not supported on objects, the presence of any ACL headers
|
||||
// results in a NotImplemented error. It returns nil only when all ACL headers
|
||||
// are absent.
|
||||
func ValidateNoACLHeaders(ctx *fiber.Ctx) error {
|
||||
func ValidateNoACLHeaders(ctx *fiber.Ctx, disableACL bool) error {
|
||||
if disableACL {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, header := range []string{
|
||||
"x-amz-acl",
|
||||
"x-amz-grant-full-control",
|
||||
|
||||
@@ -193,6 +193,7 @@ const (
|
||||
ErrDirectoryNotEmpty
|
||||
ErrQuotaExceeded
|
||||
ErrVersioningNotConfigured
|
||||
ErrACLsDisabled
|
||||
|
||||
// Admin api errors
|
||||
ErrAdminAccessDenied
|
||||
@@ -867,6 +868,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "Versioning has not been configured for the gateway.",
|
||||
HTTPStatusCode: http.StatusNotImplemented,
|
||||
},
|
||||
ErrACLsDisabled: {
|
||||
Code: "AccessControlListNotSupported",
|
||||
Description: "Access control lists are disabled at the gateway level",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
// Admin api errors
|
||||
ErrAdminAccessDenied: {
|
||||
|
||||
@@ -1171,6 +1171,16 @@ func TestSignedStreaminPayloadTrailer(ts *TestState) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoAclMode(ts *TestState) {
|
||||
ts.Run(NoAclMode_CreateBucket_with_acl)
|
||||
ts.Run(NoAclMode_PutObject_with_acl)
|
||||
ts.Run(NoAclMode_CopyObject_with_acl)
|
||||
ts.Run(NoAclMode_multipart_upload_with_acl)
|
||||
ts.Run(NoAclMode_PutBucketAcl)
|
||||
ts.Run(NoAclMode_PutObjectAcl_not_implemented)
|
||||
ts.Run(NoAclMode_GetObjectAcl_not_implemented)
|
||||
}
|
||||
|
||||
type IntTest func(s3 *S3Conf) error
|
||||
|
||||
type IntTests map[string]IntTest
|
||||
@@ -1867,5 +1877,12 @@ func GetIntTests() IntTests {
|
||||
"SignedStreamingPayloadTrailer_invalid_checksum": SignedStreamingPayloadTrailer_invalid_checksum,
|
||||
"SignedStreamingPayloadTrailer_bad_digest": SignedStreamingPayloadTrailer_bad_digest,
|
||||
"SignedStreamingPayloadTrailer_success": SignedStreamingPayloadTrailer_success,
|
||||
"NoAclMode_CreateBucket_with_acl": NoAclMode_CreateBucket_with_acl,
|
||||
"NoAclMode_PutObject_with_acl": NoAclMode_PutObject_with_acl,
|
||||
"NoAclMode_CopyObject_with_acl": NoAclMode_CopyObject_with_acl,
|
||||
"NoAclMode_multipart_upload_with_acl": NoAclMode_multipart_upload_with_acl,
|
||||
"NoAclMode_PutBucketAcl": NoAclMode_PutBucketAcl,
|
||||
"NoAclMode_PutObjectAcl_not_implemented": NoAclMode_PutObjectAcl_not_implemented,
|
||||
"NoAclMode_GetObjectAcl_not_implemented": NoAclMode_GetObjectAcl_not_implemented,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright 2026 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"
|
||||
"strings"
|
||||
|
||||
"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 NoAclMode_CreateBucket_with_acl(s *S3Conf) error {
|
||||
testName := "NoAclMode_CreateBucket_with_acl"
|
||||
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
u := getUser("user")
|
||||
err := createUsers(s, []user{u})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CreateBucket(ctx, &s3.CreateBucketInput{
|
||||
Bucket: &bucket,
|
||||
ACL: types.BucketCannedACLPublicReadWrite,
|
||||
GrantFullControl: &u.access,
|
||||
GrantRead: &u.access,
|
||||
GrantReadACP: &u.access,
|
||||
GrantWrite: &u.access,
|
||||
GrantWriteACP: &u.access,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 teardown(s, bucket)
|
||||
})
|
||||
}
|
||||
|
||||
func NoAclMode_PutObject_with_acl(s *S3Conf) error {
|
||||
testName := "NoAclMode_PutObject_with_acl"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-object"
|
||||
u := getUser("user")
|
||||
err := createUsers(s, []user{u})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ACL: types.ObjectCannedACLBucketOwnerFullControl,
|
||||
GrantFullControl: &u.access,
|
||||
GrantRead: &u.access,
|
||||
GrantReadACP: &u.access,
|
||||
GrantWriteACP: &u.access,
|
||||
Body: strings.NewReader("dummy data"),
|
||||
})
|
||||
cancel()
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func NoAclMode_CopyObject_with_acl(s *S3Conf) error {
|
||||
testName := "NoAclMode_CopyObject_with_acl"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
u := getUser("user")
|
||||
err := createUsers(s, []user{u})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srcObj, dstObj := "source-object", "destination-object"
|
||||
_, err = putObjectWithData(10, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &srcObj,
|
||||
ACL: types.ObjectCannedACLAuthenticatedRead,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &dstObj,
|
||||
CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)),
|
||||
})
|
||||
cancel()
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func NoAclMode_multipart_upload_with_acl(s *S3Conf) error {
|
||||
testName := "NoAclMode_CreateMultipartUpload_with_acl"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-object"
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
mp, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ACL: types.ObjectCannedACLAuthenticatedRead,
|
||||
GrantFullControl: getPtr("non_existing_user_1"),
|
||||
GrantRead: getPtr("non_existing_user_2"),
|
||||
GrantReadACP: getPtr("non_existing_user_3"),
|
||||
GrantWriteACP: getPtr("non_existing_user_4"),
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts, _, err := uploadParts(s3client, 100, 1, bucket, obj, *mp.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := []types.CompletedPart{}
|
||||
for _, el := range parts {
|
||||
compParts = append(compParts, types.CompletedPart{
|
||||
ETag: el.ETag,
|
||||
PartNumber: el.PartNumber,
|
||||
})
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: mp.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func NoAclMode_PutBucketAcl(s *S3Conf) error {
|
||||
testName := "NoAclMode_PutBucketAcl"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
|
||||
Bucket: &bucket,
|
||||
ACL: types.BucketCannedACLPrivate,
|
||||
})
|
||||
cancel()
|
||||
|
||||
return checkApiErr(err, s3err.GetAPIError(s3err.ErrACLsDisabled))
|
||||
})
|
||||
}
|
||||
|
||||
func NoAclMode_PutObjectAcl_not_implemented(s *S3Conf) error {
|
||||
testName := "NoAclMode_PutObjectAcl_not_implemented"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-object"
|
||||
_, err := putObjectWithData(10, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.PutObjectAcl(ctx, &s3.PutObjectAclInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ACL: types.ObjectCannedACLAuthenticatedRead,
|
||||
})
|
||||
cancel()
|
||||
|
||||
return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented))
|
||||
})
|
||||
}
|
||||
|
||||
func NoAclMode_GetObjectAcl_not_implemented(s *S3Conf) error {
|
||||
testName := "NoAclMode_GetObjectAcl_not_implemented"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-object"
|
||||
_, err := putObjectWithData(10, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.GetObjectAcl(ctx, &s3.GetObjectAclInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
})
|
||||
cancel()
|
||||
|
||||
return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user