Merge pull request #548 from versity/gateway-readonly-mode

Gateway readonly mode
This commit is contained in:
Ben McClelland
2024-05-06 16:02:50 -07:00
committed by GitHub
9 changed files with 86 additions and 17 deletions

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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)

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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})

View File

@@ -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)

View File

@@ -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)
})
}
}

View File

@@ -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)