From 2a2f9c827cc3925b2dc3974d7c284782c34fffb9 Mon Sep 17 00:00:00 2001 From: jonaustin09 Date: Mon, 6 May 2024 16:41:39 -0400 Subject: [PATCH] feat: Closes #484. Added support to run the gateway on read only mode --- auth/acl.go | 6 +++++ cmd/versitygw/main.go | 10 ++++++++ s3api/admin-server.go | 1 - s3api/controllers/base.go | 43 ++++++++++++++++++++++++++++++++- s3api/controllers/base_test.go | 2 +- s3api/middlewares/acl-parser.go | 10 +++++++- s3api/router.go | 4 +-- s3api/router_test.go | 2 +- s3api/server.go | 25 +++++++++++-------- 9 files changed, 86 insertions(+), 17 deletions(-) diff --git a/auth/acl.go b/auth/acl.go index 4aeb878a..bd8e808c 100644 --- a/auth/acl.go +++ b/auth/acl.go @@ -280,9 +280,15 @@ type AccessOptions struct { Bucket string Object string Action Action + Readonly bool } func VerifyAccess(ctx context.Context, be backend.Backend, opts AccessOptions) error { + if opts.Readonly { + if opts.AclPermission == types.PermissionWrite || opts.AclPermission == types.PermissionWriteAcp { + return s3err.GetAPIError(s3err.ErrAccessDenied) + } + } if opts.IsRoot { return nil } diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index 16fea8ba..49634b2d 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -50,6 +50,7 @@ var ( debug bool pprof string quiet bool + readonly bool iamDir string ldapURL, ldapBindDN, ldapPassword string ldapQueryBase, ldapObjClasses string @@ -391,6 +392,12 @@ func initFlags() []cli.Flag { EnvVars: []string{"VGW_HEALTH"}, Destination: &healthPath, }, + &cli.BoolFlag{ + Name: "readonly", + Usage: "allow only read operations across all the gateway", + EnvVars: []string{"VGW_READ_ONLY"}, + Destination: &readonly, + }, } } @@ -442,6 +449,9 @@ func runGateway(ctx context.Context, be backend.Backend) error { if healthPath != "" { opts = append(opts, s3api.WithHealth(healthPath)) } + if readonly { + opts = append(opts, s3api.WithReadOnly()) + } admApp := fiber.New(fiber.Config{ AppName: "versitygw", diff --git a/s3api/admin-server.go b/s3api/admin-server.go index b00f82a7..7015697c 100644 --- a/s3api/admin-server.go +++ b/s3api/admin-server.go @@ -51,7 +51,6 @@ func NewAdminServer(app *fiber.App, be backend.Backend, root middlewares.RootUse // Authentication middlewares app.Use(middlewares.VerifyV4Signature(root, iam, nil, region, false)) app.Use(middlewares.VerifyMD5Body(nil)) - app.Use(middlewares.AclParser(be, nil)) server.router.Init(app, be, iam) diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index e961d1a3..d610227f 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -44,19 +44,21 @@ type S3ApiController struct { logger s3log.AuditLogger evSender s3event.S3EventSender debug bool + readonly bool } const ( iso8601Format = "20060102T150405Z" ) -func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs s3event.S3EventSender, debug bool) S3ApiController { +func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs s3event.S3EventSender, debug bool, readonly bool) S3ApiController { return S3ApiController{ be: be, iam: iam, logger: logger, evSender: evs, debug: debug, + readonly: readonly, } } @@ -88,6 +90,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("tagging") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -133,6 +136,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("retention") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -171,6 +175,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("legal-hold") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -225,6 +230,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { } err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -263,6 +269,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("acl") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionReadAcp, IsRoot: isRoot, @@ -293,6 +300,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("attributes") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -347,6 +355,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { } err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -478,6 +487,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("tagging") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -522,6 +532,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("versioning") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -558,6 +569,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("policy") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -585,6 +597,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("versions") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -634,6 +647,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("object-lock") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -671,6 +685,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("acl") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionReadAcp, IsRoot: isRoot, @@ -707,6 +722,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("uploads") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -753,6 +769,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { if ctx.QueryInt("list-type") == 2 { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -799,6 +816,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { } err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -893,6 +911,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { } err = auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -921,6 +940,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("versioning") { parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -971,6 +991,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1008,6 +1029,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("policy") { parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1053,6 +1075,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWriteAcp, IsRoot: isRoot, @@ -1314,6 +1337,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { } err = auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1344,6 +1368,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("retention") { if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1388,6 +1413,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { } if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1483,6 +1509,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1744,6 +1771,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1874,6 +1902,7 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("tagging") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1903,6 +1932,7 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("policy") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1931,6 +1961,7 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -1982,6 +2013,7 @@ func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) error { err = auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -2042,6 +2074,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { if ctx.Request().URI().QueryArgs().Has("tagging") { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -2077,6 +2110,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -2113,6 +2147,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -2166,6 +2201,7 @@ func (c S3ApiController) HeadBucket(ctx *fiber.Ctx) error { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -2240,6 +2276,7 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) error { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -2388,6 +2425,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error { err = auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -2438,6 +2476,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error { err = auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionRead, IsRoot: isRoot, @@ -2493,6 +2532,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error { err = auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, @@ -2541,6 +2581,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ + Readonly: c.readonly, Acl: parsedAcl, AclPermission: types.PermissionWrite, IsRoot: isRoot, diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go index 8eec0378..540898cd 100644 --- a/s3api/controllers/base_test.go +++ b/s3api/controllers/base_test.go @@ -77,7 +77,7 @@ func TestNew(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := New(tt.args.be, tt.args.iam, nil, nil, false) + got := New(tt.args.be, tt.args.iam, nil, nil, false, false) if !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } diff --git a/s3api/middlewares/acl-parser.go b/s3api/middlewares/acl-parser.go index b966bbea..6beb3129 100644 --- a/s3api/middlewares/acl-parser.go +++ b/s3api/middlewares/acl-parser.go @@ -24,6 +24,7 @@ import ( "github.com/versity/versitygw/auth" "github.com/versity/versitygw/backend" "github.com/versity/versitygw/s3api/controllers" + "github.com/versity/versitygw/s3err" "github.com/versity/versitygw/s3log" ) @@ -31,7 +32,7 @@ var ( singlePath = regexp.MustCompile(`^/[^/]+/?$`) ) -func AclParser(be backend.Backend, logger s3log.AuditLogger) fiber.Handler { +func AclParser(be backend.Backend, logger s3log.AuditLogger, readonly bool) fiber.Handler { return func(ctx *fiber.Ctx) error { isRoot, acct := ctx.Locals("isRoot").(bool), ctx.Locals("account").(auth.Account) path := ctx.Path() @@ -53,6 +54,13 @@ func AclParser(be backend.Backend, logger s3log.AuditLogger) fiber.Handler { if err := auth.MayCreateBucket(acct, isRoot); err != nil { return controllers.SendXMLResponse(ctx, nil, err, &controllers.MetaOpts{Logger: logger, Action: "CreateBucket"}) } + if readonly { + return controllers.SendXMLResponse(ctx, nil, s3err.GetAPIError(s3err.ErrAccessDenied), + &controllers.MetaOpts{ + Logger: logger, + Action: "CreateBucket", + }) + } return ctx.Next() } data, err := be.GetBucketAcl(ctx.Context(), &s3.GetBucketAclInput{Bucket: &bucket}) diff --git a/s3api/router.go b/s3api/router.go index 0b2699ff..0ec44c58 100644 --- a/s3api/router.go +++ b/s3api/router.go @@ -27,8 +27,8 @@ type S3ApiRouter struct { WithAdmSrv bool } -func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs s3event.S3EventSender, debug bool) { - s3ApiController := controllers.New(be, iam, logger, evs, debug) +func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs s3event.S3EventSender, debug bool, readonly bool) { + s3ApiController := controllers.New(be, iam, logger, evs, debug, readonly) if sa.WithAdmSrv { adminController := controllers.NewAdminController(iam, be) diff --git a/s3api/router_test.go b/s3api/router_test.go index 9863a7cb..8ee07e8c 100644 --- a/s3api/router_test.go +++ b/s3api/router_test.go @@ -45,7 +45,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, false) + tt.sa.Init(tt.args.app, tt.args.be, tt.args.iam, nil, nil, false, false) }) } } diff --git a/s3api/server.go b/s3api/server.go index eb016fab..db9a78f6 100644 --- a/s3api/server.go +++ b/s3api/server.go @@ -28,14 +28,15 @@ import ( ) type S3ApiServer struct { - app *fiber.App - backend backend.Backend - router *S3ApiRouter - port string - cert *tls.Certificate - quiet bool - debug bool - health string + app *fiber.App + backend backend.Backend + router *S3ApiRouter + port string + cert *tls.Certificate + quiet bool + debug bool + readonly bool + health string } func New(app *fiber.App, be backend.Backend, root middlewares.RootUserConfig, port, region string, iam auth.IAMService, l s3log.AuditLogger, evs s3event.S3EventSender, opts ...Option) (*S3ApiServer, error) { @@ -68,9 +69,9 @@ func New(app *fiber.App, be backend.Backend, root middlewares.RootUserConfig, po app.Use(middlewares.VerifyV4Signature(root, iam, l, region, server.debug)) app.Use(middlewares.ProcessChunkedBody(root, iam, l, region)) app.Use(middlewares.VerifyMD5Body(l)) - app.Use(middlewares.AclParser(be, l)) + app.Use(middlewares.AclParser(be, l, server.readonly)) - server.router.Init(app, be, iam, l, evs, server.debug) + server.router.Init(app, be, iam, l, evs, server.debug, server.readonly) return server, nil } @@ -103,6 +104,10 @@ func WithHealth(health string) Option { return func(s *S3ApiServer) { s.health = health } } +func WithReadOnly() Option { + return func(s *S3ApiServer) { s.readonly = true } +} + func (sa *S3ApiServer) Serve() (err error) { if sa.cert != nil { return sa.app.ListenTLSWithCertificate(sa.port, *sa.cert)