Почему 403 с VPN-сайта — не сбой прокси
403 от источника, отклонённый CONNECT и запрос 407 обозначают разные границы. Сначала сравните путь выхода прокси с VPN-адресом браузера.
Пользователи отключают VPN, получают 403 на внутреннем сайте и сообщают, что прокси сломан. Эту страницу могут создать три разные вещи: источник отклонил запрос, прямой прокси отказался открывать туннель или прокси потребовал собственные учётные данные. Каждый из них — правильный ответ на другой вопрос, и сведение их в одну общую страницу сбоя уничтожает единственное доказательство, которое у вас есть. Разложите отказ по компоненту, который его создал, а затем сравните сетевой путь, который каждый компонент действительно использует.
Назовите компонент, создавший 403
Код состояния HTTP принадлежит стороне, которая его отправила. Когда источник возвращает 403, он разобрал запрос и решил, что у вызывающего может не быть ресурса, — это ответ об авторизации, а не проблема транспорта. Когда прямой прокси отвечает 403 на CONNECT, он отказался строить туннель вообще, и ничего выше по потоку не было затронуто. Когда он отвечает 407, он хочет собственные учётные данные, которые не имеют никакого отношения к входу в приложение.
| What you observe | What it means |
|---|---|
| Origin replies 403 | The origin understood the request and denied it |
| Proxy refuses CONNECT with 403 | The proxy declined to open that tunnel |
| Proxy replies 407 | Proxy authentication is required first |
| Gateway replies 502 or 504 | Upstream failure or timeout, not a permission decision |
RFC 9110 определяет эти роли, включая запрос, специфичный для прокси. Прочитайте собственный статус ответа и любой заголовок Proxy-Authenticate до чтения тела, потому что дружелюбная HTML-страница ошибки обычно скрывает, какой компонент её написал.
Что туннель CONNECT скрывает от передачи байтов
После того как прокси возвращает 200 на CONNECT, он передаёт байты, а браузер поддерживает собственную сессию TLS с источником. Поэтому прокси, передающий байты, не может прочитать зашифрованный 403 внутри этой сессии и никогда не становится автором отказа. Отказ до открытия туннеля — совершенно другое событие, созданное на уровень ниже.
Обратный прокси, завершающий TLS, находится в другом положении: он владеет границей TLS, разбирает HTTP и может законно переписывать или перехватывать ответы. Эта способность — выбор конфигурации, а не свойство проксирования вообще, и она не даёт права обходить политику доступа приложения. Чтение ответа — это возможность; решение о том, что вызывающий может видеть, остаётся работой источника.
Практическое следствие: прежде чем толковать 403, нужно знать, какую из трёх форм вы используете. Добавление правил HTTP-инспекции к потоковому прокси не даёт ему видимости, а ожидание, что передача байтов объяснит отказ источника, отправит вас в неправильный журнал.
Сравните путь выхода прокси с адресом туннеля браузера
Браузер, подключённый к VPN, не переносит отдельный прокси-сервер в этот туннель автоматически. Источник видит адрес выхода прокси, и если прокси по-прежнему выходит через локальный маршрут по умолчанию, защищённый сайт видит адрес, которому никогда не настраивался доверять. Внутренний VPN-адрес на ноутбуке невидим для этого источника. Это самая частая причина, по которой работающий туннель всё ещё даёт 403 от сайта, который ведёт себя правильно для всех остальных.
Проверьте оба пути по отдельности, чтобы адреса нельзя было перепутать.
curl -sS -o /dev/null -w 'status=%{http_code}\n' --noproxy '*' https://198.51.100.20/private
curl -sS -o /dev/null -w 'status=%{http_code}\n' -x http://203.0.113.10:3128 https://198.51.100.20/privateПервый запрос показывает, что источник отвечает на ваш собственный адрес, второй — что он отвечает на адрес прокси. Совпадающая пара 403 указывает на правила авторизации источника; 200, за которым следует 403, указывает на путь прокси. Спросите владельца источника, какие исходные адреса набор правил действительно допускает, вместо того чтобы предполагать, что пул VPN включён.
Проверьте каждую границу запросом, который её изолирует
Изолируйте уровень прокси, попросив у него то, в чём он не может отказать по причинам приложения. CONNECT к хосту, до которого прокси имеет доступ, с отброшенным выводом скажет, работает ли допуск в туннель вообще.
curl -sSv -o /dev/null -x http://203.0.113.10:3128 https://198.51.100.20/ 2>&1 | grep -E 'CONNECT|HTTP/1.1|Proxy-Authenticate'
Если это выводит 200 для CONNECT, а затем TLS-рукопожатие, допуск прокси в порядке, и последующий 403 пришёл от источника. Если выводится 407, укажите учётные данные прокси через --proxy-user и повторите, прежде чем делать какой-либо вывод о сайте. Если выводится 403 в строке CONNECT, собственный список разрешений прокси — это граница, которую нужно изучить, а приложение здесь не участвует.
Держите учётные данные вне истории оболочки и вне вставленных расшифровок. Пароль прокси, набранный в командной строке, виден другим локальным пользователям, а зафиксированный след CONNECT с учётными данными хуже, чем его отсутствие.
Держите вердикт читаемым, вместо того чтобы переписывать страницу
Когда вы знаете, какой компонент отказал, сохраните этот ответ. Обратный прокси, который переименовывает 403 источника в тайм-аут 504 или заменяет его страницей успеха, скрывает решение об авторизации за историей о доступности и отправляет следующего ответственного в неправильную команду. Пользовательские страницы ошибок хороши, когда сохраняют статус и добавляют контекст.
Кэширование заслуживает такой же заботы. RFC 9111 регулирует, как общие кэши могут повторно использовать ответы, и отказ, созданный для одного вызывающего, не должен передаваться другому как разрешение. Если ответы об отказе вообще сохраняются, держите их частными и ограниченными и никогда не добавляйте публичное правило свежести только для уменьшения трафика вверх по потоку.
Завершите письменным вердиктом, в котором названы компонент, созданный им статус и исходный адрес, который увидел источник. Запишите точный запрос, действующие настройки прокси и время, чтобы следующий отчёт можно было сопоставить с теми же доказательствами, а не выводить заново из памяти. Эта запись отвечает на следующий отчёт за секунды и не даёт снова диагностировать тот же 403 как сбой VPN.