I want to make a custom webserver via golang.
It needs root to bind to port 80.
However I want to drop root as soon as possible.
syscall.SetUid()
returns "Not supported" as per ticket #1435.
I could always reroute port 80 to something else via iptables, however this opens up any non-root process to pose as my webserver - which I'd prefer not to be possible.
How do I drop privileges for my application (or alternatively solve this cleanly).
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With