我正在部署金鱼,一个接口保险库,在专用于机密管理的服务器上进行生产。因此,安全性是这里首要关注的问题。
我正在尝试在 Unbuntu 16.04 系统上使用 systemd 部署服务,并赋予其尽可能少的权限。我希望它以非 root 用户身份运行goldfish
并监听端口 443。我尝试了多种选择。
解决方案 1:使用 systemd 套接字,正如我在多篇帖子中发现的那样这个(Goldfish 后端是用 Go 编写的,就像文章中那样)。这似乎是最精确和安全的,因为它完全由 systemd 管理,并为单个服务打开单个端口。我的单元文件如下所示:
/etc/systemd/system/goldfish.socket
:[Unit] Description=a Vault interface [Socket] ListenStream=443 NoDelay=true # Tried with false as well
/etc/systemd/system/goldfish.service
:[Unit] Description=a Vault interface After=vault.service Requires=goldfish.socket ConditionFileNotEmpty=/etc/goldfish.hcl [Service] User=goldfish Group=goldfish ExecStart=/usr/local/bin/goldfish -config=/etc/goldfish.hcl NonBlocking=true # Tried with false as well [Install] WantedBy=multi-user.target
不幸的是,我得到了“listen tcp 0.0.0.0:443: bind: permission denied”。因此,它似乎仍然没有获得所需的权限,这似乎违背了为服务创建套接字的目的。我在这里遗漏了什么?
解决方案2:赋予服务CAP_NET_BIND_SERVICE能力。这次我没有使用 systemd 套接字,而是使用AmbientCapabilities
服务中的设置(也尝试了CpabilityBoudingSet
和Capabilities
,后者令人惊讶地没有记录在systemd.exec
)。为服务添加 CAP_NET_BIND_SERVICE 功能应该会赋予服务绑定到任何特权端口的权利。
/etc/systemd/system/goldfish.service
:[Service] User=goldfish Group=goldfish ExecStart=/usr/local/bin/goldfish -config=/etc/goldfish.hcl # Tried combinations of those: AmbientCapabilities=CAP_NET_BIND_SERVICE #CapabilityBoundingSet=CAP_NET_BIND_SERVICE #Capabilities=CAP_NET_BIND_SERVICE+ep
仍然没有运气,我总是以“listen tcp 0.0.0.0:443: bind: permission denied”结束。我错过了使用 systemd 进行功能管理的哪些方面?
解决方案 3:为可执行文件提供 CAP_NET_BIND_SERVICE 功能。这次我直接将 CAP_NET_BIND_SERVICE 功能赋予 goldfish 可执行文件,并且没有什么在 systemd 服务中:
sudo setcap cap_net_bind_service=+ep /usr/local/bin/goldfish
太棒了,成功了!服务正确绑定到端口 443,我可以使用它了。但是,我现在有一个二进制文件,任何用户都可以执行它,并绑定到任何特权端口,我对此不太喜欢。可执行文件功能和 systemd 服务功能之间有什么关系?systemd 是否应该允许实现与我这里相同的结果,但仅限于特定进程?
解决方案 4:将服务置于代理之后。我现在考虑的解决方案是将 Goldfish 置于 nginx 代理后面,该代理将是唯一从外部可见的代理,并且监听端口 443。这似乎是一个很好的折衷方案,因为它保持了严格的权限,但它为设置添加了一个新部分,增加了复杂性和错误可能性。从安全性和系统管理方面来看,这看起来是一个更好的选择还是一个次要的选择?
所以我实际上有多个问题,但都与让 systemd 服务以非特权用户身份运行并监听特权端口以及它带来的安全隐患有关。有人遇到过同样的问题吗?
感谢您的帮助!
答案1
我知道这不完全是您想要的,并且为难题添加了“另一块拼图”,但您可以考虑创建一个 systemd .service 文件并将应用程序监听端口移动到>1023(这允许非 root 绑定到它),然后创建一个 iptables 规则,将所有流量从端口 443 重定向到您的新自定义端口,如下所示:
iptables -t nat -A PREROUTING -i <incoming_interface> -p tcp --dport 443 -j REDIRECT --to-port 8443
在此示例中,所有到端口 443 的 TCP 流量将透明地重定向到端口 8443。
答案2
原因解决方案 1:使用 systemd 套接字不起作用的原因是必须构建二进制文件以接受来自 systemd 的套接字。不幸的是,它不能“正常工作”。
Systemd 将套接字作为文件描述符 3 传递(在 stdin、stdout、stderr 之后)。下面是一个示例程序(来自我的此处发布博客文章)
package main
import (
"log"
"net"
"net/http"
"os"
"strconv"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
})
if os.Getenv("LISTEN_PID") == strconv.Itoa(os.Getpid()) {
// systemd run
f := os.NewFile(3, "from systemd")
l, err := net.FileListener(f)
if err != nil {
log.Fatal(err)
}
http.Serve(l, nil)
} else {
// manual run
log.Fatal(http.ListenAndServe(":8080", nil))
}
}
您必须要求金鱼添加支持。
对于 systemd 功能(解决方案 2),我不知道,但可能您也需要SecureBits=keep-caps
?systemd 应该会自动设置,但也许您的 systemd 版本(8 个月前)没有?Vault 使用它:https://learn.hashicorp.com/vault/operations/ops-deployment-guide#step-3-configure-systemd
另一个选择是使用 SELinux,尽管这可能是“现在你有两个问题”。
就你的情况而言,我个人认为应该将 nginx 放在前面。你最后做了什么?