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

94 lines
7.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Никаких секретов в выводе плейбуков
**Приоритет:** Высокий
`inv pl` всегда запускает `ansible-playbook --diff`, поэтому любая задача
`template`/`copy`, чей целевой файл содержит переменную vault, печатает значение
в терминал — целой строкой, при каждом изменении файла. `no_log: true` стоит
ровно в одном месте на весь репозиторий (`roles/secrets`), и именно поэтому
miniflux деплоится молча, а остальные — нет.
Это тот же класс, что инцидент 24 июля: секрет попадает не в vault-файл, а в
поток вывода, у которого нет никакой модели угроз. Скроллбек терминала,
мультиплексор, лог CI, транскрипт агента — всё это места, куда расшифрованному
значению попадать не должно, и куда оно попадает бесплатно.
## Инвентарь (сверено 2026-07-25)
Задачи без `no_log`, рендерящие файл с секретом:
- **backups** — `config.template.toml`, `restic-shell.template.sh`,
`rclone.template.conf`: пароли репозиториев restic, ключи S3, доступ rclone
- **authelia** — `configuration.template.yml`: jwt-секрет, секрет сессий, ключ
шифрования хранилища, hmac и jwks OIDC, четыре client secret, SMTP; плюс
`users.secrets.yml` — vault-файл копируется как есть, дифф покажет его
расшифрованным
- **transcriber** — `config.secrets.toml`, тоже vault-файл копией
- **bifrost** — `config.template.json`: ключ DeepSeek, ключ шифрования
- **apprise** — `server.template.cfg`: токен бота Telegram, SMTP
- **remembos** — `config.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` с основного: он нужен ради диффа, который сейчас
и теряется. Что именно умеет каждое приложение — матрица в
[справке](../drafts/secrets-file-support.md), сверенная по исходникам, и
задачи `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](vault-password-storage.md),
[narrow-secret-scopes](narrow-secret-scopes.md).