aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/atlas/config.go82
-rw-r--r--pkg/atlas/server.go10
2 files changed, 92 insertions, 0 deletions
diff --git a/pkg/atlas/config.go b/pkg/atlas/config.go
index c692167..f22327d 100644
--- a/pkg/atlas/config.go
+++ b/pkg/atlas/config.go
@@ -3,7 +3,10 @@ package atlas
import (
"fmt"
+ "io/fs"
+ "os/user"
"reflect"
+ "runtime"
"strconv"
"strings"
"time"
@@ -11,6 +14,8 @@ import (
"github.com/rs/zerolog"
)
+type UIDGID [2]int
+
// Config contains the configuration for Atlas. The env struct tag contains the
// environment variable name and the default value if missing, or empty (if not
// ?=). All string arrays are comma-separated.
@@ -62,6 +67,12 @@ type Config struct {
// The minimum log level for the log file.
LogFileLevel zerolog.Level `env:"ATLAS_LOG_FILE_LEVEL=info"`
+ // The permissions for the log file.
+ LogFileChmod fs.FileMode `env:"ATLAS_LOG_FILE_CHMOD"`
+
+ // The owner for the log file. Not supported on Windows.
+ LogFileChown *UIDGID `env:"ATLAS_LOG_FILE_CHOWN"`
+
// Maps source IP prefixes to another IP (useful for controlling server
// registration IPs when running within a LAN and port forwarding during
// development). Comma-separated list of prefix=ip (example:
@@ -209,6 +220,22 @@ func (c *Config) UnmarshalEnv(es []string, incremental bool) error {
} else {
return fmt.Errorf("env %s (%T): parse %q: %w", key, cvf.Interface(), val, err)
}
+ case fs.FileMode:
+ if val == "" {
+ cvf.Set(reflect.ValueOf(fs.FileMode(0)))
+ } else if v, err := strconv.ParseUint(val, 8, 32); err == nil {
+ cvf.Set(reflect.ValueOf(fs.FileMode(v)))
+ } else {
+ return fmt.Errorf("env %s (%T): parse %q: %w", key, cvf.Interface(), val, err)
+ }
+ case *UIDGID:
+ if val == "" {
+ cvf.Set(reflect.ValueOf((*UIDGID)(nil)))
+ } else if v, err := parseUIDGID(val); err == nil {
+ cvf.Set(reflect.ValueOf(&v))
+ } else {
+ return fmt.Errorf("env %s (%T): parse %q: %w", key, cvf.Interface(), val, err)
+ }
default:
return fmt.Errorf("unhandled type %T (%s)", cvf.Interface(), env)
}
@@ -220,3 +247,58 @@ func (c *Config) UnmarshalEnv(es []string, incremental bool) error {
}
return nil
}
+
+func parseUIDGID(s string) (UIDGID, error) {
+ var u UIDGID
+
+ if runtime.GOOS == "windows" {
+ return u, fmt.Errorf("not supported on windows")
+ }
+ if s == "" {
+ return u, fmt.Errorf("must not be empty")
+ }
+
+ su, sg, hg := strings.Cut(s, ":")
+
+ if su == "" || sg == "" {
+ if x, err := user.Current(); err != nil {
+ return u, fmt.Errorf("get current user: %w", err)
+ } else if uid, err := strconv.ParseInt(x.Uid, 10, 64); err != nil {
+ return u, fmt.Errorf("get current user: parse uid %q: %w", x.Uid, err)
+ } else if gid, err := strconv.ParseInt(x.Gid, 10, 64); err != nil {
+ return u, fmt.Errorf("get current user: parse gid %q: %w", x.Gid, err)
+ } else {
+ u = UIDGID{int(uid), int(gid)}
+ }
+ }
+ if su != "" {
+ if uid, err := strconv.ParseInt(su, 10, 64); err == nil {
+ u[0] = int(uid)
+ } else if x, err := user.Lookup(su); err != nil {
+ return u, fmt.Errorf("get user: %w", err)
+ } else if uid, err := strconv.ParseInt(x.Uid, 10, 64); err != nil {
+ return u, fmt.Errorf("get user: parse uid %q: %w", x.Uid, err)
+ } else {
+ if !hg && sg == "" && x.Gid != "" {
+ if gid, err := strconv.ParseInt(x.Gid, 10, 64); err != nil {
+ return u, fmt.Errorf("get user: parse gid %q: %w", x.Gid, err)
+ } else {
+ u[1] = int(gid)
+ }
+ }
+ u[0] = int(uid)
+ }
+ }
+ if sg != "" {
+ if gid, err := strconv.ParseInt(sg, 10, 64); err == nil {
+ u[1] = int(gid)
+ } else if x, err := user.LookupGroup(sg); err != nil {
+ return u, fmt.Errorf("lookup group: %w", err)
+ } else if gid, err := strconv.ParseInt(x.Gid, 10, 64); err != nil {
+ return u, fmt.Errorf("lookup group: parse gid %q: %w", x.Gid, err)
+ } else {
+ u[1] = int(gid)
+ }
+ }
+ return u, nil
+}
diff --git a/pkg/atlas/server.go b/pkg/atlas/server.go
index 44ba405..c65aeb7 100644
--- a/pkg/atlas/server.go
+++ b/pkg/atlas/server.go
@@ -355,6 +355,16 @@ func configureLogging(c *Config) (l zerolog.Logger, reopen func(), err error) {
o.Close()
}
if f, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666); err == nil {
+ if c.LogFileChown != nil {
+ if err := f.Chown((*c.LogFileChown)[0], (*c.LogFileChown)[1]); err != nil {
+ fmt.Fprintf(os.Stderr, "error: chown log file: %v\n", err)
+ }
+ }
+ if c.LogFileChmod != 0 {
+ if err := f.Chmod(c.LogFileChmod); err != nil {
+ fmt.Fprintf(os.Stderr, "error: chmod log file: %v\n", err)
+ }
+ }
return f
} else {
fmt.Fprintf(os.Stderr, "error: failed to open log file: %v\n", err)