Возможность сетевого связывания с 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 перед ним. Что ты в итоге делал?

Другие вопросы по тегам