mirror of
https://github.com/versity/versitygw.git
synced 2026-08-21 14:46:19 +00:00
feat: configuration option to disable ACLs
Closes #1847 This PR introduces a global optional gateway CLI flag `--disable-acl` (`VGW_DISABLE_ACL`) to disable ACL handling. When this flag is enabled, the gateway ignores all ACL-related headers, particularly in `CreateBucket`, `PutObject`, `CopyObject`, and `CreateMultipartUpload`. `GetBucketAcl` behavior is unchanged simply returning the bucket ACL config. There's no change in object ACL actions(`PutObjectACL`, `GetObjectACL`). They return a`NotImplemented` error as before. A new custom error is added for PutBucketAcl calls when ACLs are disabled at the gateway level. Its HTTP status code and error code match AWS S3’s behavior, with only a slightly different error message. In the access-control checker, ACL evaluation is fully bypassed. If ACLs are disabled only the bucket owner gets access to the bucket and all grantee checks are ignored. The PR also includes minor refactoring of the S3 API server and router. The growing list of parameters passed to the router’s Init method has been consolidated into fields within the router struct, initialized during router construction. Parameters not needed by the S3 server are no longer stored in the server configuration and are instead forwarded directly to the router.
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