Возможность сетевого связывания с systemd
Я развертываю Goldfish, интерфейс для Vault, в производстве на сервере, предназначенном для управления секретами. Так что безопасность здесь имеет первостепенное значение.
Я пытаюсь развернуть службу с помощью systemd в системе Unbuntu 16.04, предоставляя ей как можно меньше разрешений. Я хочу, чтобы он работал от имени пользователя без полномочий 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
К сожалению, я получаю "прослушивание tcp 0.0.0.0:443: bind: разрешение отклонено". Таким образом, кажется, что он до сих пор не получил желаемого разрешения, что, кажется, лишает цели создания сокета для службы. Что мне здесь не хватает?
Решение 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: разрешение запрещено". Что мне не хватает в управлении возможностями с помощью systemd?
Решение 3: предоставление возможности CAP_NET_BIND_SERVICE исполняемому файлу. На этот раз я даю возможность CAP_NET_BIND_SERVICE непосредственно исполняемому файлу "золотой рыбки", и ничего в сервисе systemd:
sudo setcap cap_net_bind_service=+ep /usr/local/bin/goldfish
Ура, это работает! Служба правильно связывается с портом 443, и я могу его использовать. Однако теперь у меня есть двоичный файл, который может быть выполнен любым пользователем и привязан к любому привилегированному порту, что мне не очень нравится. Какова взаимосвязь между исполняемыми возможностями и возможностями службы systemd? Разве systemd не должен позволять достичь того же результата, который я получаю здесь, но только для определенного процесса?
Решение 4: поставить сервис за прокси. Решение, которое я сейчас рассматриваю, заключается в том, чтобы установить Goldfish за прокси-сервером nginx, который будет единственным, видимым снаружи и прослушивающим порт 443. Это кажется хорошим компромиссом, так как сохраняет жесткие разрешения, но добавляет новый элемент в настройки, с чем она добавляет сложности и потенциальных ошибок. Похоже ли это на лучший или меньший вариант с точки зрения безопасности и системного администрирования?
Таким образом, у меня на самом деле есть несколько вопросов, но все они связаны с тем, чтобы служба systemd работала как непривилегированный пользователь во время прослушивания привилегированного порта, и последствиями для безопасности, которые он имеет. Кто-то уже сталкивался с такими же проблемами?
Спасибо за вашу помощь!
2 ответа
Я знаю, что это не совсем то, что вы хотели, и добавляет "еще один кусочек" в головоломку, но вы можете рассмотреть вопрос о создании файла 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.
Причина, по которой решение 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
Другой вариант - использовать SELinux, хотя это может быть "теперь у вас две проблемы".
Лично в вашей ситуации, я думаю, я бы поставил nginx перед ним. Что ты в итоге делал?