diff --git a/cmd/rest-server/main.go b/cmd/rest-server/main.go index 0bdd1ebc..67ab8a33 100644 --- a/cmd/rest-server/main.go +++ b/cmd/rest-server/main.go @@ -69,6 +69,7 @@ func newRestServerApp() *restServerApp { flags.BoolVar(&rv.Server.PrivateRepos, "private-repos", rv.Server.PrivateRepos, "users can only access their private repo") flags.BoolVar(&rv.Server.Prometheus, "prometheus", rv.Server.Prometheus, "enable Prometheus metrics") flags.BoolVar(&rv.Server.PrometheusNoAuth, "prometheus-no-auth", rv.Server.PrometheusNoAuth, "disable auth for Prometheus /metrics endpoint") + flags.BoolVar(&rv.Server.GroupAccessibleRepos, "group-accessible-repos", rv.Server.GroupAccessibleRepos, "let filesystem group be able to read repo files") return rv } @@ -147,6 +148,12 @@ func (app *restServerApp) runRoot(cmd *cobra.Command, args []string) error { log.Println("Private repositories disabled") } + if app.Server.GroupAccessibleRepos { + log.Println("Group accessible repos enabled") + } else { + log.Println("Group accessible repos disabled") + } + enabledTLS, privateKey, publicKey, err := app.tlsSettings() if err != nil { return err diff --git a/handlers.go b/handlers.go index 8163ccfa..cde06374 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 @@ -88,12 +89,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Pass the request to the repo.Handler opt := repo.Options{ - AppendOnly: s.AppendOnly, - Debug: s.Debug, - QuotaManager: s.quotaManager, // may be nil - PanicOnError: s.PanicOnError, - NoVerifyUpload: s.NoVerifyUpload, - FsyncWarning: &s.fsyncWarning, + AppendOnly: s.AppendOnly, + Debug: s.Debug, + QuotaManager: s.quotaManager, // may be nil + PanicOnError: s.PanicOnError, + NoVerifyUpload: s.NoVerifyUpload, + FsyncWarning: &s.fsyncWarning, + GroupAccessible: s.GroupAccessibleRepos, } if s.Prometheus { opt.BlobMetricFunc = makeBlobMetricFunc(username, folderPath) @@ -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..36142781 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 @@ -40,6 +38,13 @@ type Options struct { BlobMetricFunc BlobMetricFunc QuotaManager *quota.Manager FsyncWarning *sync.Once + + // If set makes files group accessible + GroupAccessible bool + + // Defaults dir and file mode + dirMode os.FileMode + fileMode os.FileMode } // DefaultDirMode is the file mode used for directory creation if not @@ -50,6 +55,12 @@ const DefaultDirMode os.FileMode = 0700 // overridden in the Options const DefaultFileMode os.FileMode = 0600 +// File mode used for directory creation when group access is enabled +const GroupAccessibleDirMode os.FileMode = 0770 + +// File mode used for file creation when group access is enabled +const GroupAccessibleFileMode os.FileMode = 0660 + // 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,12 +68,15 @@ 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 + + opt.dirMode = DefaultDirMode + opt.fileMode = DefaultFileMode + + if opt.GroupAccessible { + opt.dirMode = GroupAccessibleDirMode + opt.fileMode = GroupAccessibleFileMode } + h := Handler{ path: path, opt: opt, @@ -289,7 +303,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 +559,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 +764,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 +778,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 }