From 9eb888e5f9cb8f47ee818b89024f23a5992a4261 Mon Sep 17 00:00:00 2001 From: Felicitas Pojtinger Date: Wed, 5 Jan 2022 03:50:37 +0100 Subject: [PATCH] feat: Add read-only flag to FTP server --- cmd/stfs/cmd/serve_ftp.go | 3 ++ cmd/stfs/cmd/serve_http.go | 1 + examples/fs/main.go | 1 + pkg/fs/filesystem.go | 102 +++++++++++++++++++++++++++++-------- 4 files changed, 86 insertions(+), 21 deletions(-) diff --git a/cmd/stfs/cmd/serve_ftp.go b/cmd/stfs/cmd/serve_ftp.go index 1eee308..b896f9e 100644 --- a/cmd/stfs/cmd/serve_ftp.go +++ b/cmd/stfs/cmd/serve_ftp.go @@ -37,6 +37,7 @@ const ( signatureRecipientFlag = "signature-recipient" cacheWriteFlag = "cache-write-type" + readOnlyFlag = "read-only" ) var ( @@ -197,6 +198,7 @@ var serveFTPCmd = &cobra.Command{ ) }, true, // FTP needs read permission for `STOR` command even if O_WRONLY is set + viper.GetBool(readOnlyFlag), func(hdr *config.Header) { jsonLogger.Trace("Header transform", hdr) @@ -313,6 +315,7 @@ func init() { serveFTPCmd.PersistentFlags().StringP(cacheWriteFlag, "q", config.WriteCacheTypeFile, fmt.Sprintf("Write cache to use (default %v, available are %v)", config.WriteCacheTypeFile, config.KnownWriteCacheTypes)) serveFTPCmd.PersistentFlags().DurationP(cacheDurationFlag, "u", time.Hour, "Duration until cache is invalidated") serveFTPCmd.PersistentFlags().StringP(cacheDirFlag, "w", cacheDir, "Directory to use if dir cache is enabled") + serveFTPCmd.PersistentFlags().BoolP(readOnlyFlag, "j", false, "Block all write operations") viper.AutomaticEnv() diff --git a/cmd/stfs/cmd/serve_http.go b/cmd/stfs/cmd/serve_http.go index 410b51e..e03e740 100644 --- a/cmd/stfs/cmd/serve_http.go +++ b/cmd/stfs/cmd/serve_http.go @@ -131,6 +131,7 @@ var serveHTTPCmd = &cobra.Command{ "", // We never write nil, // We never write false, // We never write + true, // We never write func(hdr *config.Header) { jsonLogger.Trace("Header transform", hdr) diff --git a/examples/fs/main.go b/examples/fs/main.go index 08de3df..aff0c41 100644 --- a/examples/fs/main.go +++ b/examples/fs/main.go @@ -144,6 +144,7 @@ func main() { ) }, false, + false, func(hdr *config.Header) { l.Trace("Header transform", hdr) diff --git a/pkg/fs/filesystem.go b/pkg/fs/filesystem.go index 379cc1a..9f536a2 100644 --- a/pkg/fs/filesystem.go +++ b/pkg/fs/filesystem.go @@ -30,6 +30,7 @@ type STFS struct { compressionLevel string getFileBuffer func() (cache.WriteCache, func() error, error) + readOnly bool ignoreReadWritePermissions bool ioLock sync.Mutex @@ -47,6 +48,7 @@ func NewSTFS( compressionLevel string, getFileBuffer func() (cache.WriteCache, func() error, error), ignorePermissionFlags bool, + readOnly bool, onHeader func(hdr *config.Header), log logging.StructuredLogger, @@ -60,6 +62,7 @@ func NewSTFS( compressionLevel: compressionLevel, getFileBuffer: getFileBuffer, ignoreReadWritePermissions: ignorePermissionFlags, + readOnly: readOnly, onHeader: onHeader, log: log, @@ -82,6 +85,10 @@ func (f *STFS) Create(name string) (afero.File, error) { "name": name, }) + if f.readOnly { + return nil, os.ErrPermission + } + return f.OpenFile(name, os.O_CREATE|os.O_RDWR, 0666) } @@ -91,6 +98,10 @@ func (f *STFS) mknodeWithoutLocking(dir bool, name string, perm os.FileMode, ove "perm": perm, }) + if f.readOnly { + return os.ErrPermission + } + usr, err := user.Current() if err != nil { return err @@ -171,6 +182,10 @@ func (f *STFS) MkdirRoot(name string, perm os.FileMode) error { "perm": perm, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock() @@ -192,6 +207,10 @@ func (f *STFS) Mkdir(name string, perm os.FileMode) error { "perm": perm, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock() @@ -204,6 +223,10 @@ func (f *STFS) MkdirAll(path string, perm os.FileMode) error { "perm": perm, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock() @@ -244,30 +267,36 @@ func (f *STFS) OpenFile(name string, flag int, perm os.FileMode) (afero.File, er defer f.ioLock.Unlock() flags := &ifs.FileFlags{} - if flag&os.O_RDONLY != 0 { - flags.Read = true - } + if f.readOnly { + if flag&os.O_RDONLY != 0 || flag&os.O_RDWR != 0 || f.ignoreReadWritePermissions { + flags.Read = true + } + } else { + if flag&os.O_RDONLY != 0 { + flags.Read = true + } - if flag&os.O_WRONLY != 0 { - flags.Write = true - } + if flag&os.O_WRONLY != 0 { + flags.Write = true + } - if flag&os.O_RDWR != 0 { - flags.Read = true - flags.Write = true - } + if flag&os.O_RDWR != 0 { + flags.Read = true + flags.Write = true + } - if f.ignoreReadWritePermissions { - flags.Read = true - flags.Write = true - } + if f.ignoreReadWritePermissions { + flags.Read = true + flags.Write = true + } - if flag&os.O_APPEND != 0 { - flags.Append = true - } + if flag&os.O_APPEND != 0 { + flags.Append = true + } - if flag&os.O_TRUNC != 0 { - flags.Truncate = true + if flag&os.O_TRUNC != 0 { + flags.Truncate = true + } } hdr, err := inventory.Stat( @@ -280,7 +309,7 @@ func (f *STFS) OpenFile(name string, flag int, perm os.FileMode) (afero.File, er ) if err != nil { if err == sql.ErrNoRows { - if flag&os.O_CREATE != 0 && flag&os.O_EXCL == 0 { + if !f.readOnly && flag&os.O_CREATE != 0 && flag&os.O_EXCL == 0 { if err := f.mknodeWithoutLocking(false, name, perm, false, ""); err != nil { return nil, err } @@ -299,7 +328,6 @@ func (f *STFS) OpenFile(name string, flag int, perm os.FileMode) (afero.File, er } else { return nil, os.ErrNotExist } - } else { return nil, err } @@ -332,6 +360,10 @@ func (f *STFS) Remove(name string) error { "name": name, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock() @@ -343,6 +375,10 @@ func (f *STFS) RemoveAll(path string) error { "path": path, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock() @@ -355,6 +391,10 @@ func (f *STFS) Rename(oldname, newname string) error { "newname": newname, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock() @@ -389,6 +429,10 @@ func (f *STFS) Stat(name string) (os.FileInfo, error) { } func (f *STFS) updateMetadata(hdr *tar.Header) error { + if f.readOnly { + return os.ErrPermission + } + done := false if _, err := f.writeOps.Update( func() (config.FileConfig, error) { @@ -420,6 +464,10 @@ func (f *STFS) Chmod(name string, mode os.FileMode) error { "name": mode, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock() @@ -451,6 +499,10 @@ func (f *STFS) Chown(name string, uid, gid int) error { "gid": gid, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock() @@ -483,6 +535,10 @@ func (f *STFS) Chtimes(name string, atime time.Time, mtime time.Time) error { "mtime": mtime, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock() @@ -549,6 +605,10 @@ func (f *STFS) SymlinkIfPossible(oldname, newname string) error { "newname": newname, }) + if f.readOnly { + return os.ErrPermission + } + f.ioLock.Lock() defer f.ioLock.Unlock()