Я развертываю Золотая рыбка, интерфейс для Свод, в производстве на сервере, посвященном управлению секретами. Так что безопасность здесь в первую очередь.
Я пытаюсь развернуть службу с помощью systemd в системе Unbuntu 16.04, предоставив ей минимально возможные разрешения. Я хочу, чтобы он запускался как пользователь без полномочий root goldfish
и прослушивать порт 443. Я пробовал несколько вариантов.
Решение 1.Использование сокета systemd, как было предложено в нескольких сообщениях, которые я нашел, например вот этот (Backend 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
По-прежнему не везет, я всегда получаю «слушай 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 не работает, заключается в том, что двоичный файл должен быть построен для приема сокета от 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. Чем ты закончил?
Я знаю, что это не совсем то, что вы хотели, и добавляет к головоломке «еще один кусок», но вы можете подумать о создании файла systemd .service и перемещении порта прослушивания приложений на> 1023 (это позволяет привязаться к нему без полномочий root), затем создайте правило iptables, которое перенаправляет весь трафик с порта 443 на ваш новый настраиваемый порт следующим образом:
iptables -t nat -A PREROUTING -i <incoming_interface> -p tcp --dport 443 -j REDIRECT --to-port 8443
В этом примере весь TCP-трафик на порт 443 будет прозрачно перенаправлен на порт 8443.