Golang 删除权限 (v1.7)

Golang dropping privileges (v1.7)

我想通过 golang 制作一个自定义网络服务器。 它需要 root 绑定到端口 80。 但是我想尽快放弃根。 syscall.SetUid() returns "Not supported" 根据 ticket #1435.

我总是可以通过 iptables 将端口 80 重新路由到其他东西,但是这会打开任何非根进程冒充我的网络服务器 - 我不希望这是可能的。

如何删除我的应用程序的权限(或者彻底解决这个问题)。

我会按照@JimB 的建议去做:

The preferred method is to use linux capabilities to only allow your program to bind to the correct port without having full root capabilities.

另一方面,在 Linux 上还有另一个技巧:您可以使用 os/exec.Command() 来执行 /proc/self/exe,同时告诉它在 SysProcAttr.Credential 字段中使用替代凭据它生成的 os/exec.Cmd 个实例。

参见 go doc os/exec.Cmdgo doc syscall.SysProcAttrgo doc syscall.Credential

确保当你让你的程序重新执行自己时,你需要确保生成的程序有它的标准 I/O 流连接到它的父级的流,并且所有必要的打开的文件都是继承的还有。


另一个值得一提的替代方案是根本不要尝试绑定到端口 80 并在那里挂一个合适的 Web 服务器,然后反向代理基于主机名的虚拟主机或特定的 URL 路径监听任何 TCP 或 Unix 套接字的 Go 进程的前缀(或前缀)。 Apache(至少 2.4)和 Nginx 都可以轻松做到这一点。

我最近解决了这个问题,Go 拥有您需要的所有部分。在此示例中,我更进了一步并实施了 SSL。本质上,您打开端口,检测 UID,如果为 0,则查找所需的用户,获取 UID,然后使用 glibc 调用来设置进程的 UID 和 GID。我可能会强调,最好在绑定端口后立即调用您的 setuid 代码。删除权限时最大的不同是不能使用 http.ListenAndServe(TLS)? helper 函数 - 你必须单独手动设置你的 net.Listener,然后在绑定端口后调用 setuid,但在调用 http.Serve.

之前

这种方法很有效,因为您可以在高端口上以 "development" 模式将 运行 作为 UID != 0,而无需进一步考虑。请记住,这只是一个存根 - 我建议在配置文件中设置地址、端口、用户、组和 TLS 文件名。

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