Files
pet-project-server/docs/backlog/no-secrets-in-playbook-output.md
av 0b2a3bd19c backlog: удалена задача local-image-build, она реализована
- все три приложения (homepage, transcriber, remembos) собирают образы
  локально ролью app_image, реестр и yc_*-переменные выведены из оборота
- ссылавшиеся задачи обновлены: narrow-secret-scopes и
  no-secrets-in-playbook-output больше не ждут эту работу
2026-07-25 16:26:31 +03:00

7.6 KiB
Raw Permalink Blame History

Никаких секретов в выводе плейбуков

Приоритет: Высокий

inv pl всегда запускает ansible-playbook --diff, поэтому любая задача template/copy, чей целевой файл содержит переменную vault, печатает значение в терминал — целой строкой, при каждом изменении файла. no_log: true стоит ровно в одном месте на весь репозиторий (roles/secrets), и именно поэтому miniflux деплоится молча, а остальные — нет.

Это тот же класс, что инцидент 24 июля: секрет попадает не в vault-файл, а в поток вывода, у которого нет никакой модели угроз. Скроллбек терминала, мультиплексор, лог CI, транскрипт агента — всё это места, куда расшифрованному значению попадать не должно, и куда оно попадает бесплатно.

Инвентарь (сверено 2026-07-25)

Задачи без no_log, рендерящие файл с секретом:

  • backupsconfig.template.toml, restic-shell.template.sh, rclone.template.conf: пароли репозиториев restic, ключи S3, доступ rclone
  • autheliaconfiguration.template.yml: jwt-секрет, секрет сессий, ключ шифрования хранилища, hmac и jwks OIDC, четыре client secret, SMTP; плюс users.secrets.yml — vault-файл копируется как есть, дифф покажет его расшифрованным
  • transcriberconfig.secrets.toml, тоже vault-файл копией
  • bifrostconfig.template.json: ключ DeepSeek, ключ шифрования
  • appriseserver.template.cfg: токен бота Telegram, SMTP
  • remembosconfig.template.toml: токены memos и Telegram
  • outline, tududi, wakapi, wanderer, gramps, gitea — секреты в environment: docker-compose, см. серию задач secrets-file-*

Чисто: miniflux (образец), memos, calibre, tuwunel, dashboard, caddy, netdata, dozzle, goaccess, rssbridge, homepage.

docker_login в homepage, transcriber и remembos с yc_oauth_token в аргументах модуля — снят: приложения собирают образы локально ролью app_image, реестра и токена в плейбуках больше нет.

Рычаги и их цена

  • no_log: true на задачу — прячет результат целиком, включая дифф. Так уже сделано в roles/secrets. Цена: не видно, что именно изменилось; на отладке снимать руками.
  • diff: false на задачу — прячет только содержимое диффа, статус changed остаётся виден. Хирургичнее, но не закрывает вывод при падении задачи, поэтому для файлов, состоящих из одних секретов, слабее.
  • Структурный вынос — секрет живёт в отдельном файле, который рендерит роль secrets под no_log, а основной файл ссылается на путь. Тогда дифф основного файла остаётся полезным. Это серия secrets-file-*, но она про environment: и не покрывает конфиги.
  • Убрать --diff из inv pl или выставить глобальный ANSIBLE_NO_LOG — режет обзор на всех задачах разом ради десятка проблемных. Не годится.

Порядок

  1. no_log: true на весь инвентарь вышесделано (коммит b7b1f49, 2026-07-25): пятнадцать задач-рендеров и три docker_login, всего 13 плейбуков. Не под no_log осталась одна задача — compose miniflux: он тянет из vault только miniflux_postgres_user и miniflux_postgres_database, а это имя пользователя и имя базы, не секреты (в secrets_vars роли secrets их нет). Дифф образцового файла оставлен читаемым сознательно.
  2. Где файл смешанный (compose, конфиг authelia) — вернуться и вынести секреты в отдельные файлы, сняв no_log с основного: он нужен ради диффа, который сейчас и теряется. Что именно умеет каждое приложение — матрица в справке, сверенная по исходникам, и задачи secrets-file-* по одной на приложение. Для authelia (конфиг на тысячу строк, дифф там самый ценный) ответ такой: пять полей закрываются AUTHELIA_..._FILE, оставшиеся два — только template-фильтром, а четыре client_secret штатно хранятся хэшем и тогда вообще перестают быть секретами. Для tududi и wanderer ответа нет: они не умеют читать секреты из файлов, и no_log для них — не первый шаг, а единственный.
  3. Защита от дрейфа: проверка в lefthook или CI — если шаблон ссылается на переменную, которой нет в открытых group_vars/vars, задача, которая его рендерит, обязана иметь no_log. Иначе следующее приложение заведут по образцу без него. Единственное исключение на сегодня — miniflux (см. шаг 1); чище не заводить исключение в правиле, а перенести miniflux_postgres_user и miniflux_postgres_database из vault в group_vars/all/main.yml — секретами они не являются. Правка в зашифрованном файле, поэтому за пользователем.

Готово, когда первый (создающий все файлы) прогон любого плейбука не печатает ни одного значения из vault, и это держится проверкой, а не вниманием.

Попутно стоит выяснить, почему молчит правило no-log-password в ansible-lint: похоже, оно смотрит на список модулей с параметром password, а не на содержимое шаблонов — тогда на него в этой задаче полагаться нельзя.

Связано: tasks.py (inv pl), roles/secrets (образец), files/backups, files/authelia, files/bifrost, files/apprise, files/remembos, files/transcriber/config.secrets.toml, vault-password-storage, narrow-secret-scopes.