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.Cmd
、go doc syscall.SysProcAttr
和 go 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)
}
我想通过 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.Cmd
、go doc syscall.SysProcAttr
和 go 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)
}