From f6dd2f947c5463f8bb3b322620ea1ac0f2cd990d Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Sat, 13 Jul 2024 09:58:20 -0700 Subject: [PATCH] feat: add option to allow symlinked directories as buckets This adds the ability to treat symlinks to directories at the top level gateway directory as buckets the same as normal directories. This could be a potential security issue allowing traversal into other filesystems within the system, so is defaulted to off. This can be enabled when specifically needed for both posix and scoutfs backend systems. Fixes #644 --- backend/posix/posix.go | 42 ++++++++++++++++++++----------- backend/scoutfs/scoutfs.go | 1 + backend/scoutfs/scoutfs_compat.go | 5 ++-- cmd/versitygw/posix.go | 12 +++++++-- cmd/versitygw/scoutfs.go | 7 ++++++ extra/example.conf | 8 ++++++ 6 files changed, 57 insertions(+), 18 deletions(-) diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 9b5ab5db..9147d090 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -61,6 +61,10 @@ type Posix struct { // used to determine if chowning is needed euid int egid int + + // bucketlinks is a flag to enable symlinks to directories at the top + // level gateway directory to be treated as buckets the same as directories + bucketlinks bool } var _ backend.Backend = &Posix{} @@ -87,8 +91,9 @@ const ( ) type PosixOpts struct { - ChownUID bool - ChownGID bool + ChownUID bool + ChownGID bool + BucketLinks bool } func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, error) { @@ -103,13 +108,14 @@ func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, erro } return &Posix{ - meta: meta, - rootfd: f, - rootdir: rootdir, - euid: os.Geteuid(), - egid: os.Getegid(), - chownuid: opts.ChownUID, - chowngid: opts.ChownGID, + meta: meta, + rootfd: f, + rootdir: rootdir, + euid: os.Geteuid(), + egid: os.Getegid(), + chownuid: opts.ChownUID, + chowngid: opts.ChownGID, + bucketlinks: opts.BucketLinks, }, nil } @@ -130,17 +136,25 @@ func (p *Posix) ListBuckets(_ context.Context, owner string, isAdmin bool) (s3re var buckets []s3response.ListAllMyBucketsEntry for _, entry := range entries { - if !entry.IsDir() { - // buckets must be a directory - continue - } - fi, err := entry.Info() if err != nil { // skip entries returning errors continue } + if p.bucketlinks && entry.Type() == fs.ModeSymlink { + fi, err = os.Stat(entry.Name()) + if err != nil { + // skip entries returning errors + continue + } + } + + if !fi.IsDir() { + // buckets must be a directory + continue + } + // return all the buckets for admin users if isAdmin { buckets = append(buckets, s3response.ListAllMyBucketsEntry{ diff --git a/backend/scoutfs/scoutfs.go b/backend/scoutfs/scoutfs.go index 606475f4..65336a65 100644 --- a/backend/scoutfs/scoutfs.go +++ b/backend/scoutfs/scoutfs.go @@ -42,6 +42,7 @@ type ScoutfsOpts struct { ChownUID bool ChownGID bool GlacierMode bool + BucketLinks bool } type ScoutFS struct { diff --git a/backend/scoutfs/scoutfs_compat.go b/backend/scoutfs/scoutfs_compat.go index fdd15c29..fe937636 100644 --- a/backend/scoutfs/scoutfs_compat.go +++ b/backend/scoutfs/scoutfs_compat.go @@ -37,8 +37,9 @@ func New(rootdir string, opts ScoutfsOpts) (*ScoutFS, error) { metastore := meta.XattrMeta{} p, err := posix.New(rootdir, metastore, posix.PosixOpts{ - ChownUID: opts.ChownUID, - ChownGID: opts.ChownGID, + ChownUID: opts.ChownUID, + ChownGID: opts.ChownGID, + BucketLinks: opts.BucketLinks, }) if err != nil { return nil, err diff --git a/cmd/versitygw/posix.go b/cmd/versitygw/posix.go index 9d234c80..b91fde59 100644 --- a/cmd/versitygw/posix.go +++ b/cmd/versitygw/posix.go @@ -24,6 +24,7 @@ import ( var ( chownuid, chowngid bool + bucketlinks bool ) func posixCommand() *cli.Command { @@ -54,6 +55,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: "bucketlinks", + Usage: "allow symlinked directories at bucket level to be treated as buckets", + EnvVars: []string{"VGW_BUCKET_LINKS"}, + Destination: &bucketlinks, + }, }, } } @@ -70,8 +77,9 @@ func runPosix(ctx *cli.Context) error { } be, err := posix.New(gwroot, meta.XattrMeta{}, posix.PosixOpts{ - ChownUID: chownuid, - ChownGID: chowngid, + ChownUID: chownuid, + ChownGID: chowngid, + BucketLinks: bucketlinks, }) if err != nil { return fmt.Errorf("init posix: %v", err) diff --git a/cmd/versitygw/scoutfs.go b/cmd/versitygw/scoutfs.go index 8ab1470e..d7a3f792 100644 --- a/cmd/versitygw/scoutfs.go +++ b/cmd/versitygw/scoutfs.go @@ -63,6 +63,12 @@ move interfaces as well as support for tiered filesystems.`, EnvVars: []string{"VGW_CHOWN_GID"}, Destination: &chowngid, }, + &cli.BoolFlag{ + Name: "bucketlinks", + Usage: "allow symlinked directories at bucket level to be treated as buckets", + EnvVars: []string{"VGW_BUCKET_LINKS"}, + Destination: &bucketlinks, + }, }, } } @@ -76,6 +82,7 @@ func runScoutfs(ctx *cli.Context) error { opts.GlacierMode = glacier opts.ChownUID = chownuid opts.ChownGID = chowngid + opts.BucketLinks = bucketlinks be, err := scoutfs.New(ctx.Args().Get(0), opts) if err != nil { diff --git a/extra/example.conf b/extra/example.conf index f84213cc..09687df0 100644 --- a/extra/example.conf +++ b/extra/example.conf @@ -305,6 +305,10 @@ ROOT_SECRET_ACCESS_KEY= #VGW_CHOWN_UID=false #VGW_CHOWN_GID=false +# The VGW_BUCKET_LINKS option will enable the gateway to treat symbolic links +# to directories at the top level gateway directory as buckets. +#VGW_BUCKET_LINKS=false + ########### # scoutfs # ########### @@ -336,6 +340,10 @@ ROOT_SECRET_ACCESS_KEY= #VGW_CHOWN_UID=false #VGW_CHOWN_GID=false +# The VGW_BUCKET_LINKS option will enable the gateway to treat symbolic links +# to directories at the top level gateway directory as buckets. +#VGW_BUCKET_LINKS=false + ###### # s3 # ######