diff --git a/README.md b/README.md index f5cdc33..0c40438 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,12 @@ Molly Brown only has a single dependency beyond the Go standard library, which is [this TOML parsing library](https://github.com/BurntSushi/toml). +The OpenBSD implementation also uses the [golang.org/x/sys/unix +package](https://godoc.org/golang.org/x/sys/unix) to provide the +[pledge(2)](https://man.openbsd.org/pledge.2) and +[unveil(2)](https://man.openbsd.org/unveil.2) system calls to provide +additional security features. + ## Installation The easiest way for now to install Molly Brown is to use the standard diff --git a/main.go b/main.go index 03aaa36..fd43fb3 100644 --- a/main.go +++ b/main.go @@ -106,6 +106,9 @@ func main() { } }() + // Restrict access to the files specified in config + enableSecurityRestrictions(config, errorLog) + // Infinite serve loop for { conn, err := listener.Accept() diff --git a/security.go b/security.go new file mode 100644 index 0000000..1ae9423 --- /dev/null +++ b/security.go @@ -0,0 +1,14 @@ +// +build !openbsd + +package main + +import ( + "log" +) + +// Restrict access to the files specified in config in an OS-dependent way. +// This is intended to be called immediately prior to accepting client +// connections and may be used to establish a security "jail" for the molly +// brown executable. +func enableSecurityRestrictions(config Config, errorLog *log.Logger) { +} diff --git a/security_openbsd.go b/security_openbsd.go new file mode 100644 index 0000000..bfcd1ce --- /dev/null +++ b/security_openbsd.go @@ -0,0 +1,66 @@ +package main + +import ( + "golang.org/x/sys/unix" + "log" + "path/filepath" +) + +// Restrict access to the files specified in config in an OS-dependent way. +// The OpenBSD implementation uses pledge(2) and unveil(2) to restrict the +// operations available to the molly brown executable. Please note that (S)CGI +// processes that molly brown spawns or communicates with are unrestricted +// and should pledge their own restrictions and unveil their own files. +func enableSecurityRestrictions(config Config, errorLog *log.Logger) { + + // Unveil the configured document base as readable. + log.Println("Unveiling \"" + config.DocBase + "\" as readable.") + err := unix.Unveil(config.DocBase, "r") + if err != nil { + errorLog.Println("Could not unveil DocBase: " + err.Error()) + log.Fatal(err) + } + + // Unveil cgi path globs as executable. + for _, cgiPath := range config.CGIPaths { + cgiGlobbedPaths, err := filepath.Glob(cgiPath) + for _, cgiGlobbedPath := range cgiGlobbedPaths { + log.Println("Unveiling \"" + cgiGlobbedPath + "\" as executable.") + err = unix.Unveil(cgiGlobbedPath, "rx") + if err != nil { + errorLog.Println("Could not unveil CGIPaths: " + err.Error()) + log.Fatal(err) + } + } + } + + // Unveil scgi socket paths as readable and writeable. + for _, scgiSocket := range config.SCGIPaths { + log.Println("Unveiling \"" + scgiSocket + "\" as read/write.") + err = unix.Unveil(scgiSocket, "rw") + } + + // Finalize the unveil list. + // Any files not whitelisted above won't be accessible to molly brown. + err = unix.UnveilBlock() + if err != nil { + errorLog.Println("Could not block unveil: " + err.Error()) + log.Fatal(err) + } + + // Pledge to only use stdio, inet, and rpath syscalls. + promises := "stdio inet rpath" + if len(config.CGIPaths) > 0 { + // If CGI paths have been specified, also allow exec syscalls. + promises += " exec proc" + } + if len(config.SCGIPaths) > 0 { + // If SCGI paths have been specified, also allow unix sockets. + promises += " unix" + } + err = unix.PledgePromises(promises) + if err != nil { + errorLog.Println("Could not pledge: " + err.Error()) + log.Fatal(err) + } +}