Дейл Хагглунд на месте. Так что я скажу то же самое, но по-другому, с некоторыми особенностями и примерами. ☺
Правильно в мире 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 в качестве суперпользователя, а порожденный им сервисный процесс выполняется без привилегий суперпользователя.