diff --git a/cmd/gonic/gonic.go b/cmd/gonic/gonic.go index 0d0e5099..9f171de1 100644 --- a/cmd/gonic/gonic.go +++ b/cmd/gonic/gonic.go @@ -41,6 +41,7 @@ import ( "go.senan.xyz/gonic/listenbrainz" "go.senan.xyz/gonic/playlist" "go.senan.xyz/gonic/podcast" + "go.senan.xyz/gonic/sandbox" "go.senan.xyz/gonic/scanner" "go.senan.xyz/gonic/scrobble" "go.senan.xyz/gonic/server/ctrladmin" @@ -51,6 +52,7 @@ import ( ) func main() { + sandbox.Init () confListenAddr := flag.String("listen-addr", "0.0.0.0:4747", "listen address (optional)") confTLSCert := flag.String("tls-cert", "", "path to TLS certificate (optional)") @@ -98,7 +100,11 @@ func main() { flag.Parse() flagconf.ParseEnv() - flagconf.ParseConfig(*confConfigPath) + + if *confConfigPath != "" { + sandbox.ReadOnlyPath(*confConfigPath); + flagconf.ParseConfig(*confConfigPath) + } if *confShowVersion { fmt.Printf("v%s\n", gonic.Version) @@ -115,17 +121,21 @@ func main() { var err error for i, confMusicPath := range confMusicPaths { + sandbox.ReadOnlyPath (confMusicPath.path) if confMusicPaths[i].path, err = validatePath(confMusicPath.path); err != nil { log.Fatalf("checking music dir %q: %v", confMusicPath.path, err) } } + sandbox.ReadWriteCreatePath (*confPodcastPath) if *confPodcastPath, err = validatePath(*confPodcastPath); err != nil { log.Fatalf("checking podcast directory: %v", err) } + sandbox.ReadWriteCreatePath (*confCachePath) if *confCachePath, err = validatePath(*confCachePath); err != nil { log.Fatalf("checking cache directory: %v", err) } + sandbox.ReadWriteCreatePath (*confPlaylistsPath) if *confPlaylistsPath, err = validatePath(*confPlaylistsPath); err != nil { log.Fatalf("checking playlist directory: %v", err) } @@ -156,6 +166,14 @@ func main() { log.Panicf("error migrating database: %v\n", err) } + + if *confTLSCert != "" && *confTLSKey != "" { + sandbox.ReadOnlyPath(*confTLSCert); + sandbox.ReadOnlyPath(*confTLSKey); + } + + sandbox.AllPathsAdded () + var musicPaths []ctrlsubsonic.MusicPath for _, pa := range confMusicPaths { musicPaths = append(musicPaths, ctrlsubsonic.MusicPath{Alias: pa.alias, Path: pa.path}) diff --git a/db/db.go b/db/db.go index ab5a9485..677a730b 100644 --- a/db/db.go +++ b/db/db.go @@ -15,6 +15,8 @@ import ( "github.com/jinzhu/gorm" "github.com/mattn/go-sqlite3" + "go.senan.xyz/gonic/sandbox" + // TODO: remove this dep "go.senan.xyz/gonic/server/ctrlsubsonic/specid" ) @@ -48,6 +50,10 @@ func New(path string, options url.Values) (*DB, error) { Scheme: "file", Opaque: path, } + sandbox.ReadWriteCreatePath (path) + sandbox.ReadWriteCreatePath (path + "-wal") + sandbox.ReadWriteCreatePath (path + "-shm") + sandbox.ReadWriteCreatePath (path + "-journal") url.RawQuery = options.Encode() db, err := gorm.Open("sqlite3", url.String()) if err != nil { diff --git a/db/migrations.go b/db/migrations.go index 447bfdf6..a4ba8a6e 100644 --- a/db/migrations.go +++ b/db/migrations.go @@ -14,6 +14,7 @@ import ( "github.com/jinzhu/gorm" "go.senan.xyz/gonic/fileutil" "go.senan.xyz/gonic/playlist" + "go.senan.xyz/gonic/sandbox" "go.senan.xyz/gonic/server/ctrlsubsonic/specid" "gopkg.in/gormigrate.v1" ) @@ -734,7 +735,9 @@ func backupDBPre016(tx *gorm.DB, ctx MigrationContext) error { if !ctx.Production { return nil } - return Dump(context.Background(), tx, fmt.Sprintf("%s.%d.bak", ctx.DBPath, time.Now().Unix())) + backupPath := fmt.Sprintf("%s.%d.bak", ctx.DBPath, time.Now().Unix()) + sandbox.ReadWriteCreatePath(backupPath) + return Dump(context.Background(), tx, backupPath) } func migrateAlbumTagArtistString(tx *gorm.DB, _ MigrationContext) error { diff --git a/sandbox/none.go b/sandbox/none.go new file mode 100644 index 00000000..a1f4a3ea --- /dev/null +++ b/sandbox/none.go @@ -0,0 +1,18 @@ +// +build !openbsd + +package sandbox + +func Init () { +} + +func ReadOnlyPath (path string) { +} + +func ReadWritePath (path string) { +} + +func ReadWriteCreatePath (path string) { +} + +func AllPathsAdded () { +} diff --git a/sandbox/sandbox_openbsd.go b/sandbox/sandbox_openbsd.go new file mode 100644 index 00000000..3953f553 --- /dev/null +++ b/sandbox/sandbox_openbsd.go @@ -0,0 +1,63 @@ +package sandbox + +import ( + "os/exec" + "log" + + "golang.org/x/sys/unix" +) + +func Init () { + if err := unix.PledgePromises("stdio rpath cpath wpath flock inet unveil dns proc exec fattr"); err != nil { + log.Fatalf ("failed to pledge: %v", err) + } + // find the transcoding and jukebox paths before doing any other unveils + // otherwise looking for it will fail + ffmpegPath, ffmpegErr := exec.LookPath("ffmpeg") + mpvPath, mpvErr := exec.LookPath("mpv") + if ffmpegErr == nil || mpvErr == nil { + if ffmpegErr == nil { + ExecPath(ffmpegPath) + } + if mpvErr == nil { + ExecPath(mpvPath) + } + } else { + // we can restrict our permissions + if err := unix.PledgePromises("stdio rpath cpath wpath flock inet unveil dns"); err != nil { + log.Fatalf ("failed to pledge: %v", err) + } + } + // needed to enable certificate validation + ReadOnlyPath ("/etc/ssl/cert.pem") +} + +func ExecPath (path string) { + if err := unix.Unveil (path, "rx"); err != nil { + log.Fatalf ("failed to unveil exec for %s: %v", path, err) + } +} + +func ReadOnlyPath (path string) { + if err := unix.Unveil (path, "r"); err != nil { + log.Fatalf ("failed to unveil read for %s: %v", path, err) + } +} + +func ReadWritePath (path string) { + if err := unix.Unveil (path, "rw"); err != nil { + log.Fatalf ("failed to unveil read/write for %s: %v", path, err) + } +} + +func ReadWriteCreatePath (path string) { + if err := unix.Unveil (path, "rwc"); err != nil { + log.Fatalf ("failed to unveil read/write/create for %s: %v", path, err) + } +} + +func AllPathsAdded () { + if err := unix.UnveilBlock(); err != nil { + log.Fatalf ("failed to finalize unveil: %v", err) + } +}