Перейти к основному содержимому

Конфигурирование внутреннего транспорта

Все коммуникации между компонентами Netgap — сейчас это netgap-gatewaynetgap-executor, в будущем и другие компоненты, например входящий балансировщик — выполняются исключительно по mTLS (TLS 1.3) с авторизацией по SPIFFE ID. Незащищённого внутреннего транспорта и опции «отключить проверку сертификата» в продукте не существует — ни как режима по умолчанию, ни как настройки. Это решение зафиксировано в ADR 0009.

Эта статья описывает конфигурирование внутреннего транспорта. Пошаговый разбор того, как компоненты взаимодействуют поверх него — кто кого вызывает, как доставляются запрос и ответ, как проходит mTLS-рукопожатие и какие остаточные риски есть у схемы — вынесен в подстраницу Взаимодействие netgap-gateway и netgap-executor.

Каждое внутреннее соединение одновременно обеспечивает четыре свойства доверенной среды:

  • конфиденциальность — трафик недоступен для чтения третьей стороне;
  • целостность — модификация трафика в канале обнаруживается и приводит к разрыву соединения;
  • взаимная аутентификация — каждая сторона криптографически доказывает свою подлинность до передачи первого байта прикладных данных;
  • авторизация отправителя — принимающая сторона проверяет не только валидность цепочки сертификата, но и право контрагента выполнять конкретную операцию: авторизация раздельная по мостам, у каждой внутренней точки свой список разрешённых ролевых SPIFFE ID (deny by default).

Схема соединений

netgap-executor выступает клиентом и подключается к двум внутренним gRPC-мостам netgap-gateway:

  • мост запросов — по умолчанию 0.0.0.0:50051; из него исполнитель забирает входящие запросы;
  • мост ответов — по умолчанию 0.0.0.0:50052; в него исполнитель возвращает ответы целевого сервиса.

Адреса мостов задаются опциями шлюза request_bridge_listen_address и response_bridge_listen_address; со стороны исполнителя те же точки указываются опциями request_bridge_address и response_bridge_address.

SPIFFE ID в Netgap — атрибут роли (операции), а не компонента целиком: роль записывается последним сегментом пути, spiffe://<trust-domain>/<component>/<instance>/<role>. У исполнителя две клиентские роли — http1-request-reader (чтение очереди запросов) и http1-response-writer (доставка ответов), у шлюза две серверные — http1-request-bridge и http1-response-bridge. Один сертификат может нести несколько ролевых SPIFFE ID в SAN (так делает dev-PKI); при необходимости роли можно разнести по разным сертификатам — или в будущем по разным компонентам — без смены схемы конфигурации.

Проверка симметрична — обе стороны проверяют друг друга, и раздельна по операциям — каждый мост авторизуется независимо:

  • шлюз после проверки цепочки извлекает множество SPIFFE ID из клиентского сертификата и сверяет его со списком разрешённых идентичностей того моста, к которому подключился клиент (server.request_bridge.allowed_client_spiffe_ids или server.response_bridge.allowed_client_spiffe_ids); клиент авторизован, если хотя бы один из предъявленных ID есть в списке. Сертификат только с ролью http1-request-reader пройдёт на мост запросов, но будет отклонён мостом ответов — и наоборот;
  • исполнитель сверяет множество SPIFFE ID сервера с ожидаемым ролевым ID соответствующего моста (client.request_bridge.expected_server_spiffe_id и client.response_bridge.expected_server_spiffe_id) и отказывается работать с узлом, среди идентичностей которого нет ожидаемой. Это исключает перенаправление трафика на подставной узел с любым «валидным» сертификатом того же CA.

Секция конфигурации internal_transport

Секция internal_transport одинакова по структуре для всех компонентов и обязательна: конфигурация без неё отклоняется до запуска сетевых компонентов. Как передать конфигурацию в приложение, описано в статье Конфигурирование приложения.

internal_transport:
identity:
# PEM leaf-сертификата компонента (+ промежуточная цепочка, leaf первым).
# В SAN — один или несколько ролевых URI вида
# spiffe://<trust-domain>/<component>/<instance>/<role>.
certificate_chain_pem: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
# PEM приватного ключа (PKCS#8). Не журналируется, затирается в памяти.
private_key_pem: |
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
trust:
# Доверенные корни (может быть несколько — двухфазная ротация корня).
trusted_roots_pem: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
# CRL обязателен; для PKI без отозванных сертификатов — пустой CRL.
crl_pem: |
-----BEGIN X509 CRL-----
...
-----END X509 CRL-----
# Серверная роль (компонент публикует внутренние точки, например netgap-gateway).
# Авторизация раздельная по операциям: у каждого моста свой allowlist.
server:
# Мост запросов: кто имеет право читать очередь запросов.
request_bridge:
allowed_client_spiffe_ids:
- "spiffe://netgap.prod/netgap-executor/dc1/http1-request-reader"
# Мост ответов: кто имеет право доставлять ответы.
response_bridge:
allowed_client_spiffe_ids:
- "spiffe://netgap.prod/netgap-executor/dc1/http1-response-writer"
# Клиентская роль (компонент вызывает внутренние точки, например netgap-executor).
# Для каждого моста ожидается свой ролевой SPIFFE ID сервера.
client:
request_bridge:
expected_server_spiffe_id: "spiffe://netgap.prod/netgap-gateway/dc1/http1-request-bridge"
response_bridge:
expected_server_spiffe_id: "spiffe://netgap.prod/netgap-gateway/dc1/http1-response-bridge"

Роли server и client зависят от того, какие внутренние точки компонент публикует и какие вызывает:

  • у шлюза обязательна секция server с обеими подсекциями request_bridge и response_bridge — он публикует внутренние мосты и авторизует клиентов каждого моста по его собственному allowlist;
  • у исполнителя обязательна секция client с обеими подсекциями — он вызывает мосты шлюза и проверяет ролевую идентичность сервера для каждого подключения;
  • будущие компоненты (например, входящий балансировщик) могут иметь обе секции, если одновременно публикуют внутренние точки и вызывают чужие.

Доставка криптографического материала

Правила доставки материала зафиксированы в ADR 0010:

  • Всё передаётся значением, а не ссылкой. Конфигурация содержит PEM-содержимое сертификатов, ключей и CRL непосредственно в YAML-документе, который поступает через stdin. Пути к файлам, URL и переменные окружения как способ указания криптографического материала не поддерживаются — компонент не читает его с диска и из окружения ни при каких настройках.
  • Доверие — только корни из конфигурации. Truststore компонента формируется исключительно из trusted_roots_pem; системное хранилище доверенных корневых сертификатов ОС не используется, и возможности включить его использование не существует — by design.
  • stdin закрывается после чтения конфигурации. Компонент читает ровно один YAML-документ до EOF, валидирует его и немедленно закрывает stdin; других входящих каналов управления у процесса нет.
  • Ручная ротация любого материала — всегда перезапуск процесса с новой конфигурацией в stdin нового процесса. «Горячей» ручной подмены сертификата, ключа, корня доверия или списков SPIFFE ID в работающем компоненте не существует by design.

Автоматическое обновление сертификатов по протоколам (EST, CMP, ACME, SCEP из ADR 0010) — запланировано, в текущей версии не реализовано. Штатная ротация сегодня — перезапуск компонента с новой конфигурацией.

Требования к сертификатам

  • SAN URI — один или несколько SPIFFE ID вида spiffe://<trust-domain>/<component>/<instance>/<role>, по одному на роль; дубликаты — ошибка. Несколько ID в одном сертификате — осознанное, документированное отступление от строгого X.509-SVID («ровно один ID»): оно позволяет одному сертификату нести несколько ролей и при необходимости разнести роли по разным сертификатам без смены схемы. Проверка по CN и DNS-именам для авторизации не используется.
  • EKUclientAuth и/или serverAuth по роли соединения; сертификаты без нужного назначения отклоняются.
  • Basic ConstraintsCA:FALSE для leaf-сертификатов.
  • Алгоритмы: leaf — ECDSA P-256 или Ed25519 (RSA допускается при длине ключа не менее 3072 бит, но не рекомендуется); CA — ECDSA P-384 или RSA 4096.
  • Протокол — только TLS 1.3 с AEAD-шифрами (TLS_AES_256_GCM_SHA384, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256).

Генерация сертификатов для разработки и тестов

Для разработки, тестов и лабораторных стендов в поставку входит утилита netgap-certgen (чистый Rust, openssl не требуется):

./bin/netgap-certgen dev --out local-pki [--trust-domain netgap.dev] [--instance default] [--days 30]

Команда создаёт в каталоге local-pki:

  • root-ca.pem / root-ca.key — корневой сертификат и ключ dev-CA;
  • crl.pem — пустой CRL;
  • сертификаты и ключи netgap-gateway и netgap-executor — каждый несёт по два ролевых SPIFFE ID (у шлюза …/http1-request-bridge и …/http1-response-bridge, у исполнителя …/http1-request-reader и …/http1-response-writer);
  • два готовых YAML-фрагмента netgap-gateway-internal-transport.yaml и netgap-executor-internal-transport.yaml с секцией internal_transport целиком.

Фрагменты дописываются к базовой конфигурации простым объединением:

cat config/netgap-gateway.yaml local-pki/netgap-gateway-internal-transport.yaml | exec env -i ./bin/netgap-gateway run
cat config/netgap-executor.yaml local-pki/netgap-executor-internal-transport.yaml | exec env -i ./bin/netgap-executor run

Сертификат дополнительного компонента выпускается тем же dev-CA командой:

./bin/netgap-certgen component --out local-pki --name <имя> --trust-domain <домен> [--role <роль>]...

Опция --role повторяемая: каждое значение добавляет в SAN ролевой SPIFFE ID spiffe://<домен>/<имя>/<instance>/<роль>. Без опции сертификат несёт один SPIFFE ID без ролевого сегмента.

Важно: сертификаты, сгенерированные netgap-certgen предназначены только для разработки и тестов. В продакшене используется PKI заказчика — корпоративный УЦ, Vault PKI или аналогичный механизм. Как развернуть собственную PKI, автоматизировать выпуск и проверять идентичность, описано в методичке Работа с сертификатами: Private PKI, Автоматизация выпуска, Идентичность клиента.

Отказы и диагностика

  • Невалидная или неполная секция internal_transport — отказ запуска (fail-fast): компонент сообщает причину и завершается до открытия сетевых точек.
  • Отклонённые рукопожатия — невалидная цепочка, отозванный сертификат, неразрешённый SPIFFE ID, истёкший срок действия — журналируются как события безопасности с указанием причины и предъявленной идентичности.

Типовые ошибки:

СимптомПричинаДействие
Рукопожатие отклоняется с причиной «истёкший сертификат»Истёк срок действия материала dev-стенда (по умолчанию 30 дней)Перегенерировать dev-PKI: ./bin/netgap-certgen dev --out local-pki и перезапустить компоненты
Клиент отклонён с валидным на вид сертификатомНесовпадение trust-домена: сертификат выпущен PKI другой инсталляцииВыпустить сертификат в PKI той же инсталляции; сверить --trust-domain и корни в trusted_roots_pem
Рукопожатие отклоняется с причиной «неразрешённый SPIFFE ID»Ни один из ролевых SPIFFE ID клиента не входит в allowed_client_spiffe_ids того моста, к которому он подключается (например, сертификат только с ролью http1-request-reader предъявлен мосту ответов)Добавить нужный ролевой SPIFFE ID в список соответствующего моста (или выпустить сертификат с нужной ролью) и перезапустить компоненты
Компонент отказывается запускаться после обновления с сообщением о неизвестном ключе в internal_transportКонфигурация в старом плоском формате: allowed_client_spiffe_ids/expected_server_spiffe_id прямо под server/clientПеревести секцию на подсекции request_bridge/response_bridge (см. шаблон выше)

Ссылки