Преамбула
Исторически сложилось так, что под root мы производим настройку, под «не root» работаем, а за мешанину этих действий получаем указкой по рукам. Все, кто читал хотя бы одну книгу по nix, наизусть помнят наставление автора — не работать с root-правами и избавиться от привычки сразу после установки системы настраивать ее, не создав себе учетку, хотя бы с доступом к группе wheel. Действительно, привычка дурная и чревата последствиями. Заглянув в мир MS Windows, мы посмеивались над админами и просто юзерами, сидящими с неограниченными правами. Этакие эксперты с незапертыми дверями, ведущими к сердцу системы.
Время идет, приложения, как и аппетиты, растут. Железо уменьшается, вмещая в себя не только эти самые приложения, но и всевозможную защиту от не менее всевозможного вреда. И вот уже разработка, внедрение и эксплуатация стали индустрией, и весьма прибыльной. Сложно представить предприятие, не использующее коммерческие приложения. Коммерческие приложения продолжают становиться сложнее, надежнее и умнее. Появились целые ниши корпоративной разработки, без которых стало невероятно сложно. Попробуй, например, отказаться от удаленного доступа к рабочей почте или оставить дома планшет, в котором так уютно разместился календарь и туннель к рабочим файлам.
Наверное, httpd — самый популярный продукт сообщества Apache, который до версии 2 так и назывался — apache. Веб‑сервер, работающий в фоне и справляющийся с тысячами одновременных сессий, сейчас его используют разве что в академических целях, для обратной совместимости и просто из ностальгии. Сообщество Apache же живет и являет миру все новые и новые технологии, приложения и решения. Одним из таких чудес мы займемся сейчас.
www
- Домашняя страница проекта
- Описание дистрибутива на сайте Tomcat
- Описание тредов JVM
Заведите себе кота
Разработчики Java-приложений знакомы с сервером Tomcat, умение разрабатывать под него стало требованием ко многим вакансиям. Админ, знающий этот сервер, может неплохо устроиться и не напрягаясь заработать на кусок хлеба с маслом. Кота держат и производители очень больших программных комплексов. Программные комплексы выполняют море работы для тысяч (а иногда и миллионов) людей, а кот молча предоставляет веб‑интерфейс управления. Словом, коты — животные полезные.
А все ли заглядывали коту в лоток, в среду окружения в смысле? Многие ли из нас обращают внимание на то, везде ли ему можно лазить, всю ли еду можно нюхать и все ли горшки с цветами можно ронять благородному животному?
Отойдем от метафор и посмотрим, с какими привилегиями работает наш контейнер с сервлетами. Максимум, который мне приходилось видеть, — это когда сервер запускают через /bin/su tomcat $CATALINA_HOME/bin/startup.sh. Да, создан пользователь tomcat, да, права на владение каталогу присвоены, но то ли это, что нам может дать мир Linux/UNIX? Не факт. Частный случай запуска — это когда процесс, а следовательно, и все его дочки и треды запущены от root’а. Какие опасности это в себе таит? Перечислим:
- Гипотетический злоумышленник может задействовать любую уязвимость библиотеки, класса или кода программы. С привилегиями root беды не избежать.
- Аналогичная опасность, но уже применительно к коду, библиотекам и классам сервера. Отслеживается по changelog’у релиза.
- Баг в коде или алгоритме может дорогого стоить, если он выполнился с root-правами.
- Ошибка в проектировании приложения может заблокировать разделяемый ресурс.
Ошибки людям свойственны, поэтому защищаться будем механизмами, на которых стоят современные многопользовательские операционные системы, и обозначим смысл изоляции процесса сервера и превращения его в настоящий демон.
Теоретическая выдержка как обоснование
С появлением многопользовательского режима работы операционных систем выросло как количество решаемых задач, так и требования к безопасности систем, поскольку встала необходимость сочетать нужный пользователю функционал системы и изолировать уязвимые места системы от несанкционированного доступа. Немаловажной была потребность корректно выходить из ситуации с разделяемыми ресурсами, так, принтер должен напечатать сначала документ одного пользователя и только после документ другого. Печать по слову из каждого документа недопустима. Аналогичные примеры для других разделяемых ресурсов также должны быть решены.
*nix-системы разрабатываются уже много лет, и от классических концепций осталось крайне мало, достаточно вспомнить поговорку о процессе контроля программистов, сопоставимом с выгулом кошек без поводков. Как сменить владельца процесса на лету, мне удалось найти только для ОС Solaris: там есть программа pcred(1), которая позволяет поменять UID и GID процесса. В других популярных ОС подобные нюансы решаются в соответствии с предпочтениями разработчиков, но, безусловно, самое популярное решение возможно только на этапе проектирования и разработки, через системные вызовы.
Будьте внимательны, разделение прав и владельцев процессов еще никому не принесло вреда.
Как это работает?

Потенциальные опасности запуска демона от root’а мы помним еще со времен упомянутого выше httpd: это и несанкционированный доступ к файлам (особенно в связке с Perl), и переход на уровень‑другой выше DocumentRoot, а SQL-инъекции — та область науки, в которой даже есть своя профессура. А теперь два вопроса, два ответа:
- Каким путем пошли разработчики httpd, MySQL, PostgreSQL и прочих? Ответ: лишили root-прав всех вышеперечисленных.
- Как же быть, если эти самые права нам нужны для выполнения системных вызовов, прямого доступа к ядру и его ресурсам? Нам же нужен открытый порт. Ответ: механизмов несколько, например пресловутый SUID или понижение привилегий через
fork(
.)
Посмотрим на вывод состояния процессов:

Перед нами процессы в состоянии выполнения. Процесс master породил пять процессов, сделал их владельцем системного пользователя nobody, отлепил их от терминала, перенаправив выводы в лог, и открыл для них порт (по умолчанию 80). Теперь мы можем быть уверены в том, что каталог, в котором выполняется процесс, не корневой (/
), под пользователем nobody никто не зайдет в систему. Базовые основы изоляции в системе соблюдены.
Теперь осмотрим видоизмененный вывод:

Как я определил, что процессы дочерние? По PPID’у. Здесь все по правилам: процесс master имеет PPID 1, из чего ясно — его запустил init, а у остальных PPID = PID master-процесса.
Большинство демонов не только создает дочерние процессы, но и распределяет по ним нагрузку, передает логи демону syslogd и делает другую полезную работу. В целом, как и любому другому процессу, демону мы можем передавать любые сигналы, например SIGHUP, через reload в скрипте инициализации — полезно при больших нагрузках, где перезапуск процесса с целью применения нового конфиг‑файла может занять ощутимо много времени, за которое порвется куча сессий. Можем приостановить через сигнал SIGSTOP, затем запустить вновь (SIGCONT).
Работая с коммерческими приложениями, я заметил, что даже крупные лидеры отрасли забивают на понижение привилегий, оставляя root владельцем Tomcat’а. Поговорим о том, как избежать таких проблем.
Многозадачность и многопользовательский режим
Старейший системный вызов, порождающий дочерние процессы, — fork(
. Он создает процессы, являющиеся точной копией родителя. Есть два хороших описания процесса, дающие наиболее полное определение этого термина. Первое — это программа в стадии ее выполнения, второе — совокупность кода и данных, разделяющих общее виртуальное адресное пространство. Понимая эти два коротких обозначения, мы можем прояснить для себя основу многозадачности (или мультипрограммирования, если больше нравится), а применяя ее к многопользовательскому режиму, мы можем гарантировать безопасное разделение прав доступа к ресурсам. В ОС Linux присутствует системный вызов clone(2), в результате использования которого появляются известные нам треды. При работе с этой низкоуровневой функцией управление памятью (да и другими ресурсами) перекладывается полностью на разработчика, тем не менее у него появляется возможность разделять различные ресурсы системы между дочерним, родительским процессами и процессами-«братьями». И хотя Java-разработчику не стоит сильно беспокоиться о работе тредов в JVM, администратору, эксплуатирующему продукт, необходимо знать, что правильнее запускать сервер без привилегий root.
Позвольте представить — jsvc

Проектом Apache Commons разработана утилита jsvc, ее исходный код поставляется вместе с дистрибутивом Tomcat’а. Цель этой разработки — произвести ряд действий, в результате которых Tomcat станет UNIX-демоном. Jsvc умеет менять UID дочернего процесса через fork(), укладывать спать родительский процесс, разделять стандартные потоки вывода и ошибок, принимать стандартные Java-опции, принимать опции Java-машины, работать с assertions как кода, так и системными, и многое другое, весь список по ключу -help
.
Для полноты ощущений мы соберем проект вручную, хотя, например, в base CentOS’а есть не самая свежая реализация под кодовым названием jakarta-commons-daemon-jsvc, описанная как Java daemon launcher. Итак, к делу. Нам понадобится make и GCC. Спускаемся в $CATALINA_HOME/
и распаковываем архив commons-daemon-native.tar.gz, проходя в каталог unix:
$ tar xzvf commons-daemon-native.tar.gz ; cd commons-daemon-1.0.10-native-src/unix/
Установочная дока гласит, что для компиляции достаточно сделать нехитрую связку ./
, но я, покопавшись в файле ./
, добавлю свои пять копеек, которые будут нелишними, gentoo-шники поймут.
Во‑первых, обязательно укажи при сборке -with-java=$JAVA_HOME
, где JAVA_HOME
— путь к каталогу с JDK. Насколько я понял, эта опция статично закрепляет путь к JAVA, если планируешь собирать для конкретного сервера, вместо применения переменной окружения, путь будет принят из константы в коде. Во‑вторых, желательно указать -with-os-type=linux
, эту опцию можно было бы не описывать, дословный смысл ясен. Значением является имя каталога JAVA_HOME/
, позволяет в процессе компиляции устранить типы данных, специфичные для Sun. Ну и в‑третьих, значения опций GCC CFLAGS=-m64
, тут я обратил внимание, что исполняемый файл уменьшился примерно в два раза. Будь внимателен: эти ключи лишают приложение машинозависимых x86-инструкций, и программа не стартанет на 32-битной системе.
Целиком строка конфигурирования у меня получилась такая: ./
, если есть интерес форсировать приложение под конкретный процессор, милости прошу в Wiki Gentoo. Хотя мне показалось, что при современных мощностях это не дало большой производительности.
Особо следует отметить баг, который я словил при сборке на CentOS 5, версии приложения, поставляемой с Tomcat 6. Если не выполнить make clean перед make, сборка ругнется на libservice.a. Насколько я понял, Malformed archive воспринимается более ранней версией архиватора ar. С версией 2.20 модификация архива завершается успешно.
На выходе получится маленький исполняемый файл, который и будет демонизировать нашего Томаса (Tomcat). Перемещаем его в каталог bin и готовим скрипт для его запуска и инициализации. В релизе 7.x все оказалось очень просто, опции Java передаются через файл setenv.sh, который надо просто создать и наполнить этими опциями (JAVA_OPTS
="-Xmx2G -Xms1G ..."). Там же, в bin, присутствует файл daemon.sh, открываем его и присваиваем переменной TOMCAT_USER имя юзера, которому будет принадлежать процесс, например daemon. Важно, что этот юзер в системе уже есть и его шелл — /sbin/nologin, как у всех спецюзеров, созданных для владения фоновыми процессами. Далее делаем символическую ссылку на этот файл:
$ ll /etc/init.d/tomcatd
lrwxrwxrwx 1 root root 30 Jun 26 13:38 /etc/init.d/tomcatd -> /opt/tomcatd/bin/daemon.sh
Для проверки использовался CentOS, поэтому, чтобы chkconfig мог корректно работать со скриптом инициализации, необходимо в его начало внести описание уровней инициализации и приоритеты при запуске и останове:
$ head /etc/init.d/tomcatd
#!/bin/sh# chkconfig: 345 73 21# description: Tomcat super daemon
Пробуем запуск (рис. 3).

Всегда что‑то может пойти не так, как планировали. Лог запуска сервера пишется в catalina-daemon.out. Как я и говорил, вывод ошибок отделяется в другой файл catalina-daemon.err.
Без учета конкретики в списке процессов наш процесс‑родитель, запущенный от root, и его дочка, владелец которой daemon, как несложно догадаться, его шелл — /sbin/nologin, выполнить что‑либо от имени такого пользователя, не используя доступ к ядру через системные вызовы, нельзя.
$ su daemon
This account is currently not available.
Резюме
В конечном счете мы получили полнофункциональный сервер Java-приложений, запущенный в концепции UNIX, с необходимыми и достаточными привилегиями. В общем случае сервер работает по следующему сценарию:
- Загружается в память, делает fork(), меняя владельца дочернему процессу.
- Родитель дожидается от потомка нечто вроде I’m ready, после чего падает в ожидание через wait_child() — классика жанра.
- Дочерний процесс является мастером, аналогично описанному для nginx. Именно он активирует Java-машину, применяет к ней опции и выполняет все то, что предусмотрено логикой работы Tomcat’а, распадается на треды и принимает клиентские соединения.
