(no subject)

Feb 24, 2012 18:27

Ишшо техническое - чтоб было, кому-нибудь да пригодится:

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.

  • security, networking, computing

    Previous post Next post
    Up