ADR: Выбор способа конфигурации приложения
- Status: Accepted by Andrei Ganiushkin
andrey@ganyushkin.ru - Date: 2026-07-11
- Authors: Andrei Ganiushkin
andrey@ganyushkin.ru - Related issues: -
Context
В рамках ADR стоит задача выбрать один, бескомпромиссно безопасный способ конфигурации приложения, который минимизирует человеческий фактор, случайные утечки данных, уязвимости файловой системы и утечки через механизмы инспекции процессов операционной системы, строго соответствуя концепции Zero-Trust.
Продукт должен иметь возможность разворачиваться в гетерогенных средах: на «голом железе» (Bare-metal), виртуальных машинах (VM), в Docker и Kubernetes (K8s) без перекомпиляции бинарного файла под конкретного заказчика.
Decision
Выбрать передачу конфигурации исключительно через стандартный ввод процесса (stdin) в формате YAML как единственный способ передачи рабочей конфигурации приложения.
Оператор, init-система, контейнерный runtime или оркестратор обязан передать YAML-документ в стандартный ввод программы в момент запуска. Приложение не должно читать рабочую конфигурацию из файлов, переменных окружения или аргументов командной строки.
Правила реализации решения на уровне кода Rust:
- Единый источник истины: При запуске рабочей команды приложение считывает полный YAML-документ только из
stdin. - Полный отказ от альтернатив: Любая поддержка чтения рабочей конфигурации из файлов (
.yaml,.json,.toml,.env), переменных окружения и аргументов командной строки не допускается. - CLI не является каналом конфигурации: Аргументы командной строки могут выбирать только режим работы приложения, например запуск сервиса или печать дефолтного шаблона конфигурации, но не должны содержать параметры бизнес-логики или секреты.
- Fail-Fast Валидация: На этапе запуска YAML должен быть полностью прочитан, десериализован и провалидирован. При синтаксической ошибке, отсутствии обязательных полей или логическом конфликте правил изоляции приложение мгновенно завершает работу с кодом 1.
- Отсутствие персистентного следа: Приложение не создает, не читает и не обновляет файлы рабочей конфигурации на диске.
- Кастомный panic-hook: Стандартный обработчик паники должен быть переопределен через
panic::set_hook, чтобы при критических сбоях приложение не выводило сырой YAML, секреты или полную структуру конфигурации в логи.
Considered Alternatives
Alternative 1: Хранение конфигурации в файлах (YAML, TOML, JSON, .env)
Параметры и секреты записываются в статичные файлы на диске.
- Pros: Удобно описывать сложные, глубоко вложенные древовидные структуры данных и массивы.
- Cons: Критически небезопасно.
- Файлы конфигурации уязвимы к атакам класса Directory Traversal и Local File Inclusion (LFI) — если в логике изоляции возникнет брешь, злоумышленник сможет прочитать файл с диска.
- Файлы
.envи YAML часто случайно коммитятся в публичные или корпоративные Git-репозитории, а также автоматически включаются в незашифрованные системные бэкапы. - В Docker/K8s хранение настроек в файле внутри контейнера нарушает неизменяемость образов (Immutability) и усложняет динамическое управление секретами.
- Reason for rejection: Способ признан фундаментально небезопасным для систем защиты информации. Он оставляет персистентный цифровой след чувствительных данных на жестком диске.
Alternative 2: Передача конфигурации через аргументы командной строки (CLI)
Все параметры передаются флагами при запуске бинарного файла (например, ./netgap-gateway --port 80).
- Pros: Высокая скорость точечной настройки при ручном запуске.
- Cons: Критически небезопасно.
- Аргументы командной строки в Linux абсолютно открыты для инспекции процессов (Process Snooping). Любой непривилегированный пользователь или соседний сервис в системе может выполнить команду
ps auxилиcat /proc/<PID>/cmdlineи увидеть все секреты в открытом виде. - Все переданные аргументы навсегда сохраняются на жестком диске в истории команд терминала (например,
.bash_historyили.zsh_history).
- Аргументы командной строки в Linux абсолютно открыты для инспекции процессов (Process Snooping). Любой непривилегированный пользователь или соседний сервис в системе может выполнить команду
- Reason for rejection: Полностью компрометирует конфиденциальность параметров безопасности на уровне ядра ОС.
Alternative 3: Передача конфигурации через переменные окружения (Environment Variables)
Все параметры и секреты передаются в окружение процесса, например через NETGAP_* переменные.
- Pros: Хорошо поддерживается Docker, Kubernetes, systemd и внешними secret-management системами.
- Cons: Небезопасно.
- Переменные окружения являются частью состояния процесса, управляемого операционной системой, и могут быть раскрыты через механизмы инспекции окружения, дампы, диагностические инструменты или логи запуска.
- В Linux окружение процесса экспонируется через
/proc/<PID>/environи может быть прочитано субъектом с достаточными правами доступа к процессу. - Значения переменных могут сохраняться в конфигурации init-систем, манифестах оркестраторов, истории деплоя и системных хранилищах.
- Даже последующее удаление переменных из окружения процесса не гарантирует очистку области окружения, которую ядро Linux отдает через
/proc/<PID>/environ; надежная перезапись этой области требует небезопасной низкоуровневой работы с памятью процесса и несовместима с политикой#![forbid(unsafe_code)].
- Reason for rejection: Способ признан уязвимым, так как секреты попадают в хранилище и механизмы управления окружением операционной системы и инфраструктуры.
Consequences
Positive
- Минимизация персистентного следа: Рабочая конфигурация не должна храниться в файлах приложения, переменных окружения или аргументах процесса.
- Поддержка сложных структур: YAML естественно описывает вложенные структуры, списки правил, сетевые политики и параметры наблюдаемости без искусственного кодирования в плоские строки.
- Единый формат для всех сред: Один и тот же бинарный файл без изменений работает на Bare-metal, VM, Docker и Kubernetes, если среда запуска умеет безопасно передать YAML в
stdin. - Более безопасная диагностика: Кастомный
panic::set_hookснижает риск утечки сырой конфигурации при аварийном завершении.
Negative
- Среда запуска должна уметь безопасно сформировать и передать YAML-документ в
stdinбез записи на диск. - Нельзя использовать привычные механизмы конфигурации через
ENV,.env, mounted config files или CLI-флаги с параметрами. - При ручном запуске оператор обязан избегать shell-конструкций, которые создают временные файлы или сохраняют секреты в истории команд.
Risks and Mitigations
- Risk: Логирование сырого YAML или структуры конфигурации сторонними крэйтами/ПО при инициализации или падении приложения.
- Mitigation: Использовать кастомный хук паники (
panic::set_hook). Для структур конфигурации запрещать небезопасный автоматический вывод чувствительных полей и при необходимости реализовыватьstd::fmt::Debugвручную с редактированием секретов.
- Mitigation: Использовать кастомный хук паники (
- Risk: Небезопасная передача YAML оператором через файл или shell history.
- Mitigation: Документировать безопасные способы запуска, при которых YAML поступает в
stdinиз доверенного secret-provider, pipe или supervisor-механизма без сохранения рабочей конфигурации на диск.
- Mitigation: Документировать безопасные способы запуска, при которых YAML поступает в
- Risk: Частичное чтение или некорректная структура входного YAML.
- Mitigation: Читать
stdinполностью до старта сервиса, выполнять строгий YAML parsing и fail-fast валидацию до инициализации сетевых компонентов.
- Mitigation: Читать
Acceptance Criteria
- Рабочая команда считывает конфигурацию из
stdinкак YAML-документ. - Кодовая база приложения не содержит
dotenv-подхода и не читает рабочую конфигурацию из файлов на диске. - Переменные окружения не используются как источник рабочей конфигурации приложения.
- CLI-аргументы не принимают параметры бизнес-логики, сетевой политики или секреты; CLI может использоваться только для выбора команды, например запуска или печати дефолтного шаблона.
- При пустом, частичном или невалидном YAML во
stdinприложение завершается с кодом 1 без вывода секретов в stderr. - Кастомный
panic::set_hookустановлен до чтения и парсинга конфигурации.
Implementation Plan
- Step 1: Оставить CLI только для выбора режима работы приложения, без передачи параметров конфигурации.
- Step 2: Реализовать чтение полного YAML-документа из
stdinдля рабочей команды запуска. - Step 3: Реализовать десериализацию YAML в типизированную структуру конфигурации приложения.
- Step 4: Добавить fail-fast валидацию структуры до инициализации наблюдаемости, сетевых слушателей и бизнес-логики.
- Step 5: Реализовать команду печати дефолтного YAML-шаблона в
stdoutдля операторов и автоматизации. - Step 6: Установить кастомный
panic::set_hookдо чтения конфигурации и исключить вывод сырой конфигурации в аварийных сообщениях.
Verification
- Тест чтения из
stdin: Передать валидный YAML в стандартный ввод рабочей команды и убедиться, что приложение успешно стартует. - Тест отказа от ENV: Запустить приложение с заполненными переменными
NETGAP_*, но без YAML воstdin; убедиться, что переменные не используются как конфигурация. - Тест отказа от файлов: Убедиться с помощью утилит трассировки системных вызовов (например,
straceв Linux), что приложение при старте не совершает системных вызововopenилиopenatк файлам рабочей конфигурации на диске. - Тест отказа от CLI-конфигурации: Попытаться передать параметры бизнес-логики через аргументы командной строки и убедиться, что такой интерфейс отсутствует или вызывает ошибку запуска.
- Тест валидации: Передать пустой, частичный или синтаксически некорректный YAML в
stdin. Убедиться, что приложение падает с кодом 1 без вывода содержимого секретов в stderr. - Тест паник-хука: Вызвать контролируемую панику после установки
panic::set_hookи убедиться, что stderr/logs не содержат сырой YAML и секреты.
References
- Документация по безопасности ядра Linux: управление доступом к файловой системе
/proc. - Практики безопасного программирования на Rust.
- Rust
std::io: чтение данных из стандартного ввода процесса.