diff options
-rw-r--r-- | pkg/atlas/config.go | 82 | ||||
-rw-r--r-- | pkg/atlas/server.go | 10 |
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) |