Golang 删除权限 (v1.7)

use*_*648 4 security setuid go

我想通过 golang 制作一个自定义的网络服务器。它需要 root 才能绑定到端口 80。但是我想尽快删除 root。 syscall.SetUid()根据票证 #1435返回“不支持” 。

我总是可以通过 iptables 将端口 80 重新路由到其他地方,但是这会打开任何非 root 进程以伪装成我的网络服务器 - 我不希望这是可能的。

我如何放弃我的应用程序的权限(或者干净地解决这个问题)。

Jon*_*ins 6

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

这种做法很有效,因为例如,您可以在高端口的“开发”模式下以 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)
}
Run Code Online (Sandbox Code Playgroud)

  • 根据这个[写](https://sites.google.com/site/fullcapable/getting-started-with-go/using-go-to-set-uid-and-gids),使用cgo调用` C.setuid()` 无法 100% 可靠地工作,直到 bug [#42494](https://github.com/golang/go/issues/42494) 在 go1.16 中得到修复。 (2认同)

kos*_*tix 4

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

首选方法是使用 Linux 功能,只允许您的程序绑定到正确的端口,而不具有完整的 root 功能。

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

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

确保当您让程序重新执行自身时,您需要确保生成的程序将其标准 I/O 流连接到其父程序的标准 I/O 流,并且所有必需的打开的文件也会继承。


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