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

ADR: Доставка сертификатов в компоненты Netgap через конфигурацию

  • Status: Accepted by Andrei Ganiushkin andrey@ganyushkin.ru
  • Date: 2026-08-07
  • Authors: Andrei Ganiushkin andrey@ganyushkin.ru
  • Related issues: -

Context

ADR 0009 фиксирует, что все внутренние коммуникации Netgap выполняются по mTLS (TLS 1.3) с авторизацией по SPIFFE ID. Для работы mTLS каждому компоненту требуется набор криптографического материала и политик:

  • собственный leaf-сертификат и приватный ключ (с промежуточной цепочкой);
  • корневой сертификат (trust anchor), которым проверяются цепочки контрагентов;
  • CRL для проверки отзыва;
  • список SPIFFE ID сервисов, которым разрешено вызывать данный сервис.

ADR 0002 устанавливает единственный канал передачи рабочей конфигурации — YAML-документ через stdin процесса, без файлов, переменных окружения и CLI-аргументов. Требуется зафиксировать, как этот подход применяется к доставке сертификатов, ключей и связанных политик, включая нерешённый в ADR 0002 вопрос: как доставить в работающий процесс обновлённый сертификат при ротации (ADR 0009 требует горячую ротацию без рестарта), если stdin-конфигурация читается при запуске.

Дополнительно компоненты имеют внешние TLS-взаимодействия — входящие (внешние клиенты продукта) и исходящие (внешние системы заказчика). Способ их конфигурирования должен быть единообразен с внутренним mTLS.

Decision

Весь стартовый криптографический материал и политики доступа доставляются в компоненты исключительно через конфигурацию по ADR 0002: содержимое сертификатов, ключей, CRL и списки разрешённых SPIFFE ID передаются в YAML-документе через stdin, который закрывается сразу после чтения стартовой конфигурации. Ротацию сертификатов выполняет сам компонент по протоколу обновления сертификатов; параметры протокола передаются в той же стартовой конфигурации.

Правила, вытекающие из решения:

  1. Материал передаётся значением, а не ссылкой. Конфигурация содержит PEM-содержимое leaf-сертификата, приватного ключа, цепочки, корневого сертификата и CRL непосредственно в YAML-документе. Пути к файлам, URL и имена переменных окружения как способ указания криптографического материала не поддерживаются — компонент не читает его с диска и из окружения ни при каких настройках.

  2. Корни доверия — только из конфигурации. Truststore компонента формируется исключительно из корневых сертификатов, переданных в конфигурации. Системное хранилище доверенных корневых сертификатов ОС не используется, и возможности включить его использование не существует — by design. Это исключает влияние состояния ОС (добавленный атакующим или политикой ОС корень) на границу доверия продукта и делает trust-домен инсталляции полностью определяемым её конфигурацией.

  3. Списки разрешённых SPIFFE ID — в конфигурации. Явный список SPIFFE ID сервисов, которым разрешено вызывать данный сервис (deny by default, ADR 0009), задаётся в том же YAML-документе. Ожидаемые SPIFFE ID серверов для исходящих внутренних соединений задаются аналогично.

  4. stdin закрывается после чтения стартовой конфигурации. Компонент читает ровно один YAML-документ до EOF, валидирует его (fail-fast по ADR 0002) и немедленно закрывает дескриптор stdin. Канал ввода конфигурации прекращает существовать на всё оставшееся время жизни процесса. Держать stdin открытым для приёма обновлений запрещено by design: файловый дескриптор pipe не является аутентифицированным каналом — он наследуется дочерними процессами, может быть передан между процессами (SCM_RIGHTS) или получен через отладочные интерфейсы, поэтому записать в stdin работающего процесса способен не только исходный родитель. Долгоживущий открытый stdin образовывал бы канал внедрения криптографического материала и политик в работающий компонент.

  5. Ротация — самим компонентом по протоколу обновления сертификатов. Компонент самостоятельно обновляет свой leaf-сертификат: заблаговременно до истечения (по достижении настраиваемой доли срока действия, по умолчанию 2/3) генерирует новую ключевую пару в памяти процесса, формирует CSR и выполняет обмен с центром выпуска по одному из поддерживаемых протоколов. Параметры обновления задаются в стартовой конфигурации: протокол, endpoint центра выпуска, корень доверия для канала обмена, идентификаторы и порог ротации. Запрос аутентифицируется действующим сертификатом компонента по mTLS (re-enrollment). Полученный сертификат валидируется по цепочке до корня из конфигурации и атомарно подменяется в памяти rustls без рестарта процесса и без разрыва установленных соединений (ADR 0009). Должны быть рассмотрены к реализации протоколы обновления:

    • EST (RFC 7030) — основной протокол для внутреннего CA инсталляции: re-enrollment через /simplereenroll с аутентификацией действующим клиентским сертификатом по TLS;
    • CMP (RFC 4210, RFC 9480) — для интеграции с корпоративными PKI заказчика;
    • ACME (RFC 8555) — для серверных сертификатов внешних входящих подключений, выпускаемых публичным CA;
    • SCEP (RFC 8894) — только для совместимости с унаследованными PKI, где недоступны EST/CMP.

    SPIFFE Workload API сознательно не поддерживается — требует локального агента на узле (см. Alternative 5). CRL компонент обновляет самостоятельно по точке распространения (CDP), заданной в конфигурации. Обновление корней доверия и списков SPIFFE ID — изменение политики инсталляции; оно выполняется штатным перезапуском компонента с новой стартовой конфигурацией.

    Ручная ротация сертификата — это всегда рестарт приложения. Поскольку stdin закрыт (п. 4) и других входящих каналов управления у процесса нет (п. 6), «горячей» ручной подмены материала в работающем компоненте не существует by design. Если оператору требуется вручную заменить leaf-сертификат, ключ, корень доверия или списки SPIFFE ID (компрометация ключа, недоступность центра выпуска, изменение политики), единственный способ — перезапуск компонента с новой стартовой конфигурацией в stdin нового процесса, с разрывом установленных соединений. Ротация без рестарта возможна только автоматически, по протоколу обновления из настоящего пункта.

  6. Свойства безопасности каналов доставки. Приватный ключ либо передаётся один раз в стартовой конфигурации, либо генерируется внутри процесса при ротации; он не появляется на диске, в окружении процесса и в аргументах ни в один момент жизненного цикла. После закрытия stdin у процесса нет ни одного входящего канала управления: единственный канал обновления — исходящее mTLS-соединение к заранее сконфигурированному endpoint центра выпуска, доверие к которому закреплено корнем из конфигурации.

  7. Внешние взаимодействия конфигурируются так же. Серверные сертификаты для внешних входящих подключений и клиентские сертификаты/доверенные корни для внешних исходящих подключений передаются в тех же секциях конфигурации тем же способом (PEM-содержимое в YAML, ротация по протоколу обновления из п. 5). Отличие одно: для внешних взаимодействий TLS и/или mTLS опционален и определяется требованиями конкретной интеграции (например, внешний клиент без mTLS или исходящее соединение с проверкой по корню, переданному в конфигурации), тогда как для внутренних коммуникаций весь набор из Context обязателен, и его отсутствие — ошибка запуска. Правило п. 2 действует и здесь: доверие для внешних соединений задаётся только корнями из конфигурации, системное хранилище ОС не используется.

  8. Материал не журналируется. Приватные ключи и сырое содержимое конфигурационных документов не попадают в журналы и аварийный вывод (panic-hook по ADR 0002); в журнале фиксируется только факт и результат применения обновления (отпечаток сертификата, срок действия, причина отказа).

Considered Alternatives

Основной канал доставки (stdin YAML) зафиксирован в ADR 0002 и здесь не пересматривается. Альтернативы рассматривались для двух частных вопросов: источник корней доверия и канал доставки обновлённого материала при ротации.

Alternative 1: Корни доверия — только из конфигурации, хранилище ОС не используется

  • Pros:
    • Граница доверия продукта полностью определяется конфигурацией инсталляции и не зависит от состояния ОС; аудит сводится к аудиту одного YAML-документа.
    • Добавление корня в хранилище ОС (атакующим, групповой политикой, предустановкой вендора) не расширяет доверие компонентов Netgap.
    • Поведение идентично на всех целевых платформах (ADR 0008); нет платформенного кода чтения хранилищ Linux/Windows.
  • Cons:
    • Оператор обязан явно доставить корень в каждую конфигурацию, включая случаи, когда нужный корень уже есть в ОС.
    • Ротация корня требует обновления конфигурации всех компонентов (штатно решается п. 4).
  • Reason for selection: trust-домен под полным контролем инсталляции; исключён целый класс атак через хранилище ОС, которое продукт не контролирует.

Alternative 2: Системное хранилище доверенных корней ОС (само по себе или как опция)

  • Pros:
    • Не нужно передавать корень в конфигурации при использовании публичного CA.
    • Привычная модель для администраторов.
  • Cons:
    • Хранилище ОС — общесистемный ресурс вне контроля продукта: любой субъект с правами на его изменение расширяет границу доверия Netgap незаметно для конфигурации.
    • Содержит сотни корней публичных CA — каждый из них становится способным выпустить «валидный» сертификат для внутреннего компонента; это несовместимо с изоляцией trust-домена (ADR 0009).
    • Даже как отключённая по умолчанию опция создаёт валидное небезопасное состояние продукта — тот же класс проблем, что отвергнут в Alternative 6 ADR 0009.
    • Разное содержимое и API хранилищ на Linux и Windows — платформенно-зависимое поведение безопасности.
  • Reason for rejection: передаёт контроль над границей доверия внешней по отношению к продукту системе; отвергнуто без опции включения — by design.

Alternative 3 (ротация): поток YAML-документов в незакрытом stdin

Компонент не закрывает stdin после стартовой конфигурации; supervisor подаёт документы обновления (разделитель ---) в тот же pipe на протяжении жизни процесса.

  • Pros:
    • Единственный канал конфигурации на весь жизненный цикл, без сетевых обменов и протоколов PKI.
    • Обновлённый ключ проходит тем же путём, что и стартовый.
  • Cons:
    • Пишущий конец pipe не привязан к исходному родителю: дескриптор наследуется дочерними процессами, передаётся между процессами (SCM_RIGHTS), доступен через отладочные механизмы. Субъект, получивший запись в stdin, внедряет в работающий компонент произвольные сертификаты, корни доверия и списки SPIFFE ID — то есть переопределяет границу доверия.
    • Постоянно открытый канал ввода расширяет поверхность атаки работающего процесса и требует парсинга недоверенного ввода на всём времени жизни.
  • Reason for rejection: долгоживущий открытый stdin образует неаутентифицированный канал внедрения криптографического материала в работающий процесс; признано нарушением безопасности. stdin обязан закрываться сразу после чтения стартовой конфигурации.

Alternative 4 (ротация): перечитывание файлов сертификатов по сигналу (SIGHUP + пути на диске)

Классическая схема: сертификаты лежат в файлах, компонент перечитывает их по сигналу или inotify.

  • Pros:
    • Стандартный, широко поддерживаемый автоматизацией паттерн (cert-manager, Vault Agent пишут файлы).
  • Cons:
    • Требует хранения приватного ключа на диске — прямо нарушает ADR 0002 (отсутствие персистентного следа, уязвимость к LFI/бэкапам/снапшотам).
    • Появляется второй канал конфигурации (файловая система) с собственной моделью прав доступа, который нужно защищать и аудировать отдельно от stdin.
  • Reason for rejection: ломает ключевое свойство принятой модели конфигурации — секреты не существуют на диске.

Alternative 5 (ротация): локальный агент выдачи (SPIFFE Workload API / SPIRE Agent, Vault Agent)

Компонент получает и обновляет SVID через локальный UNIX-сокет агента.

  • Pros:
    • Зрелый стандарт (SPIFFE Workload API), автоматическая ротация без участия оператора.
    • Ключ также не появляется на диске приложения.
  • Cons:
    • Требует установки и сопровождения агента на каждом узле — противоречит модели поставки «статические бинарные файлы без агентов в ОС» (ADR 0008).
    • Агент и его сокет становятся частью границы доверия: локальный UNIX-сокет — входящий канал доставки материала с собственной моделью аутентификации вызывающего процесса, тогда как принятое решение оставляет только исходящее mTLS-соединение к центру выпуска.
    • Недоступен или существенно отличается на части целевых сред (Windows, bare-metal без инфраструктуры SPIRE).
  • Reason for rejection: внешняя зависимость в целевой среде; при этом идея (короткоживущие сертификаты, автоматическое обновление без участия оператора) реализована принятым решением средствами самого продукта через стандартные протоколы PKI.

Alternative 6 (ротация): рестарт процесса с новой конфигурацией

Ротация выполняется перезапуском компонента: supervisor подаёт новый YAML в stdin нового процесса.

  • Pros:
    • Не требует ни одного нового механизма: строго один документ в stdin за жизнь процесса.
    • Простейшая для реализации и тестирования модель.
  • Cons:
    • Разрывает установленные соединения и прерывает передачу данных через шлюз при каждой ротации; при коротких TTL (ADR 0009, часы–дни) это регулярные плановые перерывы связи.
    • Противоречит требованию горячей ротации без рестарта из ADR 0009.
  • Reason for rejection: несовместимо с короткоживущими сертификатами как штатной стратегией автоматической ротации; при этом остаётся единственным механизмом ручной ротации (любая ручная замена материала — обязательно рестарт, п. 5), резервным путём при недоступности протокола обновления и штатным способом изменения политик (корни доверия, списки SPIFFE ID).

Risks and Mitigations

  • Risk: центр выпуска недоступен в момент плановой ротации — сертификат рискует истечь.
    • Mitigation: ротация начинается заблаговременно (по умолчанию на 2/3 срока действия) и повторяется с экспоненциальным backoff до успеха; компонент экспортирует метрику остатка срока действия сертификата с алёртом заведомо раньше истечения. Резервный путь — ручная ротация, то есть обязательный рестарт с новой стартовой конфигурацией (Alternative 6, п. 5).
  • Risk: подмена endpoint центра выпуска или MITM канала обновления.
    • Mitigation: канал обмена — mTLS с проверкой сервера по корню, заданному в конфигурации (системное хранилище ОС не используется, п. 2); запрос аутентифицируется действующим сертификатом компонента; полученный сертификат валидируется по цепочке до корня из конфигурации до применения.
  • Risk: невалидный, недоверенный или частичный ответ центра выпуска (обрыв обмена, ошибка PKI-автоматизации).
    • Mitigation: новый материал применяется только целиком после успешной валидации; при ошибке действующий материал не изменяется, отказ журналируется без вывода содержимого ответа; применение каждого обновления журналируется с отпечатком сертификата.
  • Risk: ошибка в параметрах протокола обновления в стартовой конфигурации обнаруживается только к моменту ротации.
    • Mitigation: fail-fast валидация секции обновления при запуске; компонент выполняет проверочное обращение к endpoint центра выпуска на старте и журналирует его результат.
  • Risk: утечка приватного ключа через журналы или аварийный вывод при обмене по протоколу обновления.
    • Mitigation: те же меры, что для стартовой конфигурации по ADR 0002: кастомный panic::set_hook, ручные реализации Debug с редактированием секретов, запрет журналирования сырых документов.
  • Risk: рассинхронизация материала между компонентами при ротации корня (один компонент уже получил новый корень, другой ещё нет).
    • Mitigation: truststore допускает несколько корней одновременно — ротация корня выполняется в две фазы (перезапуск всех компонентов с конфигурацией, содержащей оба корня → перевыпуск leaf-сертификатов → перезапуск без старого корня); процедура фиксируется в эксплуатационной документации.

References