cat_mucius: (Default)
[personal profile] cat_mucius
Ишшо техническое - чтоб было, кому-нибудь да пригодится:

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-й порт, не получает ничего, и огорчается.

    Выглядит конфиг при этом так:
          <VirtualHost>
             ServerName external.com
             ProxyPass / http://internal.local/
             ProxyPassReverse / http://internal.local/
             <Proxy>
                AuthType Basic
                # authentication details here...
             </Proxy>
          </VirtualHost>
    

    Вот эта самая команда ProxyPassReverse и должна переводить редирект в HTTPS, но не переводит.

    И я натурально несколько часов кряду напряжённо гуглил, пытаясь понять, что не слава богу, и уж подумал было пойти на крайние меры и начать разбираться с mod_rewrite, как методом научного тыка выяснилось - всего-то навсего нужно было в явном виде указать, что у этого VirtualHost-а включён SSL:
          <VirtualHost>
             ServerName external.com
             ProxyPass / http://internal.local/
             ProxyPassReverse / http://internal.local/
             <Proxy>
                AuthType Basic
                # authentication details here...
             </Proxy>
             SSLEngine on
             SSLCertificateFile /path/to/public/cert
             SSLCertificateKeyFile /path/to/private/key
          </VirtualHost>
    

    После чего прокси начинает прекраснейшим образом передавать редиректы с префиксом https://.

    И это несмотря на то, что чертов VirtualHost прекрасно работал с SSL по дефолтовым настройкам. Причём SSLCertificateFile и SSLCertificateKeyFile опять же нужно обязательно прописать здесь явно, хоть по дефолту они уже прописаны - иначе Апач дохнет.

    Всего-то делов. Естественно, что не документированно оно нигде.


    2. Теперь другая фигня, решение которой ещё не придумал: хочется, чтобы этот самый прокси юзеров опознавал по сертификатам - а если сертификата нет, выдавал страничку с инструкцией, где их искать. Но беда-то в том, что если браузер не может наладить обмен ключами по SSL, то он не получит с сервера стандартный error page (как в случае провала аутентикации по паролю) - сервер просто оборвёт соединение. Что интересно, в Microsoft IIS этой проблемы нет.

    Частичное решение нашёл: прописать
    SSLVerifyClient optional

    чтобы соединение состоялось, а по вызываемому УРЛу положить скрипт, который бы проверял поля сертификата, и решение, куда юзера пускать, принимал по ним. Для вебсайта сгодится, но у нас-то прокси! Я ж не могу в скрипте указать "а если всё ок, так и быть, пускай юзера через прокси". Можно придумать какой-нибудь редирект, но как тогда быть, если юзер приходит по прямому линку?

    Update: благодаря этому замечательному посту проблема решилась - хоть для его понимания таки-пришлось вникать в mod_rewrite, а моя лень была сильно против. :-)

    Конфиг в результате приобрёл такую форму:
       <VirtualHost>
          ServerName external.com
          ProxyPass / http://internal.local/
          ProxyPassReverse / http://internal.local/
          SSLEngine on
          SSLCertificateFile /path/to/public/cert
          SSLCertificateKeyFile /path/to/private/key
          <Location />
             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]
          </Location>
       </VirtualHost>
    


    Итак, что тут происходит?
  • Во-первых, сервер сейчас продолжает обрабатывать запрос, а не выкидывает его нафиг, если нужного сертификата у юзера не нашлось (благодаря SSLVerifyClient optional).
  • Во-вторых, проверяет переменные SSL (доступные благодаря SSLOptions +StdEnvVars), и смотрит, есть ли у переменной SSL_CLIENT_VERIFY значение SUCCESS - если да, значит, аутентикация прошла успешно.
  • Если нет, редиректит юзера (из-за флага R) на некий скрипт (на другом внешнем сайте), передавая URL, по которому тот хотел пройти, в качестве параметра.
  • Скрипт выдаст юзеру извинительную объясняловку вида "вы хотели пройти по этому УРЛу, но увы..."

    Вот, собственно, и всё. Mischief managed.

  • You may post here only if cat_mucius has given you access; posting by non-Access List accounts has been disabled.
    If you don't have an account you can create one now.
    HTML doesn't work in the subject.
    More info about formatting

    Profile

    cat_mucius: (Default)
    cat_mucius

    June 2025

    S M T W T F S
    1 234567
    891011121314
    15161718192021
    22232425262728
    2930     

    Most Popular Tags

    Style Credit

    Expand Cut Tags

    No cut tags
    Page generated Jun. 23rd, 2025 01:33 pm
    Powered by Dreamwidth Studios