Схемы размещения фильтра: шлюз, исполнитель или оба
Политика http_filter задаётся в каждом компоненте отдельно: общей конфигурации у шлюза и исполнителя нет (см. Конфигурирование приложения). Поэтому «включить фильтр» — это не одно решение, а выбор схемы: где именно принимается решение о допуске запроса.
Практически осмысленных схем три:
| Схема | http_filter шлюза | http_filter исполнителя | Кратко |
|---|---|---|---|
| A. Двойной контроль | AllowList | AllowList (та же политика) | Максимальная защита: граница отсекает лишнее, исполнитель остаётся последним рубежом перед целевым сервисом. |
| B. Контроль на границе | AllowList | AllowAll | Самая дешёвая по ресурсам и по эксплуатации; опирается на доверие к внутреннему транспорту и на целостность процесса шлюза. |
| C. Контроль в глубине | AllowAll | AllowList | Осмысленна, когда границей владеет не та команда, что целевым сервисом, или как этап поэтапной раскатки. |
Во всех схемах речь идёт только о транзитном трафике клиентов. Служебные точки /health и /metrics самих компонентов работают на отдельных портах и фильтром не обрабатываются.
Что общего у всех схем: откуда вообще берутся запросы
Выбор схемы разумно делать, держа в голове одно свойство архитектуры (подробный разбор — в статье Взаимодействие gateway ↔ executor):
- единственный способ поместить запрос в очередь шлюза — прислать его на внешний HTTP-порт шлюза; API «положить запрос в очередь» у мостов нет;
- исполнитель не принимает входящих соединений от Netgap: он сам, по pull-модели, забирает запросы из моста запросов шлюза по mTLS (TLS 1.3) с проверкой ролевого SPIFFE ID сервера;
- каждое внутреннее соединение авторизуется отдельно: у каждого моста свой allowlist SPIFFE ID (deny by default), с проверкой цепочки и CRL при каждом рукопожатии.
Отсюда следует и сильная сторона схемы B (в очередь нельзя «подбросить» запрос снаружи), и смысл схемы A (в очередь можно подбросить запрос изнутри самого шлюза, если шлюз скомпрометирован или запущен с другой политикой).
Схема A: двойной контроль (шлюз + исполнитель)
Одинаковая политика AllowList в обеих конфигурациях. Это схема по умолчанию для промышленной эксплуатации и именно она собрана в примерах поставки config/netgap-gateway-restricted.yaml и config/netgap-executor-restricted.yaml.
# в конфигурации netgap-gateway и в конфигурации netgap-executor — посимвольно одинаково
http_filter:
mode: AllowList
rules:
- paths:
- /api/reports/**
methods:
- GET
- paths:
- /api/orders
methods:
- GET
- POST
Сиквенс: обычный запрос проходит две независимые проверки
Ключевая деталь: в GatewayRequest нет поля «фильтр пройден». Исполнитель не доверяет решению шлюза и не может его унаследовать — он принимает собственное решение по собственной конфигурации. Именно поэтому вторая проверка что-то значит.
Сиквенс: граница скомпрометирована или работает со старой политикой
Это ситуация, ради которой схема и существует. Запрос попал в очередь, минуя ограничение на границе, — например, процесс шлюза скомпрометирован, подменён бинарь, или шлюз перезапущен с ошибочной конфигурацией AllowAll.
Побочный, но ценный эффект: рост netgap_executor_blocked_requests при нулевом netgap_gateway_blocked_requests — однозначный сигнал, что граница работает не с той политикой, которую ожидает доверенный сегмент. Это готовое правило алертинга (см. Заблокированные запросы).
От каких векторов защищает вторая проверка
| Вектор | Как реализуется | Что даёт проверка на исполнителе |
|---|---|---|
| Компрометация процесса шлюза (RCE, подмена бинаря, эксплуатация уязвимости в разборе HTTP на границе) | Атакующий получает контроль над компонентом, который стоит ближе всего к недоверенной сети, и формирует произвольные запросы уже «изнутри» | Целевой сервис остаётся доступен только в пределах политики: расширить поверхность атаки на нём нельзя без второй компрометации — уже в доверенном сегменте |
Ошибочная или неполная раскатка политики (шлюз запущен с mode: AllowAll или со старым файлом конфигурации) | Человеческий фактор, откат конфигурации, рассинхронизация окружений | Ограничение продолжает действовать; факт расхождения виден в аудите и метриках исполнителя, а не остаётся незамеченным |
| Подмена или добавление компонента на границе (второй экземпляр шлюза, «временный» шлюз для отладки, будущий входящий балансировщик с собственной конфигурацией) | Запросы приходят из авторизованного мостами источника, но политика источника неизвестна | Политика проверяется там, где она принадлежит владельцу целевого сервиса, и не зависит от количества и настроек компонентов на границе |
| Злоупотребление доступом к мостам изнутри доверенного сегмента | Владелец валидного ключа с ролью http1-request-reader/http1-response-writer (см. остаточные риски внутреннего транспорта) | Ограничивает не сам доступ к мостам, но сужает то, что через контур можно выполнить на целевом сервисе |
| Расширение политики «по ошибке в одну сторону» | Кто-то расширил правила на границе, не согласовав с владельцем целевого сервиса | Новый путь или метод не заработает, пока изменение не внесено в обе конфигурации — политика становится согласованным решением двух сторон |
Чего вторая проверка не даёт
Честные границы схемы, чтобы не переоценивать её вклад:
- Это не защита от ошибки в самой реализации фильтра. Оба компонента используют один и тот же код сопоставления путей и методов: дефект матчера повторится на обоих рубежах одинаково.
- Это не защита от ошибки в политике. Правило, ошибочно разрешающее лишнее, будет одинаково ошибочным в обеих конфигурациях — на то она и одинаковая политика.
- Это не защита целевого сервиса от доверенного сегмента. Кто может обратиться к целевому сервису напрямую, минуя исполнителя, тот не ограничен фильтром вообще; здесь работают сетевая изоляция и аутентификация на самом сервисе.
- Это не аутентификация. Фильтр не смотрит на токены, заголовки и источник — см. Границы применимости.
Цена схемы
По производительности — на каждый запрос:
- дополнительное сопоставление пути и методов в исполнителе перед вызовом целевого сервиса. Операция дешёвая и предсказуемая: сравнение строк по сегментам пути, без регулярных выражений и без обращений вовне; стоимость линейна по числу правил и путей в них. На фоне разбора HTTP, двух вызовов gRPC поверх mTLS и собственно вызова целевого сервиса вклад пренебрежимо мал и в наблюдаемых величинах (
netgap_executor_target_call_time_seconds, гистограммы задержек шлюза) обычно не различим; - заметным этот вклад становится только на политиках с сотнями и тысячами правил: проверка выполняется дважды, поэтому и «дорогая» политика оплачивается дважды. Практическая рекомендация — держать политику компактной (объединять пути в одном правиле, использовать шаблоны по сегментам вместо перечисления), а не отказываться от второй проверки.
По эксплуатации — постоянно:
- две конфигурации нужно поддерживать синхронно, а любое изменение политики — это перезапуск обоих компонентов (конфигурация читается один раз при старте);
- в момент раскатки существует окно рассинхронизации; как его пройти безопасно — см. Порядок изменения политики;
- блокировка на исполнителе — «поздняя»: запрос уже занял место в очереди, удержал клиентское соединение в пуле и прошёл два вызова мостов. В штатном режиме таких блокировок нет (их отсекает граница), но при рассинхронизации ресурсы границы расходуются впустую.
Схема B: контроль только на шлюзе (исполнитель AllowAll)
# netgap-gateway
http_filter:
mode: AllowList
rules:
- paths:
- /api/reports/**
methods:
- GET
# netgap-executor
http_filter:
mode: AllowAll
Сиквенс: решение принимается один раз, на границе
Почему схема надёжна
Надёжность здесь опирается не на «доверие по умолчанию», а на конкретные свойства контура:
- Единственная точка входа. Единственный способ попасть в очередь шлюза — внешний HTTP-порт, который защищён фильтром. Другого канала подачи запроса не существует: у мостов нет операции «положить запрос», исполнитель работает по pull-модели и сам инициирует все внутренние соединения.
- Внутренний транспорт неотключаем и криптографически защищён. Обмен между шлюзом и исполнителем идёт только по mTLS (TLS 1.3) с взаимной аутентификацией; plaintext-режима и опции «не проверять сертификат» в продукте нет (ADR 0009).
- Авторизация раздельная и deny by default. Каждый мост принимает только клиентов из своего allowlist ролевых SPIFFE ID, с проверкой цепочки до корней из конфигурации и CRL при каждом рукопожатии. Сертификат чужой инсталляции или чужой роли отклоняется до передачи прикладных данных.
- Исполнитель не адресуем снаружи. Он не публикует входящих точек для трафика и разговаривает только с сервером, предъявившим ожидаемый ролевой SPIFFE ID, — перенаправить его на подставной «шлюз» подменой DNS или адреса не получается.
- Никакого персистентного следа конфигурации. Политика приходит одним YAML-документом в
stdinи не может быть подменена файлом на диске или переменной окружения.
Иначе говоря: в схеме B исполнитель доверяет содержимому очереди не «на слово», а потому, что путь в эту очередь один и он закрыт фильтром, а канал доставки аутентифицирован с обеих сторон.
Какие векторы и условия остаются
| Вектор | Условие реализации | Вероятность | Последствия и что видно оператору |
|---|---|---|---|
Ошибочная конфигурация границы: шлюз запущен с mode: AllowAll или с устаревшими правилами | Ошибка раскатки, откат конфигурации, ручной перезапуск «чтобы быстрее», рассинхронизация окружений | Наиболее вероятная из всех — человеческий фактор, не требующий атакующего | Ограничение исчезает полностью и тихо: целевой сервис доступен по всем путям. В метриках это выглядит как отсутствие блокировок, то есть «всё хорошо»; отдельного сигнала нет — в схеме A такой случай подсветил бы счётчик блокировок исполнителя |
| Компрометация процесса шлюза: RCE, подмена бинаря или конфигурации при запуске | Уязвимость в компоненте на границе либо доступ к хосту/пайплайну запуска шлюза | Низкая: компонент на Rust, узкая функциональность, конфигурация не берётся с диска. Но это именно тот компонент, который смотрит в недоверенную сеть | Полный обход политики; целевой сервис доступен целиком в пределах сетевой достижимости исполнителя. В аудите — штатные события прохождения запросов, признаков блокировки нет |
| Дополнительный или подменённый источник запросов на границе (второй шлюз, отладочный экземпляр, будущий входящий балансировщик со своей конфигурацией) | Административное решение или изменение топологии без пересмотра политики | Низкая-средняя, растёт вместе со сложностью инсталляции | Политика фактически определяется настройками нового компонента; исполнитель принимает всё, что пришло из авторизованного моста |
| Злоупотребление внутренним доступом: владелец валидного ключа роли исполнителя или узел в доверенном сегменте | Компрометация приватного ключа (окно — до попадания в CRL и перезапуска компонентов) либо доступ к сегменту | Низкая при коротких leaf-сертификатах и автоматизации выпуска | Фильтр этот вектор не закрывает ни в одной из схем: обратиться к целевому сервису напрямую можно, минуя Netgap. Компенсация — сетевая изоляция и аутентификация на целевом сервисе |
| Расхождение «что видит граница» и «что делает исполнитель» | Запрос модифицируется на пути между компонентами | Пренебрежимо мала | Целостность канала обеспечивает TLS 1.3: модификация трафика приводит к разрыву соединения, а не к проходу изменённого запроса |
Итог: единственный реалистичный сценарий полной потери контроля в схеме B — это состояние самого шлюза (его конфигурация или целостность его процесса). Схема B ровно на этом и стоит: она принимает целостность границы как допущение, а схема A — нет.
Компенсирующие меры, если выбрана схема B
- Доставлять конфигурацию шлюза только из доверенного secret-provider, без промежуточных файлов на диске (см. Конфигурирование приложения).
- Контролировать факт применения политики после каждого перезапуска: прогнать на границе заведомо запрещённый запрос и убедиться, что он получает 403/405 и приращение
netgap_gateway_blocked_requests. Проверка «фильтр включён» дешевле, чем расследование по факту. - Не рассчитывать на фильтр как на единственный контроль доступа: аутентификация на целевом сервисе остаётся обязательной.
- Помнить, что
http_filter: mode: AllowAllв исполнителе — это осознанный выбор схемы, зафиксированный в конфигурации: режим всегда задан явно, «забыть» секцию нельзя — без неё компонент не запустится.
Схема C: контроль только на исполнителе (шлюз AllowAll)
Схема-зеркало предыдущей, и она имеет смысл, хотя выглядит менее выгодной.
Когда осмысленна:
- разное владение компонентами: границей управляет команда периметра, целевым сервисом и исполнителем — команда сервиса; политика допуска принадлежит владельцу API и должна действовать независимо от настроек границы;
- на границе уже стоит внешний контроль — WAF, API-gateway или сетевые политики, — и дублировать его правилами Netgap на шлюзе не хотят, но последний рубеж перед целевым сервисом нужен;
- этап поэтапной раскатки: ужесточение сначала обкатывается в доверенном сегменте, где блокировки видны в метриках исполнителя, и только потом переносится на границу;
- несколько источников на границе: если запросы могут приходить от разных компонентов, единая точка применения политики — исполнитель.
Цена схемы:
- блокировка поздняя и дорогая. Запрещённый запрос занимает место в неограниченной по размеру очереди шлюза, паркует клиентское TCP-соединение в пуле, проходит вызовы обоих мостов поверх mTLS — и только потом получает отказ. Поток сканирующих запросов из недоверенной сети нагружает границу так, как если бы фильтра не было вовсе;
- граница не защищена от исчерпания ресурсов самой политикой: см. остаточный риск «шлюз буферизует запросы в памяти» во взаимодействии компонентов;
- теряется диагностический сигнал. В схемах A и B рост
netgap_executor_blocked_requestsпри нулевомnetgap_gateway_blocked_requestsозначает рассинхронизацию политик. В схеме C это штатное состояние, поэтому такой алерт настроить нельзя, и разбирать блокировки придётся по событиям аудита; - высокая кардинальность меток
pathв метриках блокировок исполнителя на сканирующем трафике — ограничивайте её на стороне сбора метрик (см. Метрики блокировок).
Клиент при этом получает ровно те же 403/405 с пустым телом, что и в схеме B, — снаружи разница не видна. То есть схема C даёт тот же уровень ограничения доступа к целевому сервису, но оплачивает его ресурсами границы.
Как выбрать
| Критерий | A. Двойной контроль | B. Только шлюз | C. Только исполнитель |
|---|---|---|---|
| Защита при компрометации/ошибке конфигурации шлюза | да | нет | да |
| Ранний отказ, экономия ресурсов границы | да | да | нет |
| Стоимость сопоставления на запрос | двойная | одинарная | одинарная |
| Сложность эксплуатации (синхронизация, перезапуски) | высокая | низкая | низкая |
| Работает сигнал «политики разошлись» | да | да | нет |
| Типовое применение | промышленный контур с недоверенной внешней сетью | внутренние и полудоверенные контуры, стенды с ограниченным доступом | разное владение компонентами, внешний WAF на границе, этап раскатки |
Рекомендация по умолчанию — схема A: её вычислительная цена мала и предсказуема, а закрывает она самый неприятный класс сценариев — те, где граница перестала быть надёжной. Схема B оправдана там, где целостность и конфигурация шлюза контролируются теми же средствами и той же командой, что и доверенный сегмент. Схема C — осознанный выбор в пользу владения политикой, а не в пользу производительности.
Порядок изменения политики в двух компонентах
Конфигурация читается один раз при старте, поэтому изменение политики — это перезапуск компонентов. Если одновременный перезапуск невозможен, соблюдайте одно правило: шлюз не должен оказаться мягче исполнителя — тогда все отказы происходят на границе, дёшево и с понятной диагностикой.
- Сужение политики (убираем путь или метод): сначала шлюз, затем исполнитель.
- Расширение политики (добавляем путь или метод): сначала исполнитель, затем шлюз.
- Переход B → A: сначала привести исполнителя к
AllowListс той же политикой — это расширение ограничения в глубину, внешнее поведение не меняется. - Переход A → B: сначала перевести исполнителя в
AllowAll, политика на границе продолжает действовать без перерыва.
После каждого перехода проверяйте по метрикам, что блокировки происходят там, где ожидается: netgap_gateway_blocked_requests — на границе, netgap_executor_blocked_requests — нулевой в схемах A и B.
Ссылки
- Конфигурирование фильтра HTTP-запросов — секция
http_filter, режимы, проверка конфигурации при старте. - Правила фильтра: пути и методы — синтаксис правил и семантика сопоставления.
- Заблокированные запросы: ответ, аудит и метрики — коды 403/405, события аудита, счётчики блокировок.
- Взаимодействие netgap-gateway и netgap-executor — pull-модель, mTLS-рукопожатие, остаточные риски внутреннего транспорта.
- Конфигурирование внутреннего транспорта — mTLS, ролевые SPIFFE ID, раздельная авторизация мостов.