Apache как прокси-сервер

Apache httpd давно завоевал славу самого популярного в мире web-сервера. Однако, многие даже не догадываются, что он может составить конкруенцию не только IIS, но и Squid...

[Валентин Синицын (val AT linuxcenter DOT ru)]

Рассмотрим типичную для небольшой организации конструкцию: шлюз в Интернет, работающий под управлением Unix или Microsoft Windows, прокси-сервер, web-сервер сети Интранет, почтовый сервер и т.д. Оказывается, число ее элементов можно несколько сократить, и в соответствии с золотым правилом инженера, увеличить тем самым общую надежность системы. Сегодня мы поговорим о делегировании Apache основных функций кэширующего прокси-сервера (например, Squid) и даже кое-чего сверх них. В качестве базовой ОС будем использовать Linux, хотя многое из сказанного ниже может быть без ограничения общности применено и для других платформ.

Согласен, использование Apache в качестве прокси-сервера выглядит несколько нестандартно, однако, оно имеет ряд преимуществ. Это, в первую очередь, возможность динамического сжатия документов, отправляемых клиентам, что может вылиться в серьезную экономию, если для передачи данных используется арендованный канал с помегабайтной оплатой входящего трафика (допустим, офисов у фирмы два, а сервер всего один). Кроме того, оно сокращает число сервисов, работающих в системе, а значит, у потенциального злоумышленника будет меньше мишеней для проведения атаки, а у администратора, в свою очередь, меньше объектов, требующих неусыпного наблюдения и поддержки. Впрочем, как и все в нашем неидеальном мире, Apache в роли прокси-сервера имеет и некоторые недостатки, как-то: содержит экспериментальные или написанные сторонними разработчиками модули (при желании можно обойтись и без них) и неизвестным образом ведет себя при увеличении нагрузки (вполне допускаю, что все будет прекрасно работать, однако, специализированного стресс-тестирования не проводил). Какая чаша перевесит – решать вам. Руководствуясь личным опытом, я вполне могу рекомендовать применение прокси-сервера на базе Apache в небольших сетях.

Покончив с теоретическими вопросами, перейдем к техническим деталям. Для реализации задуманного нам понадобится Apache версии 2.0.53 и выше (более ранние версии имеют ошибки в mod_cache, препятствующие нормальной работе с кэшированными документами) с включенными mod_proxy, mod_cache и mod_deflate. В отличие от специализированных решений вроде упомянутого выше Squid, прокси-сервер на базе Apache имеет существенно модульную архитектуру, все мыслимые и немыслимые функции которой построены по принципу: общая база + провайдеры. Мы не раз убедимся в этом по ходу изложения. Итак, приступим к первой фазе:

Запуск прокси-сервера

Для того, чтобы Apache мог принимать и обрабатывать прокси-запросы, необходимо загрузить модуль mod_proxy, входящий в стандартный комплект поставки и прекрасно документированный. Здесь и далее в этой статье мы не будем дублировать страницы руководства, останавливаясь лишь на нетривиальных моментах. mod_proxy, как и большая часть других упомянутых в статье модулей, обычно не собирается в стандартной конфигурации Apache, поэтому вам, вероятно, придется заново скомпилировать сервер, указав соответствующие параметры сценарию configure. За поддержку mod_proxy отвечает опция «--enable-proxy», прочие же параметры я буду приводить в тексте в скобках после имени соответствующего модуля.

В соответствии с представленной выше формулой, mod_proxy образует фундамент, на котором работает система поддержки прокси-запросов. Их реализации, специфичные для различных протоколов, вынесены в отдельные модули: mod_proxy_http (--enable-proxy-http), mod_proxy_ftp (--enable-proxy-ftp) и mod_proxy_connect (--enable-proxy-connect). Последний из них необходим для работы с запросами HTTP CONNECT, в частности, защищенными SSL-соединениями.

Прежде чем говорить о настройке mod_proxy, сделаем пару замечаний. Первое: Apache поддерживает два типа прокси-серверов: прямые (forwarding proxy) и обратные (reverse proxy). Нас будут интересовать исключительно прямые прокси-сервера. Обратные прокси применяются для балансировки нагрузки и не имеют ничего общего с темой данной статьи. Второе: прокси-запрос не является для Apache чем-то чужеродным. Заглянув в исходные тексты сервера, вы обнаружите, что все клиентские запросы, независимо от того, кому они адресованы, описываются одной и той же структурой. Apache помечает запросы, предназначенные другим серверам, специальным флагом, но не более того. Из этого, к примеру, следует, что в параметрах модуля mod_proxy отсутствует директива для указания порта, который следует использовать для приема входящих соединений (сноска: напомним, что согласно общепринятым соглашениям для этих целей используется порт 3128). Apache способен принимать и обрабатывать прокси-запросы на любом порту, который разрешен к “прослушиванию” директивой Listen. Впрочем, на практике зачастую оказывается удобнее провести границу между локальными и переадресуемыми запросами. Для достижения этой цели можно создать специальный виртуальный хост, например, на порту 3128, пользуясь директивой VirtualHost. Подробности ищите в документации к Apache. Дальнейшее изложение неявно предполагает, что вы уже настроили виртуальный хост и размещаете предлагаемые директивы внутри принадлежащей ему секции httpd.conf (Подсказка для самых нетерпеливых: в конце статьи приведен завершенный фрагмент конфигурационного файла, практически пригодный для вставки по методу Ctrl-C – Ctrl-V)

Чтобы Apache мог принимать прокси-запросы, необходимо явным образом разрешить их, используя директиву “ProxyRequests On”. Однако, не спешите этого делать, не позаботившись о безопасности сетевых соединений! Оплачивать мегабайты, загруженные предприимчивыми подростками с ProxyHunter'ом в руках – не самое приятное времяпровождение. Параметры доступа к серверу задаются в секции <Proxy> и, в частности, могут иметь следующий вид:

<Proxy *> # Для всех прокси-запросов

Order deny,allow # Сперва запретить, потом разрешить

Deny from All # Запретить всем

Allow from 192.168.0.1/24 # Разрешить доступ из внутренней сети организации

</Proxy>

Здесь реализована простейшая схема контроля доступа, базирующаяся на IP-адресах клиентов. Во многих случаях ее будет достаточно. Но что делать, если вы хотите разрешить использование сервера с компьютеров, не имеющих фиксированного IP-адреса (например, домашних машин особо приближенных сотрудников, которые не прочь получить “городской прокси”)? Для этих целей можно применить авторизацию по имени пользователя и паролю. Поскольку директивы контроля доступа, заключенные между тегами <Proxy> и </Proxy> на самом деле обрабатываются теми же самыми модулями, что следят за «неприкосновенностью» локальных каталогов сервера (ну, не говорил ли я вам, что Apache практически не отличает прокси-запрос от обычного?), вы можете использовать привычную конструкцию:

<Proxy *> # Для всех прокси-запросов

AuthName “Tresspassers” # «Посторонним в.»

AuthFile /some/secret/file # Имя файла, содержащего реквизиты пользователей

AuthType Basic # Метод авторизации - базовый

Require valid-user # Пропускать всех, кто перечислен в /some/secret/file

</Proxy>

Естественно, предварительно следует создать файл /some/secret/file при помощи утилиты htpasswd(1) и загрузить соответствующие модули (mod_access и/или mod_auth). При необходимости оба метода можно комбинировать. При этом бывает полезно пользоваться директивой “Satisfy All|Any”, указывающей, требовать ли от потенциального клиента соответствия всем условиям (применительно к нашей задаче это означает «иметь разрешенный IP-адрес и ввести правильное имя пользователя/пароль») или лишь одному из них. Последний вариант наиболее интересен с практической точки зрения, поскольку позволяет избежать раздражающей процедуры ввода пароля для пользователей внутренней сети организации.

Внеся необходимые изменения в httpd.conf, не забудьте перезапустить Apache. После этого откройте свой любимый браузер и удостоверьтесь, что настройки безопасности действуют именно так, как вы задумали.

Кэширование

Мы успешно справились с первым этапом, а именно: научили Apache обрабатывать запросы, адресованные внешним серверам. Нашей следующей задачей будет организация локального кэширования запрашиваемых данных. По некоторым сведениям, это позволяет сэкономить 10-20 процентов внешнего трафика, что, согласитесь, не так уж мало.

Кэшированием данных в Apache заведует модуль mod_cache (--enable-cache). Именно он принимает решение о том, допустимо ли локальное сохранение того или иного объекта. Непосредственной записью данных на носители занимаются модули-”провайдеры”, из которых нас в первую очередь будет интересовать mod_disk_cache (--enable-disk-cache), реализующий хранение кэша на жестком диске.

Отметим, что в Apache 2.0 оба этих модуля (mod_cache и mod_disk_cache) имеют статус экспериментальных. Ситуация обещает измениться в Apache 2.1, который пока что пребывает в состоянии альфа-версии.

Чтобы включить кэширование, используйте директиву “CacheEnable disk /”, где “disk” - идентификатор модуля-провайдера. Местоположение дискового кэша и его желаемый объем (в килобайтах) задается соответственно директивами “CacheRoot <имя каталога>” и «CacheSize <NNN>», относящимися уже не к mod_cache, а mod_disk_cache. Apache предпринимает меры к тому, чтобы конфиденциальные данные никогда не попадали в кэш сервера, однако, лишняя безопасность все же не повредит. Я рекомендую сделать каталог, в котором хранится кэш, недоступным ни для кого, кроме Apache.

# chown apache:apache /path/to/cache

# chmod 0700 /path/to/cache

Естественно, если в вашей системе Apache работает от имени другого пользователя, имя и группу владельца каталога также следует изменить соответствующим образом.

В целях повышения производительности дисковый кэш имеет многоуровневую структуру. Глубиной вложенности подкаталогов и максимальной длиной их имени управляют директивы CacheDirLevels и CacheDirLength. По умолчанию для них используются следующие значения: CacheDirLevels 2, CacheDirLength 3

К сожалению, Apache 2.0 не имеет никаких штатных средств для управления содержимым кэша. Вы не можете постепенно удалять старые данные или делать это при превышении кэшем некоторой дисковой квоты. Часть из этих проблем решена в Apache 2.1 при помощи специальной утилиты htcacheclean, которая очищает кэш по мере необходимости. Мне не известно, перенесена ли она в ветвь 2.0, однако, в качестве некоторой замены, вы можете написать сценарий, периодически очищающий каталог (и, для верности, перезапускающий Apache), самостоятельно.

Если вы все же не последовали данному выше совету выделить прокси-серверу отдельный виртуальный хост, имейте в виду, что настройки кэширования влияют не только на переадресованные, но и на обычные запросы, что может иметь неожиданные побочные эффекты вроде задержки между редактированием файла на диске и фактическим изменением web-страницы с точки зрения внешнего пользователя.

На данном этапе мы завершили все работы, необходимые для получения “идентичного натуральному ароматизатора” Squid, а точнее, того подмножества его функций, которое используется в типичной малой сети. Теперь мы пойдем несколько дальше и реализуем нечто новое – прозрачное сжатие web-страниц.

Сжатие

Модуль для сжатия HTTP-документов известен каждому уважающему себя web-мастеру. Называется он mod_deflate (--enable-deflate), и, к вящей радости замученного бесконечными сборками из исходных текстов читателя, обычно включен даже в стандартной конфигурации. Его основное предназначение – сжимать локальные HMTL-страницы перед отправкой их пользователю, но, коль скоро Apache “близорук” и не делает особых различий между обычным и переадресованным запросом, он вполне годится и для последних.

После своей загрузки mod_deflate создает фильтр “DEFLATE”, который может быть установлен стандартным образом, например, при помощи директивы “SetOutputFilter DEFLATE”. Руководство к модулю рекомендует поостеречься и отключить сжатие данных для браузеров, которые, несмотря на заявленную функциональность (сноска: Отметим, что отсечение клиентов, не поддерживающих сжатие данных, mod_deflate производит сам, безо всякого участия администратора) не могут обеспечить должный уровень поддержки (например, Netscape Navigator 4.x может корректно обрабатывать только сжатые данные типа text/html, а его версии 4.06-4.08 не способны даже на это), однако, применительно к прокси-серверу это имеет смысл лишь в том случае, когда такие “динозавры” до сих пор имеют хождение в вашей организации. Из других директив, поддерживаемых mod_deflate, следует упомянуть DeflateCompressionLevel,

устанавливающую степень сжатия данных (число от 0 до 9). Большее значение этой величины обеспечивает меньший размер результирующих файлов, но повышает нагрузку на процессор. Для среднестатистической системы оптимальным выбором считается 6. Вы можете не использовать эту директиву, тогда будут иметь место значения по умолчанию, принятые при сборке системной библиотеки Zlib.

mod_filter

Казалось бы, все хорошо. Наш Apache теперь умеет обрабатывать прокси-запросы, хранить их в кэше и даже сжимать перед отправкой. Однако, спустя некоторое (обычно – весьма непродолжительное) время обнаруживаются странные артефакты. Архивы почему-то оказываются упакованными дважды, что пугает неподготовленных пользователей. И тут же возникают два вековечных русских вопроса: “Кто виноват?” и “Что делать?”

Благо, за ответом на первый из них далеко ходить не надо. Как наверняка уже догадался проницательный читатель, проблема кроется в излишней «жадности» нашего mod_deflate, который сжимает все и вся, тогда как браузер готов распаковать лишь вполне определенные типы файлов: текст, HTML-страницы, графические изображения... Лежащее на поверхности решение – ограничить список файлов, подлежащих динамическому сжатию, по расширению (кстати, оно очень хорошо описано в документации к mod_deflate), можно отбросить сразу же. Расширение файла далеко не всегда соответствует его содержимому (те же архивы иногда называются как-нибудь вроде http://www.some-tricky-company.ru/download/download.pl?id=1234&uid=5678), кроме того, вариантов, подлежащих фильтрации, оказывается чересчур много (на самом деле, нам проще сказать, что должно сжиматься и выкинуть остальное). Вторая идея – использовать директиву “AddOutputFilterByType DEFLATE <список MIME-типов>” также терпит фиаско, поскольку она принципиально не способна работать с прокси-запросами (это редкое исключение, подтверждающее правило). Да и нужные нам шаблоны, типа “text/*”, не говоря уж о более сложных, ей явно не по зубам. Что же делать?

Решение существует! (Читатели, имеющие за плечами мат-мех или физфак наверняка испытали непроизвольный прилив радостных эмоций). Это специализированный модуль mod_filter, написанный Ником Кью (Nick Kew) и включенный в стандартный комплект поставки Apache 2.1, усеченная версия которого была портирована автором этих строк обратно в 2.0. В данном разделе речь будет вестись именно о ней.

Исходный код модуля доступен по адресу: http://ktf.physics.usu.ru/~val/mod_filter.zip. Чтобы установить его, распакуйте архив во временный каталог на вашем сервере и дайте команду: apxs -c -i -a mod_filter.c.

Основная идея mod_filter состоит в том, чтобы заменить обычный фильтр так называемым “умным” (smart filter). «Умный» фильтр – это некоторая абстрактная конструкция, содержащая условия срабатывания и набор так называемых провайдеров (опять это слово!), которые, в свою очередь, являются обычными фильтрами. В перспективе подобный подход может сократить количество дублирующегося кода, встречающегося практически в каждом модуле и дающего ответ на вопрос: “Должны ли мы обработать эту порцию данных?” Впрочем, сейчас нас будут интересовать сугубо практические аспекты.

Условия срабатывания “умного” фильтра могут использовать самую различную информацию: поля заголовков прямого запроса (req) и ответа на него (resp), значения внутренних переменных Apache, устанавливаемых с помощью mod_setenvif (env), имена обработчиков (handler), а таже тип передаваемых данных (Content-type).

Для создания “умного” фильтра используется директива “FilterDeclare <имя фильтра>”. Подключением отдельных провайдеров управляет директива “FilterProvider <имя фильтра> <имя провайдера> <условие>”. Здесь под именем провайдера подразумевается название “обычного” фильтра, например, DEFLATE. Завершив настройку “умного” фильтра, следует добавить его в «цепочку» командой FilterChain. При обработке поступившего запроса звенья цепочки последовательно просматриваются до тех пор, пока не будет найден фильтр, условие срабатывания которого для данного конкретного запроса окажется истинным. По умолчанию добавление нового “умного фильтра” происходит в конец цепочки, однако, это поведение можно подавить, используя специальные префиксы в директиве FilterChain. Подробности ищите в документации.

Теперь путь решения проблемы становится ясным. Нам необходимо добавить “умный” фильтр “Compressor” (конечно, вы можете использовать другое имя), который будет обрабатывать данные с типом “text/*”. На самом деле, диапазон MIME-типов, подлежащих сжатию, может быть более широким. Я, например, использую следующую конструкцию:

FilterProvider Compressor DEFLATE resp=Content-type $text/

FilterProvider Compressor DEFLATE resp=Content-type $application/xhtml

FilterProvider Compressor DEFLATE resp=Content-type $application/xml

Знак долара «$» в начале каждого условия обозначает операцию поиска по подстроке. Кроме него, доступен поиск по регулярному выражению (обрамляется символами «/»), а также весь спектр арифметических операций сравнения и специальное условие «*», которое всегда истинно. Обратите внимание, что мы используем «resp=Content-type» вместо «Content-type». Между этими двумя вариантами существует тонкое различие. В первом случае MIME-тип определяется по заголовкам, сформированным удаленным сервером, тогда как в последнем сведения поступают из локальной базы MIME-типов. Некоторые сайты, например, репозитарий SourceForge.net, используют для своих файлов двусмысленные имена, что приводит к недопониманию: http://prdownloads.sourceforge.net/project/release-x.y.tar.gz?download на проверку оказывается HTML-страницей, содержащей список доступных зеркал для файла release-x.y.tar.gz, поэтому лучше доверить право определения типа содержимого удаленному серверу. Уж он-то точно знает, что нам отправил.

Заключение

Вот и подошла к концу данная статья. Если вы внимательно и творчески следовали всем перечисленным рекомендациям, то сейчас в вашем распоряжении имеется многофункциональный сервер Apache, который может самостоятельно обрабатывать запросы, переадресовывать их удаленным web-узлам, кэшировать и динамически сжимать передаваемые данные, причем делает все это в рамках одной кодовой базы. Некоторые из описанных в статье функций в настоящий момент имеют статус экспериментальных или являются сторонними разработками. Ситуация изменится с выходом финальной версии Apache 2.1, который будет содержать “штатные“ реализации mod_cache и mod_filter. До тех пор вы, при желании, можете рассматривать предлагаемое решение как перспективное, хотя мой личный опыт показывает, что надежности вышеупомянутых модулей вполне достаточно для решения “бытовых” задач. Попробуйте сами!

Врезка 1. Пример вызова configure, обеспечивающий поддержку всех необходимых модулей

сonfigure \

--enable-proxy –-enable-proxy-http -–enable-proxy-ftp -–enable-proxy-connect\

--enable-cache –enable-disk-cache\

--enable-deflate\

--enable-mods-shared=all

Врезка 2. Фрагмент файла httpd.conf, реализующий описанную в статье систему

...

# Слушать порт 3128

Listen 3128

...

# Загрузить необходимые модули

LoadModule cache_module modules/mod_cache.so

LoadModule disk_cache_module modules/mod_disk_cache.so

...

LoadModule deflate_module modules/mod_deflate.so

...

LoadModule proxy_module modules/mod_proxy.so

LoadModule proxy_connect_module modules/mod_proxy_connect.so

LoadModule proxy_ftp_module modules/mod_proxy_ftp.so

LoadModule proxy_http_module modules/mod_proxy_http.so

...

LoadModule filter_module modules/mod_filter.so

...

# Создать виртуальный хост на порту 3128

NameVirtualHost *:3128

<VirtualHost *:3128>

# Включить поддержку прокси-запросов

ProxyRequests On

<Proxy *>

# Ограничение доступа по IP

Order deny,allow

Deny from all

Allow from 192.168.0.1/24

# или авторизация по имени пользователя и паролю

AuthName "No trespassers"

AuthType Basic

AuthUserFile <файл с реквизитами пользователей>

Require valid-user

# Если обе схемы используются совместно, укажите здесь

# All, чтобы затребовать разрешенный IP-адрес И правильный пароль

# Any, чтобы затребовать разрешенный IP-адрес ИЛИ правильный пароль

Satisfy Any

</Proxy>

# Настройки кэша

CacheEnable disk /

CacheRoot <путь к каталогу кэша>

CacheSize 51200

CacheDirLevels 2

CacheDirLength 3

# «Умный» фильтр для динамического сжатия

FilterDeclare Compressor

FilterProvider Compressor DEFLATE resp=Content-type $text/

FilterProvider Compressor DEFLATE resp=Content-type $application/xhtml

FilterProvider Compressor DEFLATE resp=Content-type $application/xml

FilterChain Compressor

</VirtualHost>


Статья была впервые опубликована в журнале "Системный администратор", апрель 2005

[ опубликовано 04/05/2006 ]

Валентин Синицын (val AT linuxcenter DOT ru) - Apache как прокси-сервер   Версия для печати