feat: add option to configure mode permissions on new directories

We had 0755 hard coded for newly created directories before. This
adds a user option to configure what the default mode permissions
should be for newly created directories.

Fixes #878
This commit is contained in:
Ben McClelland
2024-10-10 15:47:25 -07:00
parent a78c826d0f
commit 2c713c58f9
9 changed files with 62 additions and 25 deletions

View File

@@ -15,11 +15,6 @@ import (
"github.com/versity/versitygw/s3err" "github.com/versity/versitygw/s3err"
) )
var (
// TODO: make this configurable
defaultDirPerm fs.FileMode = 0755
)
// MkdirAll is similar to os.MkdirAll but it will return // MkdirAll is similar to os.MkdirAll but it will return
// ErrObjectParentIsFile when appropriate // ErrObjectParentIsFile when appropriate
// MkdirAll creates a directory named path, // MkdirAll creates a directory named path,
@@ -32,7 +27,7 @@ var (
// and returns nil. // and returns nil.
// Any directory created will be set to provided uid/gid ownership // Any directory created will be set to provided uid/gid ownership
// if doChown is true. // if doChown is true.
func MkdirAll(path string, uid, gid int, doChown bool) error { func MkdirAll(path string, uid, gid int, doChown bool, dirPerm fs.FileMode) error {
// Fast path: if we can tell whether path is a directory or file, stop with success or error. // Fast path: if we can tell whether path is a directory or file, stop with success or error.
dir, err := os.Stat(path) dir, err := os.Stat(path)
if err == nil { if err == nil {
@@ -55,14 +50,14 @@ func MkdirAll(path string, uid, gid int, doChown bool) error {
if j > 1 { if j > 1 {
// Create parent. // Create parent.
err = MkdirAll(path[:j-1], uid, gid, doChown) err = MkdirAll(path[:j-1], uid, gid, doChown, dirPerm)
if err != nil { if err != nil {
return err return err
} }
} }
// Parent now exists; invoke Mkdir and use its result. // Parent now exists; invoke Mkdir and use its result.
err = os.Mkdir(path, defaultDirPerm) err = os.Mkdir(path, dirPerm)
if err != nil { if err != nil {
// Handle arguments like "foo/." by // Handle arguments like "foo/." by
// double-checking that directory doesn't exist. // double-checking that directory doesn't exist.

View File

@@ -68,6 +68,9 @@ type Posix struct {
// bucket versioning directory path // bucket versioning directory path
versioningDir string versioningDir string
// newDirPerm is the permission to set on newly created directories
newDirPerm fs.FileMode
} }
var _ backend.Backend = &Posix{} var _ backend.Backend = &Posix{}
@@ -103,6 +106,7 @@ type PosixOpts struct {
ChownGID bool ChownGID bool
BucketLinks bool BucketLinks bool
VersioningDir string VersioningDir string
NewDirPerm fs.FileMode
} }
func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, error) { func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, error) {
@@ -167,6 +171,7 @@ func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, erro
chowngid: opts.ChownGID, chowngid: opts.ChownGID,
bucketlinks: opts.BucketLinks, bucketlinks: opts.BucketLinks,
versioningDir: verioningdirAbs, versioningDir: verioningdirAbs,
newDirPerm: opts.NewDirPerm,
}, nil }, nil
} }
@@ -291,11 +296,6 @@ func (p *Posix) HeadBucket(_ context.Context, input *s3.HeadBucketInput) (*s3.He
return &s3.HeadBucketOutput{}, nil return &s3.HeadBucketOutput{}, nil
} }
var (
// TODO: make this configurable
defaultDirPerm fs.FileMode = 0755
)
func (p *Posix) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, acl []byte) error { func (p *Posix) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, acl []byte) error {
if input.Bucket == nil { if input.Bucket == nil {
return s3err.GetAPIError(s3err.ErrInvalidBucketName) return s3err.GetAPIError(s3err.ErrInvalidBucketName)
@@ -310,7 +310,7 @@ func (p *Posix) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, a
bucket := *input.Bucket bucket := *input.Bucket
err := os.Mkdir(bucket, defaultDirPerm) err := os.Mkdir(bucket, p.newDirPerm)
if err != nil && os.IsExist(err) { if err != nil && os.IsExist(err) {
aclJSON, err := p.meta.RetrieveAttribute(nil, bucket, "", aclkey) aclJSON, err := p.meta.RetrieveAttribute(nil, bucket, "", aclkey)
if err != nil { if err != nil {
@@ -642,7 +642,7 @@ func (p *Posix) createObjVersion(bucket, key string, size int64, acc auth.Accoun
versionPath = filepath.Join(versionBucketPath, versioningKey) versionPath = filepath.Join(versionBucketPath, versioningKey)
err = os.MkdirAll(filepath.Join(versionBucketPath, genObjVersionKey(key)), defaultDirPerm) err = os.MkdirAll(filepath.Join(versionBucketPath, genObjVersionKey(key)), p.newDirPerm)
if err != nil { if err != nil {
return versionPath, err return versionPath, err
} }
@@ -1375,7 +1375,7 @@ func (p *Posix) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteM
dir := filepath.Dir(objname) dir := filepath.Dir(objname)
if dir != "" { if dir != "" {
uid, gid, doChown := p.getChownIDs(acct) uid, gid, doChown := p.getChownIDs(acct)
err = backend.MkdirAll(dir, uid, gid, doChown) err = backend.MkdirAll(dir, uid, gid, doChown, p.newDirPerm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -2158,7 +2158,7 @@ func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (s3respons
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrDirectoryObjectContainsData) return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrDirectoryObjectContainsData)
} }
err = backend.MkdirAll(name, uid, gid, doChown) err = backend.MkdirAll(name, uid, gid, doChown, p.newDirPerm)
if err != nil { if err != nil {
if errors.Is(err, syscall.EDQUOT) { if errors.Is(err, syscall.EDQUOT) {
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded) return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
@@ -2248,7 +2248,7 @@ func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (s3respons
dir := filepath.Dir(name) dir := filepath.Dir(name)
if dir != "" { if dir != "" {
err = backend.MkdirAll(dir, uid, gid, doChown) err = backend.MkdirAll(dir, uid, gid, doChown, p.newDirPerm)
if err != nil { if err != nil {
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrExistingObjectIsDirectory) return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrExistingObjectIsDirectory)
} }

View File

@@ -43,6 +43,7 @@ type tmpfile struct {
needsChown bool needsChown bool
uid int uid int
gid int gid int
newDirPerm fs.FileMode
} }
var ( var (
@@ -62,7 +63,7 @@ func (p *Posix) openTmpFile(dir, bucket, obj string, size int64, acct auth.Accou
fd, err := unix.Open(dir, unix.O_RDWR|unix.O_TMPFILE|unix.O_CLOEXEC, defaultFilePerm) fd, err := unix.Open(dir, unix.O_RDWR|unix.O_TMPFILE|unix.O_CLOEXEC, defaultFilePerm)
if err != nil { if err != nil {
// O_TMPFILE not supported, try fallback // O_TMPFILE not supported, try fallback
err = backend.MkdirAll(dir, uid, gid, doChown) err = backend.MkdirAll(dir, uid, gid, doChown, p.newDirPerm)
if err != nil { if err != nil {
return nil, fmt.Errorf("make temp dir: %w", err) return nil, fmt.Errorf("make temp dir: %w", err)
} }
@@ -108,6 +109,7 @@ func (p *Posix) openTmpFile(dir, bucket, obj string, size int64, acct auth.Accou
needsChown: doChown, needsChown: doChown,
uid: uid, uid: uid,
gid: gid, gid: gid,
newDirPerm: p.newDirPerm,
} }
// falloc is best effort, its fine if this fails // falloc is best effort, its fine if this fails
@@ -151,7 +153,7 @@ func (tmp *tmpfile) link() error {
dir := filepath.Dir(objPath) dir := filepath.Dir(objPath)
err = backend.MkdirAll(dir, tmp.uid, tmp.gid, tmp.needsChown) err = backend.MkdirAll(dir, tmp.uid, tmp.gid, tmp.needsChown, tmp.newDirPerm)
if err != nil { if err != nil {
return fmt.Errorf("make parent dir: %w", err) return fmt.Errorf("make parent dir: %w", err)
} }

View File

@@ -41,7 +41,7 @@ func (p *Posix) openTmpFile(dir, bucket, obj string, size int64, acct auth.Accou
// Create a temp file for upload while in progress (see link comments below). // Create a temp file for upload while in progress (see link comments below).
var err error var err error
err = backend.MkdirAll(dir, uid, gid, doChown) err = backend.MkdirAll(dir, uid, gid, doChown, p.newDirPerm)
if err != nil { if err != nil {
return nil, fmt.Errorf("make temp dir: %w", err) return nil, fmt.Errorf("make temp dir: %w", err)
} }

View File

@@ -44,6 +44,7 @@ type ScoutfsOpts struct {
ChownGID bool ChownGID bool
GlacierMode bool GlacierMode bool
BucketLinks bool BucketLinks bool
NewDirPerm fs.FileMode
} }
type ScoutFS struct { type ScoutFS struct {
@@ -74,6 +75,9 @@ type ScoutFS struct {
// used to determine if chowning is needed // used to determine if chowning is needed
euid int euid int
egid int egid int
// newDirPerm is the permissions to use when creating new directories
newDirPerm fs.FileMode
} }
var _ backend.Backend = &ScoutFS{} var _ backend.Backend = &ScoutFS{}
@@ -277,7 +281,7 @@ func (s *ScoutFS) CompleteMultipartUpload(ctx context.Context, input *s3.Complet
dir := filepath.Dir(objname) dir := filepath.Dir(objname)
if dir != "" { if dir != "" {
uid, gid, doChown := s.getChownIDs(acct) uid, gid, doChown := s.getChownIDs(acct)
err = backend.MkdirAll(dir, uid, gid, doChown) err = backend.MkdirAll(dir, uid, gid, doChown, s.newDirPerm)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -40,6 +40,7 @@ func New(rootdir string, opts ScoutfsOpts) (*ScoutFS, error) {
ChownUID: opts.ChownUID, ChownUID: opts.ChownUID,
ChownGID: opts.ChownGID, ChownGID: opts.ChownGID,
BucketLinks: opts.BucketLinks, BucketLinks: opts.BucketLinks,
NewDirPerm: opts.NewDirPerm,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -58,6 +59,7 @@ func New(rootdir string, opts ScoutfsOpts) (*ScoutFS, error) {
chownuid: opts.ChownUID, chownuid: opts.ChownUID,
chowngid: opts.ChownGID, chowngid: opts.ChownGID,
glaciermode: opts.GlacierMode, glaciermode: opts.GlacierMode,
newDirPerm: opts.NewDirPerm,
}, nil }, nil
} }
@@ -71,10 +73,10 @@ type tmpfile struct {
needsChown bool needsChown bool
uid int uid int
gid int gid int
newDirPerm fs.FileMode
} }
var ( var (
// TODO: make this configurable
defaultFilePerm uint32 = 0644 defaultFilePerm uint32 = 0644
) )
@@ -102,6 +104,7 @@ func (s *ScoutFS) openTmpFile(dir, bucket, obj string, size int64, acct auth.Acc
needsChown: doChown, needsChown: doChown,
uid: uid, uid: uid,
gid: gid, gid: gid,
newDirPerm: s.newDirPerm,
} }
if doChown { if doChown {
@@ -129,7 +132,7 @@ func (tmp *tmpfile) link() error {
dir := filepath.Dir(objPath) dir := filepath.Dir(objPath)
err = backend.MkdirAll(dir, tmp.uid, tmp.gid, tmp.needsChown) err = backend.MkdirAll(dir, tmp.uid, tmp.gid, tmp.needsChown, tmp.newDirPerm)
if err != nil { if err != nil {
return fmt.Errorf("make parent dir: %w", err) return fmt.Errorf("make parent dir: %w", err)
} }

View File

@@ -58,7 +58,9 @@ func initPosix(ctx context.Context) {
log.Fatalf("make temp directory: %v", err) log.Fatalf("make temp directory: %v", err)
} }
be, err := posix.New(tempdir, meta.XattrMeta{}, posix.PosixOpts{}) be, err := posix.New(tempdir, meta.XattrMeta{}, posix.PosixOpts{
NewDirPerm: 0755,
})
if err != nil { if err != nil {
log.Fatalf("init posix: %v", err) log.Fatalf("init posix: %v", err)
} }

View File

@@ -16,6 +16,8 @@ package main
import ( import (
"fmt" "fmt"
"io/fs"
"math"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/versity/versitygw/backend/meta" "github.com/versity/versitygw/backend/meta"
@@ -26,6 +28,7 @@ var (
chownuid, chowngid bool chownuid, chowngid bool
bucketlinks bool bucketlinks bool
versioningDir string versioningDir string
dirPerms uint
) )
func posixCommand() *cli.Command { func posixCommand() *cli.Command {
@@ -68,6 +71,14 @@ will be translated into the file /mnt/fs/gwroot/mybucket/a/b/c/myobject`,
EnvVars: []string{"VGW_VERSIONING_DIR"}, EnvVars: []string{"VGW_VERSIONING_DIR"},
Destination: &versioningDir, Destination: &versioningDir,
}, },
&cli.UintFlag{
Name: "dir-perms",
Usage: "default directory permissions for new directories",
EnvVars: []string{"VGW_DIR_PERMS"},
Destination: &dirPerms,
DefaultText: "0755",
Value: 0755,
},
}, },
} }
} }
@@ -83,11 +94,16 @@ func runPosix(ctx *cli.Context) error {
return fmt.Errorf("posix xattr check: %v", err) return fmt.Errorf("posix xattr check: %v", err)
} }
if dirPerms > math.MaxUint32 {
return fmt.Errorf("invalid directory permissions: %d", dirPerms)
}
be, err := posix.New(gwroot, meta.XattrMeta{}, posix.PosixOpts{ be, err := posix.New(gwroot, meta.XattrMeta{}, posix.PosixOpts{
ChownUID: chownuid, ChownUID: chownuid,
ChownGID: chowngid, ChownGID: chowngid,
BucketLinks: bucketlinks, BucketLinks: bucketlinks,
VersioningDir: versioningDir, VersioningDir: versioningDir,
NewDirPerm: fs.FileMode(dirPerms),
}) })
if err != nil { if err != nil {
return fmt.Errorf("init posix: %v", err) return fmt.Errorf("init posix: %v", err)

View File

@@ -16,6 +16,8 @@ package main
import ( import (
"fmt" "fmt"
"io/fs"
"math"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/versity/versitygw/backend/scoutfs" "github.com/versity/versitygw/backend/scoutfs"
@@ -69,6 +71,14 @@ move interfaces as well as support for tiered filesystems.`,
EnvVars: []string{"VGW_BUCKET_LINKS"}, EnvVars: []string{"VGW_BUCKET_LINKS"},
Destination: &bucketlinks, Destination: &bucketlinks,
}, },
&cli.UintFlag{
Name: "dir-perms",
Usage: "default directory permissions for new directories",
EnvVars: []string{"VGW_DIR_PERMS"},
Destination: &dirPerms,
DefaultText: "0755",
Value: 0755,
},
}, },
} }
} }
@@ -78,11 +88,16 @@ func runScoutfs(ctx *cli.Context) error {
return fmt.Errorf("no directory provided for operation") return fmt.Errorf("no directory provided for operation")
} }
if dirPerms > math.MaxUint32 {
return fmt.Errorf("invalid directory permissions: %d", dirPerms)
}
var opts scoutfs.ScoutfsOpts var opts scoutfs.ScoutfsOpts
opts.GlacierMode = glacier opts.GlacierMode = glacier
opts.ChownUID = chownuid opts.ChownUID = chownuid
opts.ChownGID = chowngid opts.ChownGID = chowngid
opts.BucketLinks = bucketlinks opts.BucketLinks = bucketlinks
opts.NewDirPerm = fs.FileMode(dirPerms)
be, err := scoutfs.New(ctx.Args().Get(0), opts) be, err := scoutfs.New(ctx.Args().Get(0), opts)
if err != nil { if err != nil {