Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang dropping privileges (v1.7)

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).

like image 511
user2089648 Avatar asked Dec 24 '22 22:12

user2089648


1 Answers

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)
}
like image 151
Jon Jenkins Avatar answered Jan 07 '23 05:01

Jon Jenkins