- Проверено по исходникам закреплённых версий: outline, wakapi, gitea, gramps и authelia умеют брать секреты из файла, tududi и wanderer — нет. - Отмечено, что `secrets-env-to-file` переросла в эпик: разбивать по приложениям отдельным заходом.
87 lines
6.8 KiB
Markdown
87 lines
6.8 KiB
Markdown
# Никаких секретов в выводе плейбуков
|
||
|
||
**Приоритет:** Высокий
|
||
|
||
`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-env-to-file](secrets-env-to-file.md)
|
||
|
||
Чисто: miniflux (образец), memos, calibre, tuwunel, dashboard, caddy, netdata,
|
||
dozzle, goaccess, rssbridge, homepage.
|
||
|
||
Отдельным пунктом — `docker_login` в homepage, transcriber и remembos:
|
||
`yc_oauth_token` идёт параметром модуля. По умолчанию аргументы не печатаются, но
|
||
всплывают при `-v` и в отчёте о падении. Закрывается тем же `no_log`, а совсем
|
||
уходит после [local-image-build](local-image-build.md).
|
||
|
||
## Рычаги и их цена
|
||
|
||
- **`no_log: true`** на задачу — прячет результат целиком, включая дифф. Так уже
|
||
сделано в `roles/secrets`. Цена: не видно, что именно изменилось; на отладке
|
||
снимать руками.
|
||
- **`diff: false`** на задачу — прячет только содержимое диффа, статус `changed`
|
||
остаётся виден. Хирургичнее, но не закрывает вывод при падении задачи, поэтому
|
||
для файлов, состоящих из одних секретов, слабее.
|
||
- **Структурный вынос** — секрет живёт в отдельном файле, который рендерит роль
|
||
`secrets` под `no_log`, а основной файл ссылается на путь. Тогда дифф основного
|
||
файла остаётся полезным. Это [secrets-env-to-file](secrets-env-to-file.md), но
|
||
она про `environment:` и не покрывает конфиги.
|
||
- **Убрать `--diff` из `inv pl`** или выставить глобальный `ANSIBLE_NO_LOG` —
|
||
режет обзор на всех задачах разом ради десятка проблемных. Не годится.
|
||
|
||
## Порядок
|
||
|
||
1. `no_log: true` на весь инвентарь выше — дёшево, закрывает целиком и сразу,
|
||
независимо от того, поедут ли структурные задачи.
|
||
2. Где файл смешанный (compose, конфиг authelia) — вернуться и вынести секреты в
|
||
отдельные файлы, сняв `no_log` с основного: он нужен ради диффа, который сейчас
|
||
и теряется. Что именно умеет каждое приложение — матрица в
|
||
[secrets-env-to-file](secrets-env-to-file.md), сверенная по исходникам. Для
|
||
authelia (конфиг на тысячу строк, дифф там самый ценный) ответ такой: пять
|
||
полей закрываются `AUTHELIA_..._FILE`, оставшиеся два — только
|
||
template-фильтром, а четыре `client_secret` штатно хранятся хэшем и тогда
|
||
вообще перестают быть секретами. Для tududi и wanderer ответа нет: они не
|
||
умеют читать секреты из файлов, и `no_log` для них — не первый шаг, а
|
||
единственный.
|
||
3. Защита от дрейфа: проверка в lefthook или CI — если шаблон ссылается на
|
||
переменную, которой нет в открытых `group_vars`/`vars`, задача, которая его
|
||
рендерит, обязана иметь `no_log`. Иначе следующее приложение заведут по
|
||
образцу без него.
|
||
|
||
Готово, когда первый (создающий все файлы) прогон любого плейбука не печатает ни
|
||
одного значения из 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).
|