Указание виртуальному хосту Subversion на адрес VPN меняет то, какой сайт отвечает на запрос, а не то, кому разрешено его отправлять. Проверка доступа выполняется позже, по правилам в разделе, который совпал с URL, и имя хоста никогда не участвует в этом решении. Если ServerName был единственным изменением, репозиторий доступен ровно так же, как вчера. В конфигурации нужно ответить на два отдельных вопроса: какой сокет принял соединение и какую личность нёс запрос.

Что на самом деле определяет ServerName

Виртуальный хостинг по имени сравнивает заголовок Host с записями ServerName и ServerAlias, выбирает один хост и позволяет ему обработать запрос. Само соединение было принято раньше сокетом Listen, который принадлежит серверу, а не хосту. Поэтому имя, выглядящее приватным, ничего не защищает: любой клиент, достигший сокета, может отправить этот заголовок Host. Имя — это ключ маршрутизации, а ключи маршрутизации выбирает вызывающая сторона.

Соответствие можно увидеть без догадок. apachectl -S выводит каждый хост, его порт и имена и отмечает, какой из них является по умолчанию для адреса.

RequireAll — это контейнер, делающий обе проверки обязательными

Apache 2.4 оборачивает директивы Require одного раздела в неявный RequireAny, поэтому две голые директивы означают, что достаточно любой из них. Добавьте контейнер, и смысл меняется на противоположный:

<Location /svn/repo>
    AuthType Basic
    AuthName "Subversion"
    AuthBasicProvider file
    AuthUserFile /etc/apache2/svn.htpasswd
    <RequireAll>
        Require ip 10.8.0.0/24
        Require valid-user
    </RequireAll>
</Location>

RequireAll требует оба условия: адрес источника внутри диапазона туннеля и пользователя, которого принимает репозиторий. Именно это должно означать «только VPN» для URL Subversion. Справочник mod_authz_core описывает правила контейнера. Оставьте блок внутри существующего Location репозитория и не трогайте уже работающие директивы DAV, потому что этот фрагмент не заменяет ни AuthType, ни файл паролей, ни авторизацию на уровне пути.

Проверка источника берётся из mod_authz_host, поэтому убедитесь, что он загружен, прежде чем отлаживать что-либо ещё: apachectl -M | grep authz_host.

Адрес, который оценивает Apache, — это адрес сокета

Require ip сравнивает с адресом, который Apache записал для соединения, а это не всегда тот адрес, который клиент считает своим.

Когда VPN маршрутизирует свои адреса без трансляции, Apache видит адрес туннеля, и 10.8.0.0/24 совпадает. Когда шлюз транслирует, Apache видит шлюз, и правило тихо не совпадает ни с чем; то же происходит за балансировщиком, который завершает TLS перед хостом. Для этого случая существует mod_remoteip. RemoteIPHeader X-Forwarded-For вместе с RemoteIPTrustedProxy для балансировщика подменяет записанный адрес, и только тогда диапазону есть с чем сравнивать.

Доверять произвольному заголовку пересылки хуже, чем не иметь никакого правила. Любой клиент может отправить X-Forwarded-For с 10.8.0.9 и удовлетворить проверку диапазона, которая никогда не была привязана к доверенному переходу.

Наслоите слушателя и политику репозитория

Привязка — отдельный элемент управления и часто более простой. Listen 10.8.0.1:443 определяет, какие сокеты существуют, и запрос не может выбрать хост на сокете, который никогда не открывался. Цена — порядок запуска: если интерфейс туннеля выключен при старте Apache, привязка не удаётся и служба не поднимается. Свяжите её с порядком юнитов или привяжитесь к адресу, который существует независимо от туннеля.

Права на пути остаются в AuthzSVNAccessFile. Действительная учётная запись для сайта — не то же разрешение, что право записи в путь проекта, и сетевое правило этого не меняет.

Следите за слиянием разделов, когда блок находится во включаемом файле. Apache объединяет контейнеры авторизации по вложенным разделам, и наиболее конкретный подходящий раздел может заменить требование, а не добавиться к нему, поэтому правило на /svn может не дойти до /svn/repo/trunk, как только более узкий раздел определит своё.

Докажите оба отказа, прежде чем считать работу законченной

Проверьте четыре запроса, а не один. Внутри VPN с действительной учётной записью вы должны получить список репозитория. Внутри VPN с неверным паролем вы должны получить 401, а не список. Снаружи VPN с действительными учётными данными вы должны получить 403, и тот же публичный запрос к альтернативному имени хоста должен провалиться так же.

Одной команды на случай достаточно: curl -sI -u user:pass https://svn.example.com/svn/repo/, затем прочитайте строку состояния. Повторите работающий случай и через настоящий клиент, потому что svn info упражняет методы DAV, которые будут использовать коммиты и обновления.

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

Похожие статьи