I recently worked through this problem, and Go has all of the pieces you need. In this sample, I went one step further and implemented SSL. Essentially, you open the port, detect the UID, and if it's 0, look for the desired user, get the UID, and then use glibc calls to set the UID and GID of the process. I might emphasize that it's best to call your setuid code right after binding the port. The biggest difference when dropping privileges is that you can't use the http.ListenAndServe(TLS)? helper function - you have to manually set up your net.Listener separately, and then call setuid after the port is bound, but before calling http.Serve.
This way of doing it works well because you can, for example, run as UID != 0 in "development" mode on a high port without further considerations. Remember that this is just a stub - I recommend setting address, port, user, group, and TLS file names in a config file.
package main
import (
"crypto/tls"
"log"
"net/http"
"os/user"
"strconv"
"syscall"
)
import (
//#include <unistd.h>
//#include <errno.h>
"C"
)
func main() {
cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
if err != nil {
log.Fatalln("Can't load certificates!", err)
}
var tlsconf tls.Config
tlsconf.Certificates = make([]tls.Certificate, 1)
tlsconf.Certificates[0] = cert
listener, err := tls.Listen("tcp4", "127.0.0.1:445", &tlsconf)
if err != nil {
log.Fatalln("Error opening port:", err)
}
if syscall.Getuid() == 0 {
log.Println("Running as root, downgrading to user www-data")
user, err := user.Lookup("www-data")
if err != nil {
log.Fatalln("User not found or other error:", err)
}
// TODO: Write error handling for int from string parsing
uid, _ := strconv.ParseInt(user.Uid, 10, 32)
gid, _ := strconv.ParseInt(user.Gid, 10, 32)
cerr, errno := C.setgid(C.__gid_t(gid))
if cerr != 0 {
log.Fatalln("Unable to set GID due to error:", errno)
}
cerr, errno = C.setuid(C.__uid_t(uid))
if cerr != 0 {
log.Fatalln("Unable to set UID due to error:", errno)
}
}
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello, world!"))
})
err = http.Serve(listener, nil)
log.Fatalln(err)
}
setcap
. – Holloman