add support for privilege dropping and chroots back!

note: check added to ensure it isn't compiled with a Go
version without fixed syscalls

Signed-off-by: kim (grufwub) <grufwub@gmail.com>
development
kim (grufwub) 4 years ago
parent 214365ee2c
commit 2675a14351

@ -5,6 +5,7 @@ import (
"log"
"os"
"os/signal"
"os/user"
"strconv"
"syscall"
"time"
@ -16,6 +17,10 @@ import (
"github.com/grufwub/go-logger"
)
// Temporary check to ensure correct Go
// version being used
var _ = syscall.AllThreadsSyscall
func usage(code int) {
fmt.Printf("Usage: %s [-v|--version] [-c|--config $file]\n", os.Args[0])
os.Exit(code)
@ -47,6 +52,9 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
tree.StringVar(&Bind, "listen", "")
tree.StringVar(&Hostname, "hostname", "")
port := tree.Uint64("port", uint64(defaultPort))
chroot := tree.String("chroot", "")
username := tree.String("user", "")
groupname := tree.String("group", "")
// Filesystem configuration
fReadBuf := tree.Uint64("filesystem.read-buf", 1024)
@ -98,11 +106,54 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
Hostname = Bind
}
// Change to server directory
if osErr := os.Chdir(Root); osErr != nil {
SystemLog.Fatalf(chDirErrStr, osErr)
// - username supplied, lookup and set uid.
// - groupname supplied, lookup and set gid.
// - username but no groupname, we use the user primary gid
var uid, gid int
if *username != "" {
u, osErr := user.Lookup(*username)
if osErr != nil {
SystemLog.Fatalf(userLookupErrStr, osErr)
}
uid, _ = strconv.Atoi(u.Uid)
gid, _ = strconv.Atoi(u.Gid)
}
if *groupname != "" {
g, osErr := user.LookupGroup(*groupname)
if osErr != nil {
SystemLog.Fatalf(groupLookupErrStr, osErr)
}
gid, _ = strconv.Atoi(g.Gid)
}
// If chroot provided, change to this!
if *chroot != "" {
osErr := syscall.Chroot(*chroot)
if osErr != nil {
SystemLog.Fatalf(chrootErrStr, osErr)
}
SystemLog.Infof(chrootStr, *chroot)
// Ensure we're at root of chroot
osErr = os.Chdir("/")
if osErr != nil {
SystemLog.Fatalf(chDirErrStr, osErr)
}
}
// If root is different to chroot, we change to this dir
if Root != *chroot {
osErr := os.Chdir(Root)
if osErr != nil {
SystemLog.Fatalf(chDirErrStr, osErr)
}
SystemLog.Infof(chDirStr, Root)
}
// If chroot disabled, and root empty, this is bad!
if *chroot == "" && Root == "" {
SystemLog.Fatalf(rootDirEmptyErrStr)
}
SystemLog.Infof(chDirStr, Root)
// Set port info
Port = strconv.Itoa(int(*port))
@ -117,6 +168,29 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
SystemLog.Fatalf(listenerBeginFailStr, protocol, Hostname, Port, Bind, Port, err.Error())
}
// If been supplied a group, change to requested group
if *groupname != "" {
osErr := syscall.Setgid(gid)
if osErr != nil {
SystemLog.Fatalf(setgidErrStr, osErr)
}
SystemLog.Infof(setgidStr, *groupname)
}
// If been supplied a user, switch to requested user
if *username != "" {
osErr := syscall.Setuid(uid)
if osErr != nil {
SystemLog.Fatalf(setuidErrStr, osErr)
}
SystemLog.Infof(setuidStr, *username)
}
// Check not running as root
if syscall.Geteuid() == 0 || syscall.Getegid() == 0 {
SystemLog.Fatalf(runningAsRootErrStr)
}
// Setup the sync pools
connBufferedReaderPool = bufpools.NewBufferedReaderPool(int(*cReadBuf))
connBufferedWriterPool = bufpools.NewBufferedWriterPool(int(*cWriteBuf))
@ -171,8 +245,9 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
WithinCGIDir = withinCGIDirEnabled
}
// If no user dir supplied, set to disabled function. Else, set user dir and enable
if *userSpacesEnabled {
// If no user dir supplied, or chroot enabled, set to disabled function.
// Else, set user dir and enable
if *userSpacesEnabled || *chroot != "" {
SystemLog.Info(userSpacesDisabledStr)
getRequestPath = getRequestPathUserSpacesDisabled
} else {

@ -4,9 +4,26 @@ package core
const (
hostnameBindEmptyStr = "At least one of hostname or bind-addr must be non-empty!"
chrootStr = "Chrooting into dir: %s"
chrootErrStr = "Error chrooting into directory: %s"
chDirStr = "Entered server dir: %s"
chDirErrStr = "Error entering server directory: %s"
rootDirEmptyErrStr = "No server root directory supplied!"
userLookupErrStr = "Error looking up user: %s"
groupLookupErrStr = "Error looking up group: %s"
currentUserLookupErrStr = "Error looking up current user: %s"
setuidStr = "Running as user: %s"
setuidErrStr = "Error performing setuid: %s"
setgidStr = "Running as group: %s"
setgidErrStr = "Error performing setgid: %s"
runningAsRootErrStr = "Gophi does not support running as root!"
listenerBeginFailStr = "Failed to start listener on %s://%s:%s (%s:%s) - %s"
listeningOnStr = "Listening on %s://%s:%s (%s:%s)"

@ -2,29 +2,4 @@
By default, URLs are parsed as having standard (HTTP) URL encoding. All other
parsed text content are treated as UTF-8, as this is the default encoding scheme
Go strings. Support for more encoding schemes is planned for the future
# Chroots and Privilege Dropping
Previously, chrooting to server directory and dropping privileges was supported
by using Go C bindings. This is not officially supported due to weird behaviour
with `.Set{U,G}id()` under Linux. As such, the feature has been dropped for
now.
There is a near 10 year ongoing tracked issue
(https://github.com/golang/go/issues/1435), and as soon as this patch gets
merged I'll add support: https://go-review.googlesource.com/c/go/+/210639
In place of removing this, request sanitization has been majorly improved and
checks are in place to prevent running gophi as root.
If you run into issues binding to a lower port number due to insufficient
permissions then there are a few alternatives:
- set gophi process capabilities: e.g.
`setcap 'cap_net_bind_service=+ep' /usr/local/bin/gophi`
- use Docker (or some other solution) and configure port forwarding on the
host
- start gopher in it's own namespace in a chroot
Go strings. Support for more encoding schemes is planned for the future

@ -3,8 +3,23 @@
# statements and anywhere else you may
# need them
# Server root directory
root = "/var/gophi"
# Chroot directory (empty to disable)
chroot = "/var/gopher"
# Server root. If chroot enabled
# this will be seen as relative to
# the server chroot. If chroot is
# disabled this MUST be supplied
root = "/"
# UNIX user and group names server
# should run under:
# - if user supplied but no group, then
# user's primary group is used
# - if both are blank, runs as current
# user
user = "grufwub"
group = ""
# Server bind address
listen = "127.0.0.1"

Loading…
Cancel
Save