diff --git a/changelog/0.11.0_2022-02-10/issue-189 b/changelog/0.11.0_2022-02-10/issue-189 new file mode 100644 index 00000000..2142a4d7 --- /dev/null +++ b/changelog/0.11.0_2022-02-10/issue-189 @@ -0,0 +1,6 @@ +Feature: Implement boolean flag "group-readable-repositores" + +A CLI option to write dirs and files with a group readable permission grant + +https://github.com/restic/rest-server/issues/189 +https://github.com/restic/rest-server/pull/190 diff --git a/cmd/rest-server/main.go b/cmd/rest-server/main.go index eef0cd41..4a9f8cb8 100644 --- a/cmd/rest-server/main.go +++ b/cmd/rest-server/main.go @@ -60,6 +60,7 @@ func init() { flags.BoolVar(&server.PrivateRepos, "private-repos", server.PrivateRepos, "users can only access their private repo") flags.BoolVar(&server.Prometheus, "prometheus", server.Prometheus, "enable Prometheus metrics") flags.BoolVar(&server.PrometheusNoAuth, "prometheus-no-auth", server.PrometheusNoAuth, "disable auth for Prometheus /metrics endpoint") + flags.BoolVar(&server.GroupAccessibleRepos, "group-accessible-repositories", server.GroupAccessibleRepos, "make filesystem repo files group readable") } var version = "0.12.1-dev" @@ -132,6 +133,10 @@ func runRoot(cmd *cobra.Command, args []string) error { log.Println("Private repositories disabled") } + if server.GroupAccessibleRepos { + log.Println("Repositories are group accessible") + } + enabledTLS, privateKey, publicKey, err := tlsSettings() if err != nil { return err diff --git a/handlers.go b/handlers.go index 8163ccfa..2d3c814c 100644 --- a/handlers.go +++ b/handlers.go @@ -15,23 +15,24 @@ import ( // Server encapsulates the rest-server's settings and repo management logic type Server struct { - Path string - HtpasswdPath string - Listen string - Log string - CPUProfile string - TLSKey string - TLSCert string - TLS bool - NoAuth bool - AppendOnly bool - PrivateRepos bool - Prometheus bool - PrometheusNoAuth bool - Debug bool - MaxRepoSize int64 - PanicOnError bool - NoVerifyUpload bool + Path string + HtpasswdPath string + Listen string + Log string + CPUProfile string + TLSKey string + TLSCert string + TLS bool + NoAuth bool + AppendOnly bool + PrivateRepos bool + Prometheus bool + PrometheusNoAuth bool + Debug bool + MaxRepoSize int64 + PanicOnError bool + NoVerifyUpload bool + GroupAccessibleRepos bool htpasswdFile *HtpasswdFile quotaManager *quota.Manager @@ -93,6 +94,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { QuotaManager: s.quotaManager, // may be nil PanicOnError: s.PanicOnError, NoVerifyUpload: s.NoVerifyUpload, + GroupReadable: s.GroupAccessibleRepos, FsyncWarning: &s.fsyncWarning, } if s.Prometheus { @@ -158,7 +160,8 @@ func join(base string, names ...string) (string, error) { // splitURLPath splits the URL path into a folderPath of the subrepo, and // a remainder that can be passed to repo.Handler. // Example: /foo/bar/locks/0123... will be split into: -// ["foo", "bar"] and "/locks/0123..." +// +// ["foo", "bar"] and "/locks/0123..." func splitURLPath(urlPath string, maxDepth int) (folderPath []string, remainder string) { if !strings.HasPrefix(urlPath, "/") { // Really should start with "/" diff --git a/repo/repo.go b/repo/repo.go index d04879aa..6b8a0fec 100644 --- a/repo/repo.go +++ b/repo/repo.go @@ -29,8 +29,6 @@ import ( type Options struct { AppendOnly bool // if set, delete actions are not allowed Debug bool - DirMode os.FileMode - FileMode os.FileMode NoVerifyUpload bool // If set, we will panic when an internal server error happens. This @@ -39,17 +37,32 @@ type Options struct { BlobMetricFunc BlobMetricFunc QuotaManager *quota.Manager - FsyncWarning *sync.Once + FsyncWarning *sync.Once + + // If set, we will panic when an internal server error happens. This + // makes it easier to debug such errors. + GroupReadable bool + + dirMode os.FileMode + fileMode os.FileMode } -// DefaultDirMode is the file mode used for directory creation if not +// DefaultGroupReadable shows if files and dirs written should be group readable // overridden in the Options +const DefaultGroupReadable = false + +// DefaultDirMode is the file mode used for directory creation by default const DefaultDirMode os.FileMode = 0700 -// DefaultFileMode is the file mode used for file creation if not -// overridden in the Options +// DefaultFileMode is the file mode used for file creation by default const DefaultFileMode os.FileMode = 0600 +// GroupReadableDirMode is the file mode used for directory creation if group readable +const GroupReadableDirMode os.FileMode = 0750 + +// GroupReadableFileMode is the file mode used for directory creation if group readable +const GroupReadableFileMode os.FileMode = 0640 + // New creates a new Handler for a single Restic backup repo. // path is the full filesystem path to this repo directory. // opt is a set of options. @@ -57,11 +70,18 @@ func New(path string, opt Options) (*Handler, error) { if path == "" { return nil, fmt.Errorf("path is required") } - if opt.DirMode == 0 { - opt.DirMode = DefaultDirMode - } - if opt.FileMode == 0 { - opt.FileMode = DefaultFileMode + // if opt.dirMode == 0 { + // opt.dirMode = DefaultDirMode + // } + // if opt.fileMode == 0 { + // opt.fileMode = DefaultFileMode + // } + if opt.GroupReadable { + opt.dirMode = GroupReadableDirMode + opt.fileMode = GroupReadableFileMode + } else { + opt.dirMode = DefaultDirMode + opt.fileMode = DefaultFileMode } h := Handler{ path: path, @@ -289,7 +309,7 @@ func (h *Handler) saveConfig(w http.ResponseWriter, r *http.Request) { } cfg := h.getSubPath("config") - f, err := os.OpenFile(cfg, os.O_CREATE|os.O_WRONLY|os.O_EXCL, h.opt.FileMode) + f, err := os.OpenFile(cfg, os.O_CREATE|os.O_WRONLY|os.O_EXCL, h.opt.fileMode) if err != nil && os.IsExist(err) { if h.opt.Debug { log.Print(err) @@ -545,15 +565,15 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) { } tmpFn := filepath.Join(filepath.Dir(path), objectID+".rest-server-temp") - tf, err := tempFile(tmpFn, h.opt.FileMode) + tf, err := tempFile(tmpFn, h.opt.fileMode) if os.IsNotExist(err) { // the error is caused by a missing directory, create it and retry - mkdirErr := os.MkdirAll(filepath.Dir(path), h.opt.DirMode) + mkdirErr := os.MkdirAll(filepath.Dir(path), h.opt.dirMode) if mkdirErr != nil { log.Print(mkdirErr) } else { // try again - tf, err = tempFile(tmpFn, h.opt.FileMode) + tf, err = tempFile(tmpFn, h.opt.fileMode) } } if err != nil { @@ -750,13 +770,13 @@ func (h *Handler) createRepo(w http.ResponseWriter, r *http.Request) { log.Printf("Creating repository directories in %s\n", h.path) - if err := os.MkdirAll(h.path, h.opt.DirMode); err != nil { + if err := os.MkdirAll(h.path, h.opt.dirMode); err != nil { h.internalServerError(w, err) return } for _, d := range ObjectTypes { - if err := os.Mkdir(filepath.Join(h.path, d), h.opt.DirMode); err != nil && !os.IsExist(err) { + if err := os.Mkdir(filepath.Join(h.path, d), h.opt.dirMode); err != nil && !os.IsExist(err) { h.internalServerError(w, err) return } @@ -764,7 +784,7 @@ func (h *Handler) createRepo(w http.ResponseWriter, r *http.Request) { for i := 0; i < 256; i++ { dirPath := filepath.Join(h.path, "data", fmt.Sprintf("%02x", i)) - if err := os.Mkdir(dirPath, h.opt.DirMode); err != nil && !os.IsExist(err) { + if err := os.Mkdir(dirPath, h.opt.dirMode); err != nil && !os.IsExist(err) { h.internalServerError(w, err) return }