From 92216a10ba59266233e608d76aaa5e091d454890 Mon Sep 17 00:00:00 2001 From: Massimo Lusetti Date: Sat, 2 Nov 2024 11:08:29 +0100 Subject: [PATCH 1/4] Add group-accessible-repos option The group-accessible-repos option will let filesystem group id be able to access files and dir within the restic repo Default stick with old behaviour to be owner restricted While here make dirMode and fileMode within Options struct private --- cmd/rest-server/main.go | 7 ++++++ handlers.go | 51 ++++++++++++++++++++++------------------- repo/repo.go | 42 ++++++++++++++++++++++----------- 3 files changed, 62 insertions(+), 38 deletions(-) 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 } From f15b03ae9db92a244dcd5dce6372457a9ce6ee82 Mon Sep 17 00:00:00 2001 From: Massimo Lusetti <197125+mlusetti@users.noreply.github.com> Date: Mon, 10 Feb 2025 09:02:57 +0100 Subject: [PATCH 2/4] It's actual access not just read --- cmd/rest-server/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/rest-server/main.go b/cmd/rest-server/main.go index 67ab8a33..d1033700 100644 --- a/cmd/rest-server/main.go +++ b/cmd/rest-server/main.go @@ -69,7 +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") + flags.BoolVar(&rv.Server.GroupAccessibleRepos, "group-accessible-repos", rv.Server.GroupAccessibleRepos, "let filesystem group be able to access repo files") return rv } From f116937e4367c003cb73164c30b9d4f050be919a Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 17 Feb 2025 22:08:48 +0100 Subject: [PATCH 3/4] tadd changelog --- changelog/unreleased/issue-189 | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 changelog/unreleased/issue-189 diff --git a/changelog/unreleased/issue-189 b/changelog/unreleased/issue-189 new file mode 100644 index 00000000..f60bcd58 --- /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 From 6183d6f8ee8c86287b059c8b7c0eb5e33758f7d8 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 17 Feb 2025 22:11:56 +0100 Subject: [PATCH 4/4] fix linter warning --- repo/repo.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/repo/repo.go b/repo/repo.go index 36142781..c389b971 100644 --- a/repo/repo.go +++ b/repo/repo.go @@ -55,10 +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 +// GroupAccessibleDirMode is the 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 +// 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.