mirror of
https://github.com/navidrome/navidrome.git
synced 2026-04-26 10:30:46 +00:00
feat(server): add EnforceNonRootUser config option to exit early if started as root (#5373)
Some checks are pending
Pipeline: Test, Lint, Build / Get version info (push) Waiting to run
Pipeline: Test, Lint, Build / Lint Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (Windows) (push) Waiting to run
Pipeline: Test, Lint, Build / Test JS code (push) Waiting to run
Pipeline: Test, Lint, Build / Lint i18n files (push) Waiting to run
Pipeline: Test, Lint, Build / Check Docker configuration (push) Waiting to run
Pipeline: Test, Lint, Build / Build (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-1 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-2 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-3 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-4 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-5 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-6 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-7 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-8 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-9 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-10 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to GHCR (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build Windows installers (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Package/Release (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Blocked by required conditions
Some checks are pending
Pipeline: Test, Lint, Build / Get version info (push) Waiting to run
Pipeline: Test, Lint, Build / Lint Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (Windows) (push) Waiting to run
Pipeline: Test, Lint, Build / Test JS code (push) Waiting to run
Pipeline: Test, Lint, Build / Lint i18n files (push) Waiting to run
Pipeline: Test, Lint, Build / Check Docker configuration (push) Waiting to run
Pipeline: Test, Lint, Build / Build (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-1 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-2 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-3 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-4 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-5 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-6 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-7 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-8 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-9 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-10 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to GHCR (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build Windows installers (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Package/Release (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Blocked by required conditions
* feat(config): Add EnforceNonRootUser config option to exit early if started as root Signed-off-by: Aengus Walton <ventolin@gmail.com> * Move validateEnforceNonRootUser check to directly after parsing the config * Ensure the data directory hasn't been created in test --------- Signed-off-by: Aengus Walton <ventolin@gmail.com> Co-authored-by: Deluan Quintão <deluan@navidrome.org>
This commit is contained in:
parent
2954c052f5
commit
44e63596a0
3 changed files with 79 additions and 0 deletions
|
|
@ -27,6 +27,7 @@ type configOptions struct {
|
||||||
Address string
|
Address string
|
||||||
Port int
|
Port int
|
||||||
UnixSocketPerm string
|
UnixSocketPerm string
|
||||||
|
EnforceNonRootUser bool
|
||||||
MusicFolder string
|
MusicFolder string
|
||||||
DataFolder string
|
DataFolder string
|
||||||
CacheFolder string
|
CacheFolder string
|
||||||
|
|
@ -273,6 +274,12 @@ var logFatal = func(args ...any) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var getEUID = os.Geteuid
|
||||||
|
|
||||||
|
var currentGOOS = func() string {
|
||||||
|
return runtime.GOOS
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Server = &configOptions{}
|
Server = &configOptions{}
|
||||||
hooks []func()
|
hooks []func()
|
||||||
|
|
@ -303,6 +310,11 @@ func Load(noConfigDump bool) {
|
||||||
logFatal("Error parsing config:", err)
|
logFatal("Error parsing config:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate non-root user early, before any filesystem operations
|
||||||
|
if err := validateEnforceNonRootUser(); err != nil {
|
||||||
|
logFatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(Server.DataFolder, os.ModePerm)
|
err = os.MkdirAll(Server.DataFolder, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFatal("Error creating data path:", err)
|
logFatal("Error creating data path:", err)
|
||||||
|
|
@ -599,6 +611,18 @@ func validateMaxImageUploadSize() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateEnforceNonRootUser() error {
|
||||||
|
if !Server.EnforceNonRootUser || currentGOOS() == "windows" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if getEUID() == 0 {
|
||||||
|
return fmt.Errorf("EnforceNonRootUser is enabled but Navidrome is running as root")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func validateScanSchedule() error {
|
func validateScanSchedule() error {
|
||||||
if Server.Scanner.Schedule == "0" || Server.Scanner.Schedule == "" {
|
if Server.Scanner.Schedule == "0" || Server.Scanner.Schedule == "" {
|
||||||
Server.Scanner.Schedule = ""
|
Server.Scanner.Schedule = ""
|
||||||
|
|
@ -698,6 +722,7 @@ func setViperDefaults() {
|
||||||
viper.SetDefault("address", "0.0.0.0")
|
viper.SetDefault("address", "0.0.0.0")
|
||||||
viper.SetDefault("port", 4533)
|
viper.SetDefault("port", 4533)
|
||||||
viper.SetDefault("unixsocketperm", "0660")
|
viper.SetDefault("unixsocketperm", "0660")
|
||||||
|
viper.SetDefault("enforcenonrootuser", false)
|
||||||
viper.SetDefault("sessiontimeout", consts.DefaultSessionTimeout)
|
viper.SetDefault("sessiontimeout", consts.DefaultSessionTimeout)
|
||||||
viper.SetDefault("baseurl", "")
|
viper.SetDefault("baseurl", "")
|
||||||
viper.SetDefault("tlscert", "")
|
viper.SetDefault("tlscert", "")
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,49 @@ var _ = Describe("Configuration", func() {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("EnforceNonRootUser", func() {
|
||||||
|
It("defaults to false", func() {
|
||||||
|
conf.Load(true)
|
||||||
|
|
||||||
|
Expect(conf.Server.EnforceNonRootUser).To(BeFalse())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("allows startup for non-root users when enabled", func() {
|
||||||
|
DeferCleanup(conf.SetRuntimeInfoForTest("linux", 1000))
|
||||||
|
viper.Set("enforcenonrootuser", true)
|
||||||
|
|
||||||
|
conf.Load(true)
|
||||||
|
|
||||||
|
Expect(conf.Server.EnforceNonRootUser).To(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("exits when enabled and running as root without having created a data folder", func() {
|
||||||
|
// Create a path that doesn't exist yet
|
||||||
|
tempBase := GinkgoT().TempDir()
|
||||||
|
nonExistentDataFolder := filepath.Join(tempBase, "nonexistent", "data")
|
||||||
|
DeferCleanup(conf.SetRuntimeInfoForTest("linux", 0))
|
||||||
|
viper.Set("enforcenonrootuser", true)
|
||||||
|
viper.Set("datafolder", nonExistentDataFolder)
|
||||||
|
|
||||||
|
// Attempt to load config as root user - should fail before creating directories
|
||||||
|
Expect(func() {
|
||||||
|
conf.Load(true)
|
||||||
|
}).To(PanicWith(ContainSubstring("EnforceNonRootUser is enabled but Navidrome is running as root")))
|
||||||
|
|
||||||
|
// Verify that the data folder was NOT created
|
||||||
|
Expect(nonExistentDataFolder).ToNot(BeAnExistingFile())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("is a no-op on non-unix platforms", func() {
|
||||||
|
DeferCleanup(conf.SetRuntimeInfoForTest("windows", 0))
|
||||||
|
viper.Set("enforcenonrootuser", true)
|
||||||
|
|
||||||
|
conf.Load(true)
|
||||||
|
|
||||||
|
Expect(conf.Server.EnforceNonRootUser).To(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
DescribeTable("should load configuration from",
|
DescribeTable("should load configuration from",
|
||||||
func(format string) {
|
func(format string) {
|
||||||
filename := filepath.Join("testdata", "cfg."+format)
|
filename := filepath.Join("testdata", "cfg."+format)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,17 @@ var ToPascalCase = toPascalCase
|
||||||
|
|
||||||
var ValidateMaxImageUploadSize = validateMaxImageUploadSize
|
var ValidateMaxImageUploadSize = validateMaxImageUploadSize
|
||||||
|
|
||||||
|
func SetRuntimeInfoForTest(goos string, euid int) func() {
|
||||||
|
oldGOOS := currentGOOS
|
||||||
|
oldEUID := getEUID
|
||||||
|
currentGOOS = func() string { return goos }
|
||||||
|
getEUID = func() int { return euid }
|
||||||
|
return func() {
|
||||||
|
currentGOOS = oldGOOS
|
||||||
|
getEUID = oldEUID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func SetLogFatal(f func(...any)) func() {
|
func SetLogFatal(f func(...any)) func() {
|
||||||
old := logFatal
|
old := logFatal
|
||||||
logFatal = f
|
logFatal = f
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue