diff --git a/changelog/unreleased/issue-189 b/changelog/unreleased/issue-189 new file mode 100644 index 0000000..f60bcd5 --- /dev/null +++ b/changelog/unreleased/issue-189 @@ -0,0 +1,9 @@ +Enhancement: Support group accessible repositories + +Rest-server now supports making repositories accessible to the filesystem group +by setting the `--group-accessible-repos` option. Note that permissions of +existing files are not modified. Use `chmod -R g+rwX /path/to/repo` to make +the repository group-accessible. + +https://github.com/restic/rest-server/issues/189 +https://github.com/restic/rest-server/pull/308 diff --git a/cmd/rest-server/main.go b/cmd/rest-server/main.go index aa921c9..cabc565 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 access repo files") return rv } @@ -149,6 +150,12 @@ func (app *restServerApp) runRoot(_ *cobra.Command, _ []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 1bccc60..cde0637 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) diff --git a/repo/repo.go b/repo/repo.go index cd84929..0e38bb6 100644 --- a/repo/repo.go +++ b/repo/repo.go @@ -28,8 +28,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,6 +37,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 @@ -49,6 +54,14 @@ const DefaultDirMode os.FileMode = 0700 // overridden in the Options const DefaultFileMode os.FileMode = 0600 +// GroupAccessibleDirMode is the file mode used for directory creation when +// group access is enabled +const GroupAccessibleDirMode os.FileMode = 0770 + +// GroupAccessibleFileMode is the 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. @@ -56,12 +69,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, @@ -288,7 +304,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) @@ -554,15 +570,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 { @@ -759,13 +775,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 } @@ -773,7 +789,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 }