Apache Tomcat — сер­вер веб‑при­ложе­ний, исполь­зуемый пре­иму­щес­твен­но в ком­мерчес­кой сре­де не толь­ко как плат­форма для выпол­нения при­ложе­ний, но и как сос­тавная часть круп­ных про­ектов для пре­дос­тавле­ния веб‑интерфей­са. В кор­поратив­ном сек­торе безопас­ность информа­цион­ных сис­тем име­ет наивыс­ший при­ори­тет, а ста­биль­ность инфраструк­туры гаран­тиру­ет без­болез­ненную экс­плу­ата­цию. Опро­буем хва­леную ста­биль­ность и безопас­ность UNIX-демонов на при­мере Tomcat’а.
 

Преамбула

Ис­торичес­ки сло­жилось так, что под root мы про­изво­дим нас­трой­ку, под «не root» работа­ем, а за мешани­ну этих дей­ствий получа­ем указ­кой по рукам. Все, кто читал хотя бы одну кни­гу по nix, наизусть пом­нят нас­тавле­ние авто­ра — не работать с root-пра­вами и изба­вить­ся от при­выч­ки сра­зу пос­ле уста­нов­ки сис­темы нас­тра­ивать ее, не соз­дав себе учет­ку, хотя бы с дос­тупом к груп­пе wheel. Дей­стви­тель­но, при­выч­ка дур­ная и чре­вата пос­ледс­тви­ями. Заг­лянув в мир MS Windows, мы пос­меива­лись над адми­нами и прос­то юзе­рами, сидящи­ми с неог­раничен­ными пра­вами. Эта­кие экспер­ты с незапер­тыми две­рями, ведущи­ми к сер­дцу сис­темы.

Вре­мя идет, при­ложе­ния, как и аппе­титы, рас­тут. Железо умень­шает­ся, вме­щая в себя не толь­ко эти самые при­ложе­ния, но и все­воз­можную защиту от не менее все­воз­можно­го вре­да. И вот уже раз­работ­ка, внед­рение и экс­плу­ата­ция ста­ли индус­три­ей, и весь­ма при­быль­ной. Слож­но пред­ста­вить пред­при­ятие, не исполь­зующее ком­мерчес­кие при­ложе­ния. Ком­мерчес­кие при­ложе­ния про­дол­жают ста­новить­ся слож­нее, надеж­нее и умнее. Появи­лись целые ниши кор­поратив­ной раз­работ­ки, без которых ста­ло неверо­ятно слож­но. Поп­робуй, нап­ример, отка­зать­ся от уда­лен­ного дос­тупа к рабочей поч­те или оста­вить дома план­шет, в котором так уют­но раз­местил­ся кален­дарь и тун­нель к рабочим фай­лам.

На­вер­ное, httpd — самый популяр­ный про­дукт сооб­щес­тва Apache, который до вер­сии 2 так и называл­ся — apache. Веб‑сер­вер, работа­ющий в фоне и справ­ляющий­ся с тысяча­ми одновре­мен­ных сес­сий, сей­час его исполь­зуют раз­ве что в ака­деми­чес­ких целях, для обратной сов­мести­мос­ти и прос­то из нос­таль­гии. Сооб­щес­тво Apache же живет и явля­ет миру все новые и новые тех­нологии, при­ложе­ния и решения. Одним из таких чудес мы зай­мем­ся сей­час.

www

 

Заведите себе кота

Раз­работ­чики 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-инъ­екции — та область науки, в которой даже есть своя про­фес­сура. А теперь два воп­роса, два отве­та:

  1. Ка­ким путем пош­ли раз­работ­чики httpd, MySQL, PostgreSQL и про­чих? Ответ: лишили root-прав всех вышепе­речис­ленных.
  2. Как же быть, если эти самые пра­ва нам нуж­ны для выпол­нения сис­темных вызовов, пря­мого дос­тупа к ядру и его ресур­сам? Нам же нужен откры­тый порт. Ответ: механиз­мов нес­коль­ко, нап­ример прес­ловутый SUID или пониже­ние при­виле­гий через fork().

Пос­мотрим на вывод сос­тояния про­цес­сов:

Рис.1. Состояние процессов nginx
Рис.1. Сос­тояние про­цес­сов nginx

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

Те­перь осмотрим видо­изме­нен­ный вывод:

Рис. 2. Состояние процессов nginx с «родословной» процессов
Рис. 2. Сос­тояние про­цес­сов nginx с «родос­ловной» про­цес­сов

Как я опре­делил, что про­цес­сы дочер­ние? По 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/bin/ и рас­паковы­ваем архив commons-daemon-native.tar.gz, про­ходя в каталог unix:

$ tar xzvf commons-daemon-native.tar.gz ; cd commons-daemon-1.0.10-native-src/unix/

Ус­тановоч­ная до­ка гла­сит, что для ком­пиляции дос­таточ­но сде­лать нехит­рую связ­ку ./configure ; make ; cp jsvc ../.. ; cd ../.., но я, покопав­шись в фай­ле ./configure, добав­лю свои пять копе­ек, которые будут нелиш­ними, gentoo-шни­ки пой­мут.

Во‑пер­вых, обя­затель­но ука­жи при сбор­ке -with-java=$JAVA_HOME, где JAVA_HOME — путь к катало­гу с JDK. Нас­коль­ко я понял, эта опция ста­тич­но зак­репля­ет путь к JAVA, если пла­ниру­ешь собирать для кон­крет­ного сер­вера, вмес­то при­мене­ния перемен­ной окру­жения, путь будет при­нят из кон­стан­ты в коде. Во‑вто­рых, желатель­но ука­зать -with-os-type=linux, эту опцию мож­но было бы не опи­сывать, дос­ловный смысл ясен. Зна­чени­ем явля­ется имя катало­га JAVA_HOME/include/<OS_под_которую_собран_JDK>, поз­воля­ет в про­цес­се ком­пиляции устра­нить типы дан­ных, спе­цифич­ные для Sun. Ну и в‑треть­их, зна­чения опций GCC CFLAGS=-m64 LDFLAGS=-m64, тут я обра­тил вни­мание, что исполня­емый файл умень­шил­ся при­мер­но в два раза. Будь вни­мате­лен: эти клю­чи лиша­ют при­ложе­ние машино­зави­симых x86-инс­трук­ций, и прог­рамма не стар­танет на 32-бит­ной сис­теме.

Це­ликом стро­ка кон­фигури­рова­ния у меня получи­лась такая: ./configure -with-java=/usr/java/latest -with-os-type=/include/linux CFLAGS=-m64 LDFLAGS=-m64, если есть инте­рес фор­сировать при­ложе­ние под кон­крет­ный про­цес­сор, милос­ти про­шу в 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).

Рис. 3. Инициализация демона Tomcat
Рис. 3. Ини­циали­зация демона Tomcat

Всег­да что‑то может пой­ти не так, как пла­ниро­вали. Лог запус­ка сер­вера пишет­ся в catalina-daemon.out. Как я и говорил, вывод оши­бок отде­ляет­ся в дру­гой файл catalina-daemon.err.

Без уче­та кон­кре­тики в спис­ке про­цес­сов наш про­цесс‑родитель, запущен­ный от root, и его доч­ка, вла­делец которой daemon, как нес­ложно догадать­ся, его шелл — /sbin/nologin, выпол­нить что‑либо от име­ни такого поль­зовате­ля, не исполь­зуя дос­туп к ядру через сис­темные вызовы, нель­зя.

$ su daemon
This account is currently not available.
 

Резюме

В конеч­ном сче­те мы получи­ли пол­нофун­кци­ональ­ный сер­вер Java-при­ложе­ний, запущен­ный в кон­цепции UNIX, с необ­ходимы­ми и дос­таточ­ными при­виле­гиями. В общем слу­чае сер­вер работа­ет по сле­дующе­му сце­нарию:

  1. Заг­ружа­ется в память, дела­ет fork(), меняя вла­дель­ца дочер­нему про­цес­су.
  2. Ро­дитель дожида­ется от потом­ка неч­то вро­де I’m ready, пос­ле чего пада­ет в ожи­дание через wait_child() — клас­сика жан­ра.
  3. До­чер­ний про­цесс явля­ется мас­тером, ана­логич­но опи­сан­ному для nginx. Имен­но он акти­виру­ет Java-машину, при­меня­ет к ней опции и выпол­няет все то, что пре­дус­мотре­но логикой работы Tomcat’а, рас­пада­ется на тре­ды и при­нима­ет кли­ент­ские соеди­нения.
Результат. Вид общий
Ре­зуль­тат. Вид общий

Оставить мнение