Directum RX за обратным прокси: nginx, HAProxy, Envoy, заголовки, WebSocket и типовые ошибки
Что ломается, когда перед Directum RX ставят nginx, HAProxy или Envoy. Заголовки адреса клиента и схемы, постоянное соединение для уведомлений, таймауты, размер загрузки, TLS. Два прокси с общим адресом вместо одной точки отказа, прокси в ДМЗ как укреплённый узел на границе, и как за пять минут понять, виноват прокси или система.
Коротко. Прокси передаёт серверу
Host, адрес клиента и схему, пропускает WebSocket (статус 101, соединение живёт минутами), ждёт ответа дольше самой долгой операции, принимает документы нужного размера и отдаёт полную цепочку сертификатов. Прокси два, с общим адресом или за балансировщиком. В ДМЗ узел видит изнутри только веб-серверы RX, не в домене, обновляется и отдаёт журналы наружу. При проблеме сначала повторите запрос в обход прокси, потом читайте время ответа сервера в журнале nginx, коды завершения HAProxy или флаги Envoy.
Перед веб-сервером Directum RX почти всегда стоит прокси: он принимает подключения пользователей, расшифровывает TLS и передаёт запросы дальше. Пока прокси настроен по умолчанию, система вроде бы работает. Потом документ на 20 МБ не загружается, уведомления о новых заданиях приходят с опозданием, а отчёт за год обрывается с ошибкой 504, хотя на сервере продолжает считаться. Каждую из этих проблем создаёт одна-две строки в конфигурации прокси.
Готовую конфигурацию nginx и HAProxy под ваши адреса собирает генератор на нашем сайте. Здесь разбираем, почему настройки такие и как искать проблему, когда что-то уже сломалось.
Симптом
| Что видит пользователь | Что обычно в журнале прокси | Где искать |
|---|---|---|
| Документ не загружается | 413 | лимит тела запроса |
| Долгий отчёт или массовая операция обрывается | 504 через ровно 60 с (nginx) или 15 с (Envoy) | таймаут ожидания ответа |
| Новые задания видны только после обновления страницы | постоянное соединение закрывается и открывается заново каждые несколько секунд или минут | WebSocket |
| После входа бесконечная переадресация или ошибка «redirect_uri» | 302 на http:// вместо https:// |
заголовок схемы |
| Вход падает с ошибкой 400 или 502 после нескольких дней работы | upstream sent too big header |
размер заголовков, большие cookie |
| В журналах RX у всех пользователей один и тот же адрес | заголовок адреса клиента | |
| Мобильное приложение или интеграция не подключается, браузер работает | ошибка TLS на стороне клиента | цепочка сертификатов |
Почему так
Прокси меняет три вещи, которые приложение видит как свойства запроса.
- Адрес клиента. Для сервера RX соединение приходит от прокси, поэтому без дополнительного заголовка в журналах и событиях безопасности окажется адрес прокси.
- Схема и имя сайта. Пользователь пришёл по
https://rx.example.ru, а до сервера запрос доходит поhttp://на внутренний адрес. Если приложение строит ссылки и адреса возврата после входа из того, что видит само, получаетсяhttp://и внутреннее имя. Провайдер входа (Keycloak, ADFS) такой адрес возврата отклоняет. - Время жизни соединения. У прокси свои таймауты, и они не знают, что соединение уведомлений веб-клиента должно жить часами, а отчёт может считаться пять минут.
Заголовки
Минимальный набор, который прокси передаёт серверу:
| Заголовок | Что передаёт |
|---|---|
Host |
имя сайта, которое набрал пользователь |
X-Forwarded-For |
адрес клиента, дописывается к цепочке |
X-Forwarded-Proto |
схема, https |
X-Forwarded-Host |
имя сайта, если Host переписывается |
Как именно сервер использует эти заголовки, зависит от версии и настроек RX, поэтому после настройки проверьте
результат по журналу веб-сервера: какой адрес клиента в нём записан. Если прокси несколько (балансировщик, WAF,
прокси), адрес пользователя первый в цепочке X-Forwarded-For, а доверять заголовку можно только от своих прокси:
иначе любой клиент подставит в него что угодно.
WebSocket
Веб-клиент держит постоянное соединение с сервером, по которому приходят уведомления. Соединение начинается как
обычный HTTP-запрос с заголовками Upgrade: websocket и Connection: Upgrade, сервер отвечает кодом 101,
и дальше по тому же соединению идёт обмен в обе стороны.
Что для этого нужно от прокси:
- nginx:
proxy_http_version 1.1, передачаUpgradeиConnection,proxy_read_timeoutбольше интервала, с которым соединение проверяется (обычно десятки секунд; час с запасом подходит). - HAProxy: WebSocket поддерживается без отдельных настроек, но после переключения протокола действует
timeout tunnel. Если он не задан, соединение закрывается поtimeout clientилиtimeout server, которые обычно короткие. - Envoy: в
HttpConnectionManagerнуженupgrade_configsсupgrade_type: websocket, иначе запрос на переключение протокола не проходит. Для маршрута с постоянным соединением таймаут маршрута отключают (timeout: 0s) и проверяютidle_timeout.
Проверить, что переключение проходит через прокси, проще всего в браузере: инструменты разработчика, вкладка «Сеть», фильтр WS. У соединения должен быть статус 101, и оно должно висеть открытым минутами. Если в списке каждые несколько секунд появляется новое соединение, прокси его рвёт.
С командной строки то же самое проверяется так (путь соединения возьмите из той же вкладки браузера):
curl -i -N --http1.1 \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
https://rx.example.ru/<путь соединения>
Ответ HTTP/1.1 101 Switching Protocols означает, что прокси пропускает переключение. Ответ 200, 400 или 426
означает, что заголовки по дороге потерялись.
Если прокси в порядке, а уведомлений всё равно нет, проблема глубже: сервис клиентских подключений не достучался до сервера уведомлений. Это разобрано в справочнике ошибок.
Таймауты, размеры и буферы
- Ожидание ответа. У nginx
proxy_read_timeoutпо умолчанию 60 секунд, у Envoy таймаут маршрута по умолчанию 15 секунд. Отчёты, выгрузки и массовые операции в RX идут дольше и получают 504, хотя на сервере продолжают выполняться. Значение ставится под самую долгую легитимную операцию. Ошибка разобрана в справочнике. - Размер тела запроса. nginx по умолчанию принимает 1 МБ (
client_max_body_size), остальное получает 413. У Envoy и HAProxy жёсткого лимита по умолчанию нет, но его может задать WAF перед ними. - Буферизация загрузки. nginx по умолчанию сначала складывает загружаемый файл на свой диск, потом передаёт
серверу.
proxy_request_buffering offпередаёт его по мере получения: меньше нагрузки на диск прокси и нет второго ожидания после загрузки. - Размер заголовков ответа. После входа через внешний провайдер в cookie лежат токены, и заголовок ответа
перерастает буфер nginx (
proxy_buffer_size, по умолчанию 4 или 8 КБ). Ошибка выглядит как upstream sent too big header и часто проявляется только у части пользователей: у тех, у кого больше групп и, значит, длиннее токен.
TLS
- Цепочка сертификатов. В файле сертификата на прокси должны быть сертификат сайта и промежуточные
сертификаты. Браузер достроит неполную цепочку сам, а клиенты на .NET и Java, мобильные приложения
и интеграции откажутся подключаться. Проверка:
openssl s_client -connect rx.example.ru:443 -showcertsдолжен показать больше одного сертификата. - Имя в сертификате. Современные браузеры смотрят только на поле SAN. Сертификат с именем лишь в CN отклоняется с NET::ERR_CERT_COMMON_NAME_INVALID.
- Протоколы. Только TLS 1.2 и 1.3. Старые версии отмечает любая проверка безопасности.
- От прокси до сервера. Внутри сети запросы можно передавать по HTTP, если сегмент закрыт. Если требования ИБ запрещают открытый трафик внутри, прокси шифрует запросы заново, и тогда ему нужно доверять сертификату сервера.
Два прокси вместо одного
Серверов RX ставят два, базу делают с репликой, а прокси перед всем этим почти всегда один. Когда он перезагружается после обновлений, система недоступна для всех пользователей, хотя всё за ним работает. Прокси не хранит состояния, поэтому второй экземпляр стоит дёшево.
Типовые схемы:
| Схема | Как работает | Где подходит |
|---|---|---|
| Два прокси и общий виртуальный адрес (keepalived, VRRP) | адрес висит на одном узле, при его отказе за секунды переезжает на второй | свои серверы, один сегмент сети |
| Два прокси за балансировщиком | внешний балансировщик (аппаратный, облачный, сетевой) раздаёт трафик на оба и проверяет их живость | облако, где VRRP не работает, или есть свой балансировщик |
| Два адреса в DNS | клиент сам выбирает адрес | только как дополнение: при отказе узла часть клиентов ещё долго ходит на мёртвый адрес |
Для схемы с виртуальным адресом достаточно keepalived на обоих узлах и проверки, что прокси живой:
vrrp_script chk_proxy {
script "/usr/bin/pgrep -x nginx"
interval 2
fall 2
rise 2
}
vrrp_instance RX {
state BACKUP
interface eth0
virtual_router_id 51
priority 100 # на втором узле 90
advert_int 1
nopreempt
unicast_peer { 10.10.0.12 } # адрес соседа; в сетях без multicast
virtual_ipaddress { 10.10.0.10/24 }
track_script { chk_proxy }
}
nopreempt не даёт адресу прыгать обратно, когда первый узел вернулся: лишнее переключение рвёт соединения
ещё раз. В облаках VRRP часто не работает, потому что сеть не пропускает перехват адреса, и там нужен
балансировщик облака.
Что ещё нужно для двух узлов:
- Одинаковая конфигурация. Оба прокси раскатываются из одного репозитория одним плейбуком, а не правятся руками по очереди. Как это устроить, рассказано в статье про GitOps.
- Сертификат на обоих. Продление сертификата проверяется на обоих узлах, и срок действия каждого стоит под наблюдением.
- Проверка серверов RX. HAProxy умеет активно проверять серверы за собой и снимать мёртвый из работы
до того, как на него попадёт пользователь. Бесплатный nginx проверяет только пассивно, по ошибкам
на реальных запросах (
max_fails,fail_timeout): первый пользователь после отказа сервера ошибку увидит. - Переключение рвёт соединения. Постоянные соединения уведомлений при переезде адреса обрываются, веб-клиент переподключается сам. Долгая операция, которая шла в этот момент, получит ошибку, поэтому обновления прокси делаются по одному узлу и вне пиковых часов.
Прокси в ДМЗ
Когда к RX ходят из интернета (удалённые сотрудники, мобильное приложение, контрагенты), прокси ставят в демилитаризованную зону: отдельный сегмент между внешним и внутренним межсетевыми экранами. Это единственный узел, который видит интернет, поэтому настраивается он строже остальных.
Сеть. Правила межсетевых экранов пишутся от узла:
| Направление | Разрешено |
|---|---|
| Из интернета в ДМЗ | 443, и 80 только для переадресации на HTTPS |
| Из ДМЗ во внутреннюю сеть | только адреса и порты веб-серверов RX (и провайдера входа, если вход идёт через него) |
| Из ДМЗ к базе, брокеру, контроллерам домена, хранилищу | запрещено |
| Из ДМЗ в интернет | только репозитории обновлений и NTP, через прокси или по списку |
| Из внутренней сети в ДМЗ | SSH и мониторинг только с адресов администраторов и системы мониторинга |
Смысл в том, что взломанный прокси не даёт злоумышленнику ничего, кроме того, что и так видно снаружи.
Узел.
- Минимальная установка ОС, на узле работает только прокси. Никаких других сервисов, агентов «на всякий случай» и учётных данных внутренних систем.
- Узел не вводится в домен. Доменная учётка на узле в ДМЗ это мостик внутрь.
- SSH только по ключам, без входа под root, только с адресов администраторов или через выделенный узел доступа.
- Обновления безопасности ОС ставятся автоматически или по регламенту, но не реже раза в месяц. Узел на границе с интернетом, у которого год не обновлялись пакеты, первым попадает в отчёт пентеста.
- Закрытый ключ сертификата доступен только root (
chmod 600), копий ключа на ноутбуках нет. - Время синхронизировано, журналы прокси и системы уходят на центральный сервер журналов или в SIEM. Журнал, который лежит только на взломанном узле, доверия не заслуживает.
Сам прокси.
- Сайт по умолчанию отвечает на запросы с чужим или пустым именем закрытием соединения (
return 444в nginx). Сканеры, которые перебирают адреса, не получают ни страницы, ни версии сервера. - Версия сервера не отдаётся в заголовках (
server_tokens off). - Ограничение частоты запросов на страницу входа (
limit_reqв nginx,stick-tableв HAProxy) против подбора паролей. - Короткие таймауты на получение заголовков и тела запроса от клиента (
client_header_timeout,client_body_timeout): медленные клиенты, которые держат соединения открытыми, не съедают все рабочие процессы. - Наружу публикуется только то, что нужно пользователям. Служебные адреса, проверки работоспособности,
метрики и консоль администратора провайдера входа (в Keycloak это
/admin) из интернета закрыты. - Защитные заголовки и только TLS 1.2 и 1.3: их собирает генератор конфигурации.
- Запросы внутрь шифруются заново, если служба ИБ требует закрытый трафик между сегментами.
Если у клиента есть WAF, он ставится перед прокси или вместо него, но правила для RX настраиваются вместе с его владельцами: WAF с правилами по умолчанию режет часть запросов веб-клиента и интеграций.
Диагностика: прокси или система
Первое действие при любой проблеме за прокси: повторить запрос в обход него, прямо на сервер RX с машины, где стоит прокси.
curl -sk -o /dev/null -w '%{http_code} %{time_total}s\n' https://rx.example.ru/<адрес>
curl -s -o /dev/null -w '%{http_code} %{time_total}s\n' -H 'Host: rx.example.ru' http://10.0.0.21:<порт>/<адрес>
Если напрямую работает, а через прокси нет, проблема в прокси. Дальше помогают журналы.
nginx. В стандартном формате журнала нет времени ответа сервера. Добавьте его:
log_format timing '$remote_addr "$request" $status $body_bytes_sent '
'rt=$request_time urt=$upstream_response_time ua="$upstream_addr"';
request_time намного больше upstream_response_time означает медленного клиента или сеть до него.
Если они равны и большие, медленный сервер.
HAProxy. В каждой строке журнала есть код завершения из двух символов: первый говорит, кто закрыл
соединение, второй на каком этапе. cD означает таймаут на стороне клиента во время передачи данных,
типичный признак WebSocket без timeout tunnel. sH означает, что сервер не прислал заголовки ответа
за timeout server, то есть долгая операция. SC означает отказ сервера в соединении.
Envoy. В журнале доступа поле флагов ответа: UT таймаут ожидания ответа сервера, UF не удалось
соединиться с сервером, UH нет живых серверов в группе, DC клиент закрыл соединение сам.
WAF. Если перед прокси стоит межсетевой экран прикладного уровня, часть запросов он отклоняет своей страницей с кодом 403 или обрывает соединение. Признак: запроса нет ни в журнале прокси, ни в журнале RX. В этом случае разбираться нужно с правилами WAF вместе с его владельцами.
Шифрование на участках за прокси разобрано отдельно: до PostgreSQL и до RabbitMQ.
Чего не делать
- Не ставить бесконечные таймауты на всё. Зависший запрос будет держать соединение и ресурсы сервера без ограничения. Длинный таймаут нужен только там, где он оправдан.
- Не доверять
X-Forwarded-Forот кого угодно. Подделанный адрес попадёт в журнал событий безопасности. - Не включать HSTS, пока HTTPS не работает стабильно. Браузеры запомнят правило на срок из заголовка, и откатиться на HTTP не получится.
- Не проверять сертификат только браузером. Браузер прощает неполную цепочку, остальные клиенты нет.
- Не ставить один прокси перед отказоустойчивой системой. Его перезагрузка останавливает всех пользователей.
- Не пускать прокси из ДМЗ во внутреннюю сеть дальше веб-серверов RX. И не вводить его в домен.
- Не публиковать наружу служебные адреса и консоль администратора провайдера входа.
- Не искать причину в RX, пока запрос в обход прокси не повторён.
Похожая картина у вас?
Разберём ваш контур за 30 минут бесплатного созвона: версия RX, состав сервисов, что болит сильнее всего.
Написать