Перейти к содержимому
OmniRoute source

MITM TPROXY Transparent Decrypt (Русский)

Каждый из остальных четырёх режимов перехвата имеет свои ограничения:

Режим Как перенаправляется трафик Ограничение
AgentBridge Подмена DNS через /etc/hosts для фиксированного набора хостов только зарегистрированные хосты IDE-агентов
Пользовательские хосты Подмена DNS через /etc/hosts для каждого хоста одна запись на хост; для редактирования требуется sudo
HTTP_PROXY Переменные среды HTTP_PROXY/HTTPS_PROXY только приложения, учитывающие переменную среды
Общесистемный прокси Настройки прокси ОС изменяет глобальное состояние; требует отката

Прозрачная расшифровка TPROXY перенаправляет трафик на уровне ядра. Она помечает новые локальные исходящие TCP-соединения с целевым портом (по умолчанию 443) в цепочке mangle OUTPUT, правило ip rule перенаправляет помеченные пакеты на локальную доставку, а при повторном входе цель TPROXY в mangle PREROUTING передаёт их прослушивателю IP_TRANSPARENT, который затем завершает TLS-соединение и захватывает незашифрованные данные.

Используйте этот режим, если требуется перехватить и расшифровать трафик процесса, который:

  • обращается к хосту, не зарегистрированному в AgentBridge, и
  • не учитывает HTTP_PROXY, и
  • не должен затрагиваться изменением общесистемных настроек прокси.

Поскольку перехват выполняется в ядре, исходному процессу не требуется изменять конфигурацию, однако процесс должен доверять динамическому центру сертификации, который устанавливает OmniRoute (см. §4).


Требование Подробности
ОС Только Linux — IP_TRANSPARENT является опцией сокета, доступной только в Linux. На всех остальных платформах загрузчик возвращает «недоступно».
Привилегии Возможность CAP_NET_ADMIN для создания прозрачного сокета и применения правил iptables/ip — на практике следует запускать от имени root.
Нативный аддон Небольшой аддон N-API (src/mitm/tproxy/native/transparent.c) должен быть собран или поставляться в виде предварительно собранного бинарного файла. См. §3.
Модули ядра iptables с поддержкой TPROXY, mangle и сопоставления mark (проверено с ядром 6.8.0).

Корректная деградация: если какое-либо требование не выполнено (система не на базе Linux, отсутствует набор инструментов, аддон не собран), загрузчик аддона (src/mitm/tproxy/transparentSocket.ts::loadTransparentAddon) возвращает null вместо выбрасывания исключения. После этого статус режима перехвата сообщает available: false, переключатель на панели управления отключается, а во всплывающей подсказке отображается текст «Для расшифровки TPROXY требуются Linux, права root и нативный аддон»; остальная часть OmniRoute продолжает работать.


Модуль Node net не может вызвать setsockopt(IP_TRANSPARENT) до bind(), как того требует TPROXY (в противном случае ядро отбрасывает перенаправленные пакеты). Аддон (src/mitm/tproxy/native/transparent.c, собираемый через binding.gyp) представляет собой небольшой модуль N-API, предоставляющий три функции, которые используются через transparentSocket.ts:

Функция аддона Работа с сокетом Назначение
createTransparentListener(ip, port) socket() + SO_REUSEADDR + IP_TRANSPARENT + bind() + listen(), возвращает исходный fd прозрачный прослушиватель перехвата (Node принимает fd через server.listen({ fd }))
setSocketMark(fd, mark) setsockopt SO_MARK для существующего fd предотвращение зацикливания (маркировка собственных сокетов прокси)
connectMarked(ip, port, mark) socket() + SO_MARK до неблокирующего connect(), возвращает fd перенаправление повторно зашифрованного трафика вышестоящему серверу (SYN содержит метку)

Исходный адрес назначения считывается из socket.localAddress/localPort — TPROXY сохраняет его, поэтому поиск через SO_ORIGINAL_DST/NAT не требуется.

Окно терминала
npm run build:native:tproxy # cd src/mitm/tproxy/native && node-gyp rebuild
# -> native/build/Release/transparent.node
  • Во время выполнения npm run build скрипт scripts/build/build-tproxy-native.mjs запускает node-gyp rebuild. Он предназначен только для Linux, а его сбой не является критическим — отсутствие набора инструментов просто делает режим перехвата недоступным.
  • assembleStandalone.mjs копирует build/Release/transparent.node в автономный пакет; transparentSocket.ts находит его как относительно модуля, так и относительно cwd (<cwd>/src/mitm/tproxy/native/...).
  • build/ и prebuilds/ игнорируются Git — бинарный файл собирается, но никогда не добавляется в репозиторий.

Загрузчик проверяет в порядке приоритета: native/build/Release/transparent.node, затем native/prebuilds/transparent.node (как относительно модуля, так и в <cwd>/src/mitm/tproxy/).


§4 Динамический CA для каждого SNI и установщик хранилища доверия

Заголовок раздела «§4 Динамический CA для каждого SNI и установщик хранилища доверия»

Обновление #6684: статический сервер AgentBridge (src/mitm/server.cjs) теперь использует ту же архитектурную схему CA/конечного сертификата вместо одного статического самоподписанного конечного сертификата. Он использует отдельный экземпляр CA (src/mitm/cert/rootCa.ts, сохраняемый в <DATA_DIR>/mitm/ca.key/ca.crt) и устанавливает сертификат в ранее существовавший слот хранилища доверия omniroute-mitm.crt (заменяя в нём старый одиночный конечный сертификат — очистка двойного доверия не требуется), оставаясь полностью отделённым от собственного слота TPROXY omniroute-tproxy-ca.crt, описанного ниже. Новые установки AgentBridge автоматически используют модель CA; установка, которая уже доверяет старому статическому конечному сертификату, продолжает использовать его, пока оператор явно не включит MITM_ROOT_CA_ENABLED=true (см. src/mitm/cert/migration.ts) — доверенный MITM CA, способный подписать конечный сертификат для любого хоста, обладает существенно большими возможностями, чем старый сертификат с фиксированным SAN, поэтому для уже доверяющей ему установки переключение никогда не выполняется незаметно.

Исторически статический MITM-сертификат AgentBridge работал только потому, что AgentBridge подменяет DNS для фиксированного набора хостов (теперь унифицированного с приведённой ниже моделью). TPROXY перехватывает произвольные хосты, поэтому его прослушиватель должен предъявлять действительный конечный сертификат для любого SNI, запрошенного клиентом, — то же требование теперь применяется к AgentBridge для полного набора MITM_TOOL_HOSTS (9 записей инструментов), а не только для 4 хостов antigravity.

DynamicCertStore запускает локальный CA (созданный на основе зависимости selfsigned), который:

  • Создаёт долгоживущий CA с помощью generateMitmCa() (CN "OmniRoute MITM CA", срок действия 10 лет, basicConstraints CA=true + keyUsage keyCertSign,cRLSign, 2048-битный RSA / SHA-256).
  • Выдаёт отдельный конечный сертификат для каждого имени хоста SNI по требованию с помощью issueLeafCert() (срок действия 1 год, subjectAltName = хост SNI) и кэширует по одному tls.SecureContext для каждого имени хоста.
  • Предоставляет createSNICallback() для сервера, завершающего TLS-соединение (см. §5).
  • Может быть создан с existingCa, чтобы CA оставался неизменным после перезапусков (и хранилище доверия не требовалось переустанавливать).

Закрытый ключ CA никогда не покидает машину.

Установщик хранилища доверия (src/mitm/tproxy/caTrust.ts)

Заголовок раздела «Установщик хранилища доверия (src/mitm/tproxy/caTrust.ts)»

Перехватываемый клиент должен доверять динамическому CA, поэтому при запуске режима перехвата сертификат CA устанавливается в хранилище доверия ОС в выделенный слот — omniroute-tproxy-ca.crt (константа TPROXY_CA_CERT_NAME), отдельный от слота статического MITM-сертификата (omniroute-mitm.crt), чтобы они никогда не перезаписывали друг друга.

installTproxyCa(caPem, sudoPassword?) определяет каталог доверенных сертификатов дистрибутива (сначала проверяется вариант Debian) и запускает соответствующую команду обновления:

Каталог доверенных сертификатов Команда обновления
/usr/local/share/ca-certificates update-ca-certificates
/etc/ca-certificates/trust-source/anchors update-ca-trust
/etc/pki/ca-trust/source/anchors update-ca-trust
/etc/pki/trust/anchors update-ca-certificates

При установке PEM помещается во временный файл, после чего с повышенными привилегиями выполняется mkdir -p для каталога доверенных сертификатов, командой cp подготовленный файл копируется в него, а затем запускается команда обновления. uninstallTproxyCa() удаляет только выделенный слот (не затрагивая статический MITM-сертификат) и обновляет хранилище; вне Linux операция ничего не делает.

Все привилегированные команды выполняются через execFileWithPassword (src/mitm/systemCommands.ts) — spawn с массивами аргументов, без оболочки и без строковой интерполяции (жёсткое правило №13). Когда процесс выполняется от имени root (например, на VPS), целевая команда запускается напрямую и пароль не требуется; на настольной системе не от имени root значение sudoPassword передаётся через стандартный ввод sudo -S.

Значение sudoPassword для настольной системы передаётся в теле POST-запроса, чтобы авторизовать установку в хранилище доверия; оно полностью игнорируется, если процесс выполняется от имени root.


Конвейер (всё находится в src/mitm/tproxy/):

локальное приложение ──TCP/443──▶ mangle OUTPUT помечает соединение (fwmark)
ip rule → локальная таблица маршрутизации → lo
mangle PREROUTING TPROXY → слушатель IP_TRANSPARENT (порт 8443)
│ captureMode.ts: считывает исходный адрес назначения из socket.localAddress
▼
tlsCapture.ts:
1. Завершает TLS-соединение КЛИЕНТА с отдельным сертификатом для каждого SNI (dynamicCert)
2. внутренний http.Server разбирает расшифрованный открытый текст
3. перехват → globalTrafficBuffer.push() с source: "tproxy"
(применяются sanitizeHeaders + maskSecret)
4. пересылает данные, ПОВТОРНО зашифровав их, исходному получателю
через сокет с обходной меткой (connectMarked, защита от зацикливания)
│
▼
исходный вышестоящий сервер (api.example.com)
  • Завершение TLS (createTlsCaptureServer): оборачивает перехваченный необработанный сокет в серверный tls.TLSSocket, используя SNI-обработчик динамического центра сертификации, а затем передаёт расшифрованный поток внутреннему http.Server (стандартный приём завершения соединения в MITM). Время жизни сокетов ограничено значением MITM_IDLE_TIMEOUT_MS, чтобы зависший туннель не мог исчерпать файловые дескрипторы.
  • Перехват (handleDecryptedRequest): помещает InterceptedRequest с source: "tproxy" и начальным статусом "in-flight" в буфер, предварительно обрабатывая заголовки с помощью sanitizeHeaders(), а тела — с помощью maskSecret(). Затем запись дополняется ответом, размерами и задержкой.
  • Повторно зашифрованная пересылка (createForward / realForward): повторно шифрует данные для исходного получателя. По умолчанию rejectUnauthorized имеет значение true (безопасно по умолчанию) — сертификат вышестоящего сервера проверяется относительно SNI/Host, запрошенного клиентом, поэтому прокси отклоняет именно то, что отклонил бы исходный клиент.

Поскольку правила помечают новые локальные исходящие соединения, собственная повторно зашифрованная пересылка прокси обычно перехватывалась бы повторно, что привело бы к бесконечному циклу. Путь пересылки защищён от этого с помощью обходной метки сокета (SO_MARK):

  • realForward открывает сокет к вышестоящему серверу через connectMarked(ip, port, DEFAULT_BYPASS_MARK) — DEFAULT_BYPASS_MARK = 0x539 — который устанавливает SO_MARK до вызова connect(), поэтому SYN-пакет пересылаемого соединения содержит обходную метку.
  • Правило mangle OUTPUT исключает соединения, уже содержащие обходную метку (-m mark ! --mark &lt;bypassMark&gt;), поэтому пересылаемое прокси соединение не помечается повторно и не попадает снова в TPROXY.

Примечание о реализации: сокет с обходной меткой должен устанавливаться через createConnection агента (https.request({ createConnection }) незаметно игнорируется при наличии агента), иначе для пересылки был бы открыт сокет без метки и зацикливание возобновилось бы. Это исправление защиты от зацикливания было проверено e2e-тестами.


Мера защиты Подробности
API только для loopback-интерфейса /api/tools/agent-bridge/tproxy охватывается префиксом /api/tools/agent-bridge/ в LOCAL_ONLY_API_PREFIXES (src/server/authz/routeGuard.ts). Ограничение loopback применяется до аутентификации (жёсткие правила №15 и №17): утёкший JWT, переданный через туннель, не позволит запустить перехват TPROXY, который применяет правила iptables и устанавливает CA в хранилище доверенных сертификатов посредством дочерних процессов.
Выделенное место для CA Динамический CA устанавливается как omniroute-tproxy-ca.crt, не перезаписывая статический сертификат MITM.
Ключ CA не покидает хост DynamicCertStore хранит ключ CA в памяти; он не экспортируется.
Маскирование секретов maskSecret() для тел запросов и ответов и sanitizeHeaders() для заголовков выполняются до globalTrafficBuffer.push().
Без интерполяции командной оболочки Все команды iptables/ip/хранилища доверенных сертификатов выполняются через execFile/execFileWithPassword с массивами аргументов (жёсткое правило №13).
Проверка сертификата вышестоящего сервера Повторно зашифрованное перенаправление по умолчанию проверяет сертификат вышестоящего сервера (rejectUnauthorized: true).
Санитизация ошибок Ответы маршрута с ошибками обрабатываются через sanitizeErrorMessage() (жёсткое правило №12).

CA для MITM — это мощная возможность. CA, которому доверяет ОС и который может подписывать сертификаты для любого хоста, означает, что всё перехватываемое OmniRoute содержимое может быть расшифровано. Эта возможность доступна только в явно включённом локальном режиме перехвата TPROXY, по умолчанию отключена, а запись в хранилище доверенных сертификатов удаляется при остановке режима.


§7 Транзакционное применение / откат правил брандмауэра

Заголовок раздела «§7 Транзакционное применение / откат правил брандмауэра»

Сбой никогда не должен оставлять после себя правило mangle или устаревший маршрут. Построитель команд (src/mitm/tproxy/commands.ts) и исполнитель (src/mitm/tproxy/setup.ts) гарантируют, что откат является точной операцией, обратной применению, и выполняется в обратном порядке.

applyTproxy(cfg) выполняет команды применения по порядку; при любом сбое он выполняет полный revertTproxy(cfg) в режиме максимальных усилий и повторно выбрасывает исключение — поэтому правила брандмауэра либо применяются полностью, либо полностью откатываются, но никогда не остаются применёнными частично. revertTproxy(cfg) выполняет обратные команды в обратном порядке и игнорирует сбои (операция идемпотентна — её можно безопасно вызывать безусловно, например при очистке AgentBridge в repairMitm()).

validateTproxyConfig(cfg) выполняется до любой команды: порты должны находиться в диапазоне 1–65535, mark/routeTable/bypassMark должны быть положительными целыми числами, а bypassMark должен отличаться от mark (защита от зацикливания).

Окно терминала
ip rule add fwmark &lt;mark&gt; lookup &lt;routeTable&gt;
ip route add local 0.0.0.0/0 dev lo table &lt;routeTable&gt;
iptables -t mangle -A OUTPUT -p tcp --dport &lt;dport&gt; -m mark ! --mark &lt;bypassMark&gt; -j MARK --set-mark &lt;mark&gt;
iptables -t mangle -A PREROUTING -p tcp --dport &lt;dport&gt; -m mark --mark &lt;mark&gt; -j TPROXY --on-port &lt;onPort&gt; --tproxy-mark &lt;mark&gt;

При откате они удаляются в обратном порядке: PREROUTING -D, OUTPUT -D, ip route del, ip rule del.

Этот механизм основан на OUTPUT, поскольку в сценарии MITM перехватывается локальный исходящий трафик (приложения на том же хосте), который TPROXY только в PREROUTING не видит — PREROUTING видит только перенаправляемый трафик. Цепочка OUTPUT маркирует новые локальные соединения, ip rule перенаправляет их на локальную доставку (lo), после чего PREROUTING направляет их прозрачному слушателю.


Запрос запуска (POST /api/tools/agent-bridge/tproxy) принимает следующие поля, проверяемые с помощью StartTproxyBodySchema (tproxy/route.ts). Все они необязательны и при отсутствии получают значения по умолчанию:

Поле Тип По умолчанию Примечания
dport int (1–65535) 443 TCP-порт назначения, который требуется прозрачно перехватывать
mark int (≥1) 0x2333 Метка брандмауэра, устанавливаемая в OUTPUT и сопоставляемая посредством ip rule + PREROUTING
onPort int (1–65535) 8443 Порт, к которому привязывается прозрачный слушатель (IP_TRANSPARENT)
routeTable int (≥1) 233 Идентификатор таблицы маршрутизации на основе политик, содержащей маршрут local 0.0.0.0/0
bypassMark int (≥1, ≠ mark) 0x539 Обходная метка сокета (SO_MARK), которую прокси устанавливает для собственных исходящих соединений; исключается в OUTPUT (защита от зацикливания)
sudoPassword string — Только для настольных систем без root-доступа: разрешает установку в хранилище доверия; игнорируется при наличии root-доступа

Для TPROXY нет переменных окружения — вся конфигурация передаётся через тело POST-запроса либо используются указанные выше значения по умолчанию.


  1. Откройте Инспектор трафика (/dashboard/tools/traffic-inspector).
  2. На панели режимов захвата найдите кнопку «Расшифровка TPROXY» ⚠ (src/app/(dashboard)/dashboard/tools/traffic-inspector/components/CaptureModesToolbar.tsx).
    • Если она отключена и отображается подсказка «Для расшифровки TPROXY требуются Linux, права root и нативный аддон», значит нативный аддон недоступен на этом хосте (система не на Linux, отсутствует набор инструментов сборки или аддон не собран). См. §2 и §3.
  3. Нажмите кнопку. Она вызывает POST /api/tools/agent-bridge/tproxy через startTproxyCaptureMode() (src/lib/inspector/tproxyCaptureApi.ts), который: создаёт динамический ЦС, открывает прозрачный слушатель, применяет правила межсетевого экрана и устанавливает ЦС в системное хранилище доверенных сертификатов.
  4. Во время работы переключатель становится янтарным и показывает текущее количество перехватов (· &lt;interceptCount&gt;). Перехваченные запросы появляются в списке запросов с source: "tproxy".
  5. Нажмите ещё раз, чтобы остановить работу: DELETE /api/tools/agent-bridge/tproxy через stopTproxyCaptureMode() закрывает слушатель, удаляет ЦС и отменяет правила межсетевого экрана.

Состояние режима захвата (работает / доступен / количество перехватов / порт слушателя) получается из GET /api/tools/agent-bridge/tproxy (getCaptureStatus() в src/mitm/tproxy/captureManager.ts). Одновременно может выполняться только один сеанс TPROXY — попытка запуска второго завершается ошибкой «Режим захвата TPROXY уже запущен».


Нативный аддон невозможно загрузить. Убедитесь, что используется Linux, аддон собран (npm run build:native:tproxy), а процесс может загрузить transparent.node. isTransparentSocketAvailable() управляет доступностью переключателя; GET /api/tools/agent-bridge/tproxy возвращает available: false, если аддон отсутствует.

  • Убедитесь, что перехватываемый процесс действительно подключается к настроенному dport (по умолчанию 443).
  • Убедитесь, что процесс доверяет динамическому ЦС. ЦС устанавливается как omniroute-tproxy-ca.crt; в приложениях с собственным хранилищем доверенных сертификатов (Firefox/Chrome NSS), возможно, потребуется также добавить сертификат туда.
  • Запустите самопроверку Диагностика AgentBridge (см. AGENTBRIDGE.md) для проверки доверия к сертификату и работоспособности сервера.

Устаревшие правила межсетевого экрана после сбоя

Заголовок раздела «Устаревшие правила межсетевого экрана после сбоя»

revertTproxy() является точной обратной операцией для применения правил и идемпотентна. Остановка режима отменяет правила; если OmniRoute был принудительно завершён во время сеанса, используйте действие Восстановление AgentBridge (POST /api/tools/agent-bridge/repair), чтобы отменить оставшееся системное состояние (подмена DNS, корневой ЦС, системный прокси-сервер). Правила TPROXY mangle и маршрут также автоматически очищаются при перезагрузке.

Бесконечный цикл / прокси-сервер перехватывает собственную пересылку

Заголовок раздела «Бесконечный цикл / прокси-сервер перехватывает собственную пересылку»

Это случай защиты от зацикливания. Убедитесь, что bypassMark отличается от mark (это обеспечивается валидацией) и что для пересылки используется connectMarked (он используется в realForward). См. §5 Защита от зацикливания.


Файл Назначение
src/mitm/tproxy/commands.ts Чистый построитель команд применения и отмены iptables/ip; validateTproxyConfig
src/mitm/tproxy/setup.ts Транзакционный исполнитель applyTproxy / revertTproxy (откат при сбое)
src/mitm/tproxy/transparentSocket.ts Загрузчик нативного аддона (loadTransparentAddon), createTransparentListenerFd, connectMarked, setSocketMark, isTransparentSocketAvailable
src/mitm/tproxy/native/transparent.c Аддон N-API: createTransparentListener (IP_TRANSPARENT), setSocketMark, connectMarked
src/mitm/tproxy/native/binding.gyp Манифест сборки node-gyp
src/mitm/tproxy/dynamicCert.ts DynamicCertStore — динамический ЦС для каждого SNI и кеш конечных сертификатов
src/mitm/tproxy/caTrust.ts Установка/удаление в хранилище доверия ОС (installTproxyCa / uninstallTproxyCa, выделенный слот)
src/mitm/tproxy/tlsCapture.ts Механизм расшифровки с терминированием TLS и повторно зашифрованной пересылкой с защитой от зацикливания
src/mitm/tproxy/captureMode.ts Оркестрация прозрачного слушателя; исходный адрес назначения считывается из socket.localAddress
src/mitm/tproxy/captureManager.ts Жизненный цикл синглтона: startCaptureMode / stopCaptureMode / getCaptureStatus
src/app/api/tools/agent-bridge/tproxy/route.ts Маршрут GET / POST / DELETE (LOCAL_ONLY)
src/lib/inspector/tproxyCaptureApi.ts Клиентские вспомогательные функции fetch (fetchTproxyStatus / startTproxyCaptureMode / stopTproxyCaptureMode)

Исходный код OmniRoute (a58000c7685f)

HagiCode

HagiCode — агентная среда разработки со структурированными процессами, параллельным выполнением несколькими агентами и интерфейсами Hero Dungeon.

Превращайте идеи в полезное ПО с более умным, быстрым и увлекательным агентным рабочим процессом.

Главный экран HagiCode в светлой теме
  • SmartСтруктурированные процессы превращают намерение в исполнимый путь от идеи до готового изменения.
  • EfficientМультиагентные процессы параллельно продвигают исследование, реализацию и проверку.
  • FunHero Dungeon делает длительную совместную разработку наглядной и увлекательной.
Перейти на HagiCode