mirror of
https://github.com/versity/versitygw.git
synced 2026-08-01 04:46:14 +00:00
feat: implement read only option for posix/scoutfs
Fixes #484. When read only option selected, backend will return AccessDenied for any operations that would update data or metadata in the backend. This include creating and deleting objects and buckets as well as updating object metadata. This will still allow admin to change bucket owner since read only is mainly intended for s3 api only.
This commit is contained in:
@@ -52,6 +52,9 @@ type Posix struct {
|
||||
chownuid bool
|
||||
chowngid bool
|
||||
|
||||
// read only mode prevents any backend modifications
|
||||
readonly bool
|
||||
|
||||
// euid/egid are the effective uid/gid of the running versitygw process
|
||||
// used to determine if chowning is needed
|
||||
euid int
|
||||
@@ -77,6 +80,7 @@ const (
|
||||
type PosixOpts struct {
|
||||
ChownUID bool
|
||||
ChownGID bool
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
func New(rootdir string, opts PosixOpts) (*Posix, error) {
|
||||
@@ -103,6 +107,7 @@ func New(rootdir string, opts PosixOpts) (*Posix, error) {
|
||||
egid: os.Getegid(),
|
||||
chownuid: opts.ChownUID,
|
||||
chowngid: opts.ChownGID,
|
||||
readonly: opts.ReadOnly,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -196,6 +201,10 @@ var (
|
||||
)
|
||||
|
||||
func (p *Posix) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, acl []byte) error {
|
||||
if p.readonly {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
if input.Bucket == nil {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidBucketName)
|
||||
}
|
||||
@@ -232,6 +241,10 @@ func (p *Posix) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, a
|
||||
}
|
||||
|
||||
func (p *Posix) DeleteBucket(_ context.Context, input *s3.DeleteBucketInput) error {
|
||||
if p.readonly {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
if input.Bucket == nil {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidBucketName)
|
||||
}
|
||||
@@ -265,6 +278,10 @@ func (p *Posix) DeleteBucket(_ context.Context, input *s3.DeleteBucketInput) err
|
||||
}
|
||||
|
||||
func (p *Posix) CreateMultipartUpload(_ context.Context, mpu *s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) {
|
||||
if p.readonly {
|
||||
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
if mpu.Bucket == nil {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidBucketName)
|
||||
}
|
||||
@@ -348,6 +365,10 @@ func (p *Posix) getChownIDs(acct auth.Account) (int, int, bool) {
|
||||
}
|
||||
|
||||
func (p *Posix) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteMultipartUploadInput) (*s3.CompleteMultipartUploadOutput, error) {
|
||||
if p.readonly {
|
||||
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
acct, ok := ctx.Value("account").(auth.Account)
|
||||
if !ok {
|
||||
acct = auth.Account{}
|
||||
@@ -571,6 +592,10 @@ func isValidMeta(val string) bool {
|
||||
}
|
||||
|
||||
func (p *Posix) AbortMultipartUpload(_ context.Context, mpu *s3.AbortMultipartUploadInput) error {
|
||||
if p.readonly {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
if mpu.Bucket == nil {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidBucketName)
|
||||
}
|
||||
@@ -870,6 +895,10 @@ func (p *Posix) ListParts(_ context.Context, input *s3.ListPartsInput) (s3respon
|
||||
}
|
||||
|
||||
func (p *Posix) UploadPart(ctx context.Context, input *s3.UploadPartInput) (string, error) {
|
||||
if p.readonly {
|
||||
return "", s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
acct, ok := ctx.Value("account").(auth.Account)
|
||||
if !ok {
|
||||
acct = auth.Account{}
|
||||
@@ -947,6 +976,10 @@ func (p *Posix) UploadPart(ctx context.Context, input *s3.UploadPartInput) (stri
|
||||
}
|
||||
|
||||
func (p *Posix) UploadPartCopy(ctx context.Context, upi *s3.UploadPartCopyInput) (s3response.CopyObjectResult, error) {
|
||||
if p.readonly {
|
||||
return s3response.CopyObjectResult{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
acct, ok := ctx.Value("account").(auth.Account)
|
||||
if !ok {
|
||||
acct = auth.Account{}
|
||||
@@ -1070,6 +1103,10 @@ func (p *Posix) UploadPartCopy(ctx context.Context, upi *s3.UploadPartCopyInput)
|
||||
}
|
||||
|
||||
func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (string, error) {
|
||||
if p.readonly {
|
||||
return "", s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
acct, ok := ctx.Value("account").(auth.Account)
|
||||
if !ok {
|
||||
acct = auth.Account{}
|
||||
@@ -1198,6 +1235,10 @@ func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (string, e
|
||||
}
|
||||
|
||||
func (p *Posix) DeleteObject(_ context.Context, input *s3.DeleteObjectInput) error {
|
||||
if p.readonly {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
if input.Bucket == nil {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidBucketName)
|
||||
}
|
||||
@@ -1262,6 +1303,10 @@ func (p *Posix) removeParents(bucket, object string) error {
|
||||
}
|
||||
|
||||
func (p *Posix) DeleteObjects(ctx context.Context, input *s3.DeleteObjectsInput) (s3response.DeleteResult, error) {
|
||||
if p.readonly {
|
||||
return s3response.DeleteResult{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
// delete object already checks bucket
|
||||
delResult, errs := []types.DeletedObject{}, []types.Error{}
|
||||
for _, obj := range input.Delete.Objects {
|
||||
@@ -1477,6 +1522,10 @@ func (p *Posix) HeadObject(_ context.Context, input *s3.HeadObjectInput) (*s3.He
|
||||
}
|
||||
|
||||
func (p *Posix) CopyObject(ctx context.Context, input *s3.CopyObjectInput) (*s3.CopyObjectOutput, error) {
|
||||
if p.readonly {
|
||||
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
if input.Bucket == nil {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidBucketName)
|
||||
}
|
||||
@@ -1746,6 +1795,10 @@ func (p *Posix) ListObjectsV2(_ context.Context, input *s3.ListObjectsV2Input) (
|
||||
}
|
||||
|
||||
func (p *Posix) PutBucketAcl(_ context.Context, bucket string, data []byte) error {
|
||||
if p.readonly {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
_, err := os.Stat(bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
@@ -1784,6 +1837,10 @@ func (p *Posix) GetBucketAcl(_ context.Context, input *s3.GetBucketAclInput) ([]
|
||||
}
|
||||
|
||||
func (p *Posix) PutBucketTagging(_ context.Context, bucket string, tags map[string]string) error {
|
||||
if p.readonly {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
_, err := os.Stat(bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
@@ -1869,6 +1926,10 @@ func (p *Posix) getXattrTags(bucket, object string) (map[string]string, error) {
|
||||
}
|
||||
|
||||
func (p *Posix) PutObjectTagging(_ context.Context, bucket, object string, tags map[string]string) error {
|
||||
if p.readonly {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
_, err := os.Stat(bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
@@ -1909,6 +1970,10 @@ func (p *Posix) DeleteObjectTagging(ctx context.Context, bucket, object string)
|
||||
}
|
||||
|
||||
func (p *Posix) PutBucketPolicy(ctx context.Context, bucket string, policy []byte) error {
|
||||
if p.readonly {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
_, err := os.Stat(bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
type ScoutfsOpts struct {
|
||||
ChownUID bool
|
||||
ChownGID bool
|
||||
ReadOnly bool
|
||||
GlacierMode bool
|
||||
}
|
||||
|
||||
@@ -63,6 +64,9 @@ type ScoutFS struct {
|
||||
chownuid bool
|
||||
chowngid bool
|
||||
|
||||
// read only mode prevents any backend modifications
|
||||
readonly bool
|
||||
|
||||
// euid/egid are the effective uid/gid of the running versitygw process
|
||||
// used to determine if chowning is needed
|
||||
euid int
|
||||
@@ -143,6 +147,10 @@ func (s *ScoutFS) getChownIDs(acct auth.Account) (int, int, bool) {
|
||||
// ioctl to not have to read and copy the part data to the final object. This
|
||||
// saves a read and write cycle for all mutlipart uploads.
|
||||
func (s *ScoutFS) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteMultipartUploadInput) (*s3.CompleteMultipartUploadOutput, error) {
|
||||
if s.readonly {
|
||||
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
acct, ok := ctx.Value("account").(auth.Account)
|
||||
if !ok {
|
||||
acct = auth.Account{}
|
||||
|
||||
@@ -37,6 +37,7 @@ func New(rootdir string, opts ScoutfsOpts) (*ScoutFS, error) {
|
||||
p, err := posix.New(rootdir, posix.PosixOpts{
|
||||
ChownUID: opts.ChownUID,
|
||||
ChownGID: opts.ChownGID,
|
||||
ReadOnly: opts.ReadOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -53,6 +54,7 @@ func New(rootdir string, opts ScoutfsOpts) (*ScoutFS, error) {
|
||||
rootdir: rootdir,
|
||||
chownuid: opts.ChownUID,
|
||||
chowngid: opts.ChownGID,
|
||||
readonly: opts.ReadOnly,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ func (s *ScoutFS) openTmpFile(_, _, _ string, _ int64, _ auth.Account) (*tmpfile
|
||||
_ = s.chowngid
|
||||
_ = s.euid
|
||||
_ = s.egid
|
||||
_ = s.readonly
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
var (
|
||||
chownuid, chowngid bool
|
||||
readonly bool
|
||||
)
|
||||
|
||||
func posixCommand() *cli.Command {
|
||||
@@ -53,6 +54,12 @@ will be translated into the file /mnt/fs/gwroot/mybucket/a/b/c/myobject`,
|
||||
EnvVars: []string{"VGW_CHOWN_GID"},
|
||||
Destination: &chowngid,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "readonly",
|
||||
Usage: "allow only read operations to backend",
|
||||
EnvVars: []string{"VGW_READ_ONLY"},
|
||||
Destination: &readonly,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -65,6 +72,7 @@ func runPosix(ctx *cli.Context) error {
|
||||
be, err := posix.New(ctx.Args().Get(0), posix.PosixOpts{
|
||||
ChownUID: chownuid,
|
||||
ChownGID: chowngid,
|
||||
ReadOnly: readonly,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("init posix: %v", err)
|
||||
|
||||
@@ -23,6 +23,10 @@ import (
|
||||
|
||||
var (
|
||||
glacier bool
|
||||
|
||||
// defined in posix.go:
|
||||
// chownuid, chowngid bool
|
||||
// readonly bool
|
||||
)
|
||||
|
||||
func scoutfsCommand() *cli.Command {
|
||||
@@ -63,6 +67,12 @@ move interfaces as well as support for tiered filesystems.`,
|
||||
EnvVars: []string{"VGW_CHOWN_GID"},
|
||||
Destination: &chowngid,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "readonly",
|
||||
Usage: "allow only read operations to backend",
|
||||
EnvVars: []string{"VGW_READ_ONLY"},
|
||||
Destination: &readonly,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -76,6 +86,7 @@ func runScoutfs(ctx *cli.Context) error {
|
||||
opts.GlacierMode = glacier
|
||||
opts.ChownUID = chownuid
|
||||
opts.ChownGID = chowngid
|
||||
opts.ReadOnly = readonly
|
||||
|
||||
be, err := scoutfs.New(ctx.Args().Get(0), opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -254,6 +254,12 @@ ROOT_SECRET_ACCESS_KEY=
|
||||
#VGW_CHOWN_UID=false
|
||||
#VGW_CHOWN_GID=false
|
||||
|
||||
# The VGW_READ_ONLY option will disable all write operations to the backend
|
||||
# filesystem. This is useful for creating a read-only gateway for clients.
|
||||
# This will prevent any PUT, POST, DELETE, and multipart upload operations
|
||||
# for objects and buckets as well as preventing updating metadata for objects.
|
||||
#VGW_READ_ONLY=false
|
||||
|
||||
###########
|
||||
# scoutfs #
|
||||
###########
|
||||
@@ -285,6 +291,12 @@ ROOT_SECRET_ACCESS_KEY=
|
||||
#VGW_CHOWN_UID=false
|
||||
#VGW_CHOWN_GID=false
|
||||
|
||||
# The VGW_READ_ONLY option will disable all write operations to the backend
|
||||
# filesystem. This is useful for creating a read-only gateway for clients.
|
||||
# This will prevent any PUT, POST, DELETE, and multipart upload operations
|
||||
# for objects and buckets as well as preventing updating metadata for objects.
|
||||
#VGW_READ_ONLY=false
|
||||
|
||||
######
|
||||
# s3 #
|
||||
######
|
||||
|
||||
Reference in New Issue
Block a user