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
This commit is contained in:
Massimo Lusetti 2024-11-02 11:08:29 +01:00
parent e35c6e39d9
commit 92216a10ba
3 changed files with 62 additions and 38 deletions

View file

@ -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.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.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.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 return rv
} }
@ -147,6 +148,12 @@ func (app *restServerApp) runRoot(cmd *cobra.Command, args []string) error {
log.Println("Private repositories disabled") 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() enabledTLS, privateKey, publicKey, err := app.tlsSettings()
if err != nil { if err != nil {
return err return err

View file

@ -15,23 +15,24 @@ import (
// Server encapsulates the rest-server's settings and repo management logic // Server encapsulates the rest-server's settings and repo management logic
type Server struct { type Server struct {
Path string Path string
HtpasswdPath string HtpasswdPath string
Listen string Listen string
Log string Log string
CPUProfile string CPUProfile string
TLSKey string TLSKey string
TLSCert string TLSCert string
TLS bool TLS bool
NoAuth bool NoAuth bool
AppendOnly bool AppendOnly bool
PrivateRepos bool PrivateRepos bool
Prometheus bool Prometheus bool
PrometheusNoAuth bool PrometheusNoAuth bool
Debug bool Debug bool
MaxRepoSize int64 MaxRepoSize int64
PanicOnError bool PanicOnError bool
NoVerifyUpload bool NoVerifyUpload bool
GroupAccessibleRepos bool
htpasswdFile *HtpasswdFile htpasswdFile *HtpasswdFile
quotaManager *quota.Manager quotaManager *quota.Manager
@ -88,12 +89,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Pass the request to the repo.Handler // Pass the request to the repo.Handler
opt := repo.Options{ opt := repo.Options{
AppendOnly: s.AppendOnly, AppendOnly: s.AppendOnly,
Debug: s.Debug, Debug: s.Debug,
QuotaManager: s.quotaManager, // may be nil QuotaManager: s.quotaManager, // may be nil
PanicOnError: s.PanicOnError, PanicOnError: s.PanicOnError,
NoVerifyUpload: s.NoVerifyUpload, NoVerifyUpload: s.NoVerifyUpload,
FsyncWarning: &s.fsyncWarning, FsyncWarning: &s.fsyncWarning,
GroupAccessible: s.GroupAccessibleRepos,
} }
if s.Prometheus { if s.Prometheus {
opt.BlobMetricFunc = makeBlobMetricFunc(username, folderPath) 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 // splitURLPath splits the URL path into a folderPath of the subrepo, and
// a remainder that can be passed to repo.Handler. // a remainder that can be passed to repo.Handler.
// Example: /foo/bar/locks/0123... will be split into: // 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) { func splitURLPath(urlPath string, maxDepth int) (folderPath []string, remainder string) {
if !strings.HasPrefix(urlPath, "/") { if !strings.HasPrefix(urlPath, "/") {
// Really should start with "/" // Really should start with "/"

View file

@ -29,8 +29,6 @@ import (
type Options struct { type Options struct {
AppendOnly bool // if set, delete actions are not allowed AppendOnly bool // if set, delete actions are not allowed
Debug bool Debug bool
DirMode os.FileMode
FileMode os.FileMode
NoVerifyUpload bool NoVerifyUpload bool
// If set, we will panic when an internal server error happens. This // If set, we will panic when an internal server error happens. This
@ -40,6 +38,13 @@ type Options struct {
BlobMetricFunc BlobMetricFunc BlobMetricFunc BlobMetricFunc
QuotaManager *quota.Manager QuotaManager *quota.Manager
FsyncWarning *sync.Once 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 // DefaultDirMode is the file mode used for directory creation if not
@ -50,6 +55,12 @@ const DefaultDirMode os.FileMode = 0700
// overridden in the Options // overridden in the Options
const DefaultFileMode os.FileMode = 0600 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. // New creates a new Handler for a single Restic backup repo.
// path is the full filesystem path to this repo directory. // path is the full filesystem path to this repo directory.
// opt is a set of options. // opt is a set of options.
@ -57,12 +68,15 @@ func New(path string, opt Options) (*Handler, error) {
if path == "" { if path == "" {
return nil, fmt.Errorf("path is required") return nil, fmt.Errorf("path is required")
} }
if opt.DirMode == 0 {
opt.DirMode = DefaultDirMode opt.dirMode = DefaultDirMode
} opt.fileMode = DefaultFileMode
if opt.FileMode == 0 {
opt.FileMode = DefaultFileMode if opt.GroupAccessible {
opt.dirMode = GroupAccessibleDirMode
opt.fileMode = GroupAccessibleFileMode
} }
h := Handler{ h := Handler{
path: path, path: path,
opt: opt, opt: opt,
@ -289,7 +303,7 @@ func (h *Handler) saveConfig(w http.ResponseWriter, r *http.Request) {
} }
cfg := h.getSubPath("config") 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 err != nil && os.IsExist(err) {
if h.opt.Debug { if h.opt.Debug {
log.Print(err) 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") 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) { if os.IsNotExist(err) {
// the error is caused by a missing directory, create it and retry // 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 { if mkdirErr != nil {
log.Print(mkdirErr) log.Print(mkdirErr)
} else { } else {
// try again // try again
tf, err = tempFile(tmpFn, h.opt.FileMode) tf, err = tempFile(tmpFn, h.opt.fileMode)
} }
} }
if err != nil { 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) 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) h.internalServerError(w, err)
return return
} }
for _, d := range ObjectTypes { 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) h.internalServerError(w, err)
return return
} }
@ -764,7 +778,7 @@ func (h *Handler) createRepo(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 256; i++ { for i := 0; i < 256; i++ {
dirPath := filepath.Join(h.path, "data", fmt.Sprintf("%02x", 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) h.internalServerError(w, err)
return return
} }