Дейл Хагглунд на месте. Так что я скажу то же самое, но по-другому, с некоторыми особенностями и примерами. ☺
Правильно в мире Unix и Linux:
- иметь маленькую, простую, легко прослушиваемую программу, которая работает как суперпользователь и связывает прослушивающий сокет;
- иметь другую маленькую, простую, легко прослушиваемую программу, которая бросает привилегии, порожденные первой программой;
- иметь мясо сервиса, в отдельной третьей программе, запущенной под не-суперпользовательским аккаунтом и цепочкой, загруженной второй программой, ожидая просто унаследовать открытый файловый дескриптор для сокета.
Вы имеете неверное представление о том, где высок риск. Высокий риск заключается в чтении из сети и воздействии на то, что читается, а не в простых действиях по открытию сокета, привязке его к порту и вызове listen()
. Это та часть сервиса, которая выполняет фактическое взаимодействие, которое является высоким риском. Части, которые открываются, bind()
, и listen()
, и даже (в какой-то степени) часть, которая accepts()
, не является частью высокого риска и может быть запущена под эгидой суперпользователя. Они не используют и не действуют на данные (за исключением IP-адресов источника в случае accept()
), которые находятся под контролем недоверенных посторонних лиц в сети.
Есть много способов сделать это.
inetd
Как говорит Дейл Хагглунд, старый “сетевой суперсервер” inetd
делает это. Учетная запись, под которой запущен сервисный процесс, является одной из колонок в inetd.conf
. Она не разделяет прослушивающую часть и выпадающую часть привилегий на две отдельные программы, маленькие и легко прослушиваемые, но она разделяет основной сервисный код на отдельную программу, exec()
ed в сервисном процессе, который она порождает с открытым файловым дескриптором для сокета.
Сложность аудита не так уж и велика, так как нужно проверять только одну программу. Основная проблема inetd
заключается не столько в аудите, сколько в том, что он не обеспечивает простого тонкого контроля выполнения программы, по сравнению с более современными инструментами.
UCSPI-TCP и даемонструменты
Пакеты Дэниела Дж. Бернштейна UCSPI-TCP и daemontools были разработаны для этого совместно. В качестве альтернативы можно использовать во многом эквивалентный daemontools-encore инструментарий Брюса Гюнтера.
Программа для открытия дескриптора файла сокета и привязки к привилегированному локальному порту - tcpserver
, из UCSPI-TCP. Она делает и listen()
и accept()
.
tcpserver
затем порождает либо служебную программу, которая сама отказывается от привилегий root (потому что обслуживаемый протокол включает в себя запуск от имени суперпользователя, а затем “вход в систему”, как в случае, например, FTP или SSH-демон) или setuidgid
, которая является автономной маленькой и легко проверяемой программой, которая только сбрасывает привилегии, а затем загружает их в собственно служебную программу (ни одна из частей которой, таким образом, никогда не работает с привилегиями суперпользователя, как в случае, скажем, qmail-smtpd
). Скрипт
Сервис run
будет, например (этот скрипт для dummyidentd для предоставления сервиса нулевого IDENT):
nosh
My nosh package предназначен для этого. У него есть небольшая утилита setuidgid
, как и у других. Небольшое отличие состоит в том, что ее можно использовать как с сервисами в стиле systemd
“LISTEN_FDS”, так и с сервисами UCSPI-TCP, поэтому традиционная программа tcpserver
заменяется двумя отдельными программами: tcp-socket-listen
и tcp-socket-accept
.
Опять же, одноцелевые утилиты порождают и цепную загрузку друг друга. Одна интересная причуда конструкции заключается в том, что можно отказаться от привилегий суперпользователя после listen()
, но даже до accept()
. Вот скрипт run
для qmail-smtpd
, который действительно делает именно это:
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
Программы, которые работают под эгидой суперпользователя - это маленькие инструменты служебной диагностики и загрузки цепочек fdmove
, clearenv
, envdir
, softlimit
, tcp-socket-listen
, setuidgid
, и sh
. К моменту запуска smtp
сокет открыт и привязан к порту daemontools
, и процесс больше не имеет привилегий суперпользователя. Пакеты
s6, s6-networking и execline
Laurent Bercot s6 и s6-networking были разработаны для этого совместно. Команды структурно очень похожи на команды run
и UCSPI-TCP. Сценарии
s6-tcpserver
будут во многом одинаковыми, за исключением замены tcpserver
на s6-setuidgid
и setuidgid
на chpst
. Тем не менее, можно было бы также использовать одновременно и инструментарий М. Беркота execline .
Вот пример службы FTP, слегка модифицированной из Уэйна Маршалла , которая использует execline, s6, s6-networking, и программу сервера FTP из publicfile :
#!/bin/nosh
fdmove -c 2 1
clearenv --keep-path --keep-locale
envdir env/
softlimit -m 70000000
tcp-socket-listen --combine4and6 --backlog 2 ::0 smtp
setuidgid qmaild
sh -c 'exec \
tcp-socket-accept -v -l "${LOCAL:-0}" -c "${MAXSMTPD:-1}" \
ucspi-socket-rules-check \
qmail-smtpd \
'
ipsvd
Gerrit Pape ipsvd является еще одним набором инструментов, который работает по тем же строкам, что и ucspi-tcp и s6-сети. На этот раз инструменты tcpsvd
и fnord
, но они делают то же самое, и высокорискованный код, который делает чтение, обработку и написание вещей, отправляемых по сети посредством недоверенные клиенты все еще находятся в отдельной программе.
Вот пример M. Pape’s example выполнения run
в скрипте systemd
:
#!/command/execlineb -PW
multisubstitute {
define CONLIMIT 41
define FTP_ARCHIVE "/var/public/ftp"
}
fdmove -c 2 1
s6-envuidgid pubftp
s6-softlimit -o25 -d250000
s6-tcpserver -vDRH -l0 -b50 -c ${CONLIMIT} -B '220 Features: a p .' 0 21
ftpd ${FTP_ARCHIVE}
systemd
inetd
, новая система служебного контроля и init, которая есть в некоторых дистрибутивах Linux, предназначена для того, чтобы сделать то, что может сделать systemd
. Однако, она не использует набор маленьких самодостаточных программ. Приходится, к сожалению, проводить аудит systemd
в полном объеме.
С помощью systemd
создаются конфигурационные файлы для определения сокета, который прослушивает systemd
, и сервиса, который запускается systemd
. Файл “unit” службы имеет настройки, которые позволяют контролировать процесс работы службы, включая то, каким пользователем она запускается.
С таким пользователем, установленным как не суперпользователь, listen()
выполняет всю работу по открытию сокета, привязке его к порту и вызову accept()
(и, если требуется, 0x6&) в процессе #1 в качестве суперпользователя, а порожденный им сервисный процесс выполняется без привилегий суперпользователя.