Ишшо техническое - чтоб было, кому-нибудь да пригодится:
1. Задача: есть несколько внутренних сайтов, которым нужно наладить выход в Интернет. Но поскольку секюрити у них скорее всего так себе, требуется, чтобы перед ними стоял некий фильтр, который бы:
аутентифицировал бы всех юзеров перед тем, как пускать их на внутренние сайты
пускал бы весь траффик исключительно по HTTPS
Решили использовать Apache в режиме reverse proxy. С аутентикацией справились легко, а вот с HTTPS, как ни странно, возникла идиотская проблема. Допустим, внутренний сайт наш называется internal.local, а прокси - external.com. И вот мы заходим на https://external.com, а прокси перекидывает нас на корень внутреннего сайта по HTTP:
Юзер ------HTTPS------> Прокси ------HTTP------> Сайт
А сайт выдаёт редирект на собственную главную страницу:
HTTP/1.1 301 Moved Permanently
Location: http://internal.local/MainPage.php
Что должен сделать прокси? Перекинуть редирект браузеру, изменив URL:
HTTP/1.1 301 Moved Permanently
Location: https://external.com/MainPage.php
Только на деле эта собака адрес переводит, а протокол нет! И выдаёт:
Location: http://external.com/MainPage.php
После чего браузер безнадёжно скребётся в 80-й порт, не получает ничего, и огорчается.
Выглядит конфиг при этом так:
ServerName external.com
ProxyPass / http://internal.local/
ProxyPassReverse / http://internal.local/
AuthType Basic
# authentication details here...
Вот эта самая команда ProxyPassReverse и должна переводить редирект в HTTPS, но не переводит.
И я натурально несколько часов кряду напряжённо гуглил, пытаясь понять, что не слава богу, и уж подумал было пойти на крайние меры и начать разбираться с mod_rewrite, как методом научного тыка выяснилось - всего-то навсего нужно было в явном виде указать, что у этого VirtualHost-а включён SSL:
ServerName external.com
ProxyPass / http://internal.local/
ProxyPassReverse / http://internal.local/
AuthType Basic
# authentication details here...
SSLEngine on
SSLCertificateFile /path/to/public/cert
SSLCertificateKeyFile /path/to/private/key
После чего прокси начинает прекраснейшим образом передавать редиректы с префиксом https://.
И это несмотря на то, что чертов VirtualHost прекрасно работал с SSL по дефолтовым настройкам. Причём SSLCertificateFile и SSLCertificateKeyFile опять же нужно обязательно прописать здесь явно, хоть по дефолту они уже прописаны - иначе Апач дохнет.
Всего-то делов. Естественно, что не документированно оно нигде.
2. Теперь другая фигня, решение которой ещё не придумал: хочется, чтобы этот самый прокси юзеров опознавал по сертификатам - а если сертификата нет, выдавал страничку с инструкцией, где их искать. Но беда-то в том, что если браузер не может наладить обмен ключами по SSL, то он не получит с сервера стандартный error page (как в случае провала аутентикации по паролю) - сервер просто оборвёт соединение. Что интересно, в Microsoft IIS этой проблемы нет.
Частичное решение нашёл: прописать
SSLVerifyClient optional
чтобы соединение состоялось, а по вызываемому УРЛу положить скрипт, который бы проверял поля сертификата, и решение, куда юзера пускать, принимал по ним. Для вебсайта сгодится, но у нас-то прокси! Я ж не могу в скрипте указать "а если всё ок, так и быть, пускай юзера через прокси". Можно придумать какой-нибудь редирект, но как тогда быть, если юзер приходит по прямому линку?
Update: благодаря этому замечательному посту проблема решилась - хоть для его понимания таки-пришлось вникать в mod_rewrite, а моя лень была сильно против. :-)
Конфиг в результате приобрёл такую форму:
ServerName external.com
ProxyPass / http://internal.local/
ProxyPassReverse / http://internal.local/
SSLEngine on
SSLCertificateFile /path/to/public/cert
SSLCertificateKeyFile /path/to/private/key
SSLVerifyClient optional
SSLVerifyDepth 10
SSLOptions +StdEnvVars
RewriteEngine On
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !^SUCCESS$
RewriteRule (.*) https://also.external.com/path/to/error/message/script?$1 [R,L]
Итак, что тут происходит?
Во-первых, сервер сейчас продолжает обрабатывать запрос, а не выкидывает его нафиг, если нужного сертификата у юзера не нашлось (благодаря SSLVerifyClient optional).
Во-вторых, проверяет переменные SSL (доступные благодаря SSLOptions +StdEnvVars), и смотрит, есть ли у переменной SSL_CLIENT_VERIFY значение SUCCESS - если да, значит, аутентикация прошла успешно.
Если нет, редиректит юзера (из-за флага R) на некий скрипт (на другом внешнем сайте), передавая URL, по которому тот хотел пройти, в качестве параметра.
Скрипт выдаст юзеру извинительную объясняловку вида "вы хотели пройти по этому УРЛу, но увы..."
Вот, собственно, и всё. Mischief managed.