Make server unix socket file permission configurable via flag UnixSocketPerm (#2763)

* feat(any): Add flag unixsocketperm with default 0017 - #2625

Signed-off-by: johannesengl <hello@johannesengl.com>

* feat(server): Update unix socket file perm based on config - #2625

Signed-off-by: johannesengl <hello@johannesengl.com>

* Fix default value of socket.

* Refactor unix socket file creation.

* Remove misplaced comment

---------

Signed-off-by: johannesengl <hello@johannesengl.com>
Co-authored-by: Caio Cotts <caio@cotts.com.br>
Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Johannes Engl 2024-01-20 20:50:30 +01:00 committed by GitHub
parent 8570773b90
commit 8f03454312
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 78 additions and 6 deletions

View File

@ -183,6 +183,7 @@ func init() {
rootCmd.Flags().IntP("port", "p", viper.GetInt("port"), "HTTP port Navidrome will listen to") rootCmd.Flags().IntP("port", "p", viper.GetInt("port"), "HTTP port Navidrome will listen to")
rootCmd.Flags().String("baseurl", viper.GetString("baseurl"), "base URL to configure Navidrome behind a proxy (ex: /music or http://my.server.com)") rootCmd.Flags().String("baseurl", viper.GetString("baseurl"), "base URL to configure Navidrome behind a proxy (ex: /music or http://my.server.com)")
rootCmd.Flags().String("tlscert", viper.GetString("tlscert"), "optional path to a TLS cert file (enables HTTPS listening)") rootCmd.Flags().String("tlscert", viper.GetString("tlscert"), "optional path to a TLS cert file (enables HTTPS listening)")
rootCmd.Flags().String("unixsocketperm", viper.GetString("unixsocketperm"), "optional file permission for the unix socket")
rootCmd.Flags().String("tlskey", viper.GetString("tlskey"), "optional path to a TLS key file (enables HTTPS listening)") rootCmd.Flags().String("tlskey", viper.GetString("tlskey"), "optional path to a TLS key file (enables HTTPS listening)")
rootCmd.Flags().Duration("sessiontimeout", viper.GetDuration("sessiontimeout"), "how long Navidrome will wait before closing web ui idle sessions") rootCmd.Flags().Duration("sessiontimeout", viper.GetDuration("sessiontimeout"), "how long Navidrome will wait before closing web ui idle sessions")
@ -199,6 +200,7 @@ func init() {
_ = viper.BindPFlag("address", rootCmd.Flags().Lookup("address")) _ = viper.BindPFlag("address", rootCmd.Flags().Lookup("address"))
_ = viper.BindPFlag("port", rootCmd.Flags().Lookup("port")) _ = viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
_ = viper.BindPFlag("tlscert", rootCmd.Flags().Lookup("tlscert")) _ = viper.BindPFlag("tlscert", rootCmd.Flags().Lookup("tlscert"))
_ = viper.BindPFlag("unixsocketperm", rootCmd.Flags().Lookup("unixsocketperm"))
_ = viper.BindPFlag("tlskey", rootCmd.Flags().Lookup("tlskey")) _ = viper.BindPFlag("tlskey", rootCmd.Flags().Lookup("tlskey"))
_ = viper.BindPFlag("baseurl", rootCmd.Flags().Lookup("baseurl")) _ = viper.BindPFlag("baseurl", rootCmd.Flags().Lookup("baseurl"))

View File

@ -21,6 +21,7 @@ type configOptions struct {
ConfigFile string ConfigFile string
Address string Address string
Port int Port int
UnixSocketPerm string
MusicFolder string MusicFolder string
DataFolder string DataFolder string
CacheFolder string CacheFolder string
@ -275,6 +276,7 @@ func init() {
viper.SetDefault("loglevel", "info") viper.SetDefault("loglevel", "info")
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("sessiontimeout", consts.DefaultSessionTimeout) viper.SetDefault("sessiontimeout", consts.DefaultSessionTimeout)
viper.SetDefault("scaninterval", -1) viper.SetDefault("scaninterval", -1)
viper.SetDefault("scanschedule", "@every 1m") viper.SetDefault("scanschedule", "@every 1m")

View File

@ -9,6 +9,7 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"strconv"
"strings" "strings"
"time" "time"
@ -71,13 +72,9 @@ func (s *Server) Run(ctx context.Context, addr string, port int, tlsCert string,
var err error var err error
if strings.HasPrefix(addr, "unix:") { if strings.HasPrefix(addr, "unix:") {
socketPath := strings.TrimPrefix(addr, "unix:") socketPath := strings.TrimPrefix(addr, "unix:")
// Remove the socket file if it already exists listener, err = createUnixSocketFile(socketPath, conf.Server.UnixSocketPerm)
if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("error removing previous unix socket file: %w", err)
}
listener, err = net.Listen("unix", socketPath)
if err != nil { if err != nil {
return fmt.Errorf("error creating unix socket listener: %w", err) return err
} }
} else { } else {
addr = fmt.Sprintf("%s:%d", addr, port) addr = fmt.Sprintf("%s:%d", addr, port)
@ -136,6 +133,28 @@ func (s *Server) Run(ctx context.Context, addr string, port int, tlsCert string,
return nil return nil
} }
func createUnixSocketFile(socketPath string, socketPerm string) (net.Listener, error) {
// Remove the socket file if it already exists
if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("error removing previous unix socket file: %w", err)
}
// Create listener
listener, err := net.Listen("unix", socketPath)
if err != nil {
return nil, fmt.Errorf("error creating unix socket listener: %w", err)
}
// Converts the socketPerm to uint and updates the permission of the unix socket file
perm, err := strconv.ParseUint(socketPerm, 8, 32)
if err != nil {
return nil, fmt.Errorf("error parsing unix socket file permissions: %w", err)
}
err = os.Chmod(socketPath, os.FileMode(perm))
if err != nil {
return nil, fmt.Errorf("error updating permission of unix socket file: %w", err)
}
return listener, nil
}
func (s *Server) initRoutes() { func (s *Server) initRoutes() {
s.appRoot = path.Join(conf.Server.BasePath, consts.URLPathUI) s.appRoot = path.Join(conf.Server.BasePath, consts.URLPathUI)

View File

@ -1,8 +1,11 @@
package server package server
import ( import (
"io/fs"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path/filepath"
"github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
@ -58,3 +61,49 @@ var _ = Describe("AbsoluteURL", func() {
}) })
}) })
}) })
var _ = Describe("createUnixSocketFile", func() {
var socketPath string
BeforeEach(func() {
tempDir, _ := os.MkdirTemp("", "create_unix_socket_file_test")
socketPath = filepath.Join(tempDir, "test.sock")
DeferCleanup(func() {
_ = os.RemoveAll(tempDir)
})
})
When("unixSocketPerm is valid", func() {
It("updates the permission of the unix socket file and returns nil", func() {
_, err := createUnixSocketFile(socketPath, "0777")
fileInfo, _ := os.Stat(socketPath)
actualPermission := fileInfo.Mode().Perm()
Expect(actualPermission).To(Equal(os.FileMode(0777)))
Expect(err).ToNot(HaveOccurred())
})
})
When("unixSocketPerm is invalid", func() {
It("returns an error", func() {
_, err := createUnixSocketFile(socketPath, "invalid")
Expect(err).To(HaveOccurred())
})
})
When("file already exists", func() {
It("recreates the file as a socket with the right permissions", func() {
_, err := os.Create(socketPath)
Expect(err).ToNot(HaveOccurred())
Expect(os.Chmod(socketPath, os.FileMode(0777))).To(Succeed())
_, err = createUnixSocketFile(socketPath, "0600")
Expect(err).ToNot(HaveOccurred())
fileInfo, _ := os.Stat(socketPath)
Expect(fileInfo.Mode().Perm()).To(Equal(os.FileMode(0600)))
Expect(fileInfo.Mode().Type()).To(Equal(fs.ModeSocket))
})
})
})