← Все статьи

Basic auth пропал на 80-м порту после переезда с Ingress на Gateway API

Все статусы зелёные, мой curl возвращал 401. А вход в WordPress был открыт всем: Gateway API привязывает политику к одному listener'у, а аннотации Ingress покрывали все порты.

Через две недели после того, как мы перевели публичные маркетинговые сайты Optimacros с ingress-nginx на Envoy Gateway — миграцию, проверенную хост за хостом, — страница входа WordPress на всех девяти сайтах оказалась доступна без пароля. Прямо из браузера.

Неприятнее всего то, что все проверки говорили обратное:

  • каждая SecurityPolicy была Accepted: True;
  • каждый HTTPRoute — Accepted и ResolvedRefs;
  • проверка Gateway через curl --resolve <host>:443:<gateway-ip> возвращала 401, ровно как задумано.

Манифесты были правильными, статусы — зелёными, а моя собственная проверка воспроизводила «правильный» ответ. Не соглашался только браузер.

Маршрут жил на одном listener’е

Защищённый маршрут был привязан так:

parentRefs:
  - kind: Gateway
    name: <gateway>
    sectionName: https    # только порт 443

Но эта платформа намеренно отдаёт контент на listener’е :80. Внешний nginx-фронт терминирует TLS и ходит в кластер по обычному HTTP, а безусловный редирект на :80 давал ERR_TOO_MANY_REDIRECTS — фильтры Gateway API не умеют «редиректить, только если запрос ещё не HTTPS», потому что не читают X-Forwarded-Proto.

Значит, реальный путь клиентов — порт 80. А на порту 80 маршрута для страницы входа не было вовсе. Запросы проваливались в общее правило PathPrefix: / и попадали в WordPress без всякой аутентификации.

Покрытие не переводится один к одному

Вот концептуальная разница, которая всё и вызвала. ingress-nginx применял аннотации auth-* к Ingress независимо от порта: один объект покрывал и 80, и 443. Gateway API привязывает маршрут — а значит, и любую политику, которая на него нацелена, — к конкретному listener’у. Перевести пути и аннотации один к одному не значит перевести один к одному покрытие.

А проверка через --resolve …:443 перепроверяла ровно тот путь, которым никто не пользуется.

Фикс

Парный HTTPRoute на sectionName: http с тем же Exact-совпадением, тем же бэкендом, тем же секретом basic auth и своей SecurityPolicy. Фильтр заголовка X-Forwarded-Proto: https тоже пришлось скопировать, иначе WordPress, у которого адрес сайта HTTPS, отвечает 301, и фронт уходит в петлю.

Приоритеты по-прежнему на нашей стороне: Gateway API ставит Exact выше PathPrefix, так что AJAX-эндпоинту, на который опираются публичные формы, позже можно дать свой маршрут без аутентификации, не открывая страницу входа. Изменение шло через Git — GitOps-контроллер откатывает всё, что сделано руками, — и было чисто добавочным, так что откат не затронул бы рабочий путь на :443.

Выводы

  • Accepted: True у политики значит, что она привязалась к цели, а не что цель покрывает трафик. Поля статуса «у этого listener’а нет защищённого маршрута» не существует.
  • Проверяйте тем путём, которым ходят реальные клиенты. За фронтом, терминирующим TLS, --resolve на 443 — неправильная проверка. Добавьте контрольный хост, который не меняли.
  • После любого переезда с Ingress на Gateway API проверьте всё, что раньше приходило из аннотаций, не зависящих от порта: аутентификацию, allowlist’ы источников, rate limit, правила WAF.
  • Закрыть страницу входа — не значит закрыть WordPress: xmlrpc.php аутентифицирует сам и тоже должен быть закрыт на обоих listener’ах.