backlog: заведён запрет секретов в выводе плейбуков
- `inv pl` идёт с `--diff`, и 14 задач в 12 плейбуках печатают отрендеренные секреты в терминал; `no_log` есть только в `roles/secrets`. Инвентарь, рычаги (`no_log`, `diff: false`, вынос в отдельный файл) и порядок — в задаче. - Актуализирована `secrets-env-to-file`: список приложений сверен с шаблонами (tuwunel чистый, у gitea только SMTP), добавлен мотив с диффом.
This commit is contained in:
@@ -21,6 +21,7 @@ _(sec-ревью)_ — три независимых security-ревью на м
|
|||||||
- [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_
|
- [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_
|
||||||
- [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_
|
- [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_
|
||||||
- [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata
|
- [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata
|
||||||
|
- [Никаких секретов в выводе плейбуков](no-secrets-in-playbook-output.md) — `inv pl` всегда с `--diff`, и каждая задача, рендерящая файл с секретом, печатает его в терминал; `no_log` стоит ровно в одном месте из четырнадцати
|
||||||
|
|
||||||
## Средний
|
## Средний
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Никаких секретов в выводе плейбуков
|
||||||
|
|
||||||
|
**Приоритет:** Высокий
|
||||||
|
|
||||||
|
`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` с основного: он нужен ради диффа, который сейчас
|
||||||
|
и теряется. Для authelia отдельно проверить, что из девяти секретов умеет
|
||||||
|
file-based подстановку — конфиг на тысячу строк, дифф там ценный.
|
||||||
|
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).
|
||||||
@@ -2,16 +2,35 @@
|
|||||||
|
|
||||||
**Приоритет:** средний
|
**Приоритет:** средний
|
||||||
|
|
||||||
miniflux сделан правильно — секреты через `*_FILE` + монтирование каталога
|
miniflux сделан правильно — роль `secrets` кладёт каждую переменную vault в
|
||||||
secrets `:ro` (`files/miniflux/docker-compose.template.yml`). Но outline
|
отдельный файл, каталог монтируется `:ro`, а compose ссылается на `*_FILE`
|
||||||
(DATABASE_URL с паролём, `SECRET_KEY`, `UTILS_SECRET`, OIDC secret, SMTP), wakapi
|
(`files/miniflux/docker-compose.template.yml`). Остальные держат секреты прямыми
|
||||||
(salt, SMTP), tududi (session secret, OIDC secret, ключ LLM), gitea (SMTP),
|
значениями в `environment:`:
|
||||||
gramps, tuwunel кладут секреты прямыми значениями в `environment:`. Такие
|
|
||||||
переменные читает любой член группы `docker`, любой процесс с доступом к
|
- **outline** — пароль postgres, `SECRET_KEY`, `UTILS_SECRET`, секрет OIDC, SMTP
|
||||||
|
- **tududi** — session secret, секрет OIDC, ключ LLM, SMTP
|
||||||
|
- **wakapi** — salt паролей, секрет OIDC, SMTP
|
||||||
|
- **wanderer** — мастер-ключ meilisearch, ключ шифрования pocketbase
|
||||||
|
- **gramps** — `gramps_secret_key`, SMTP
|
||||||
|
- **gitea** — только SMTP (`postbox_pass`)
|
||||||
|
|
||||||
|
tuwunel из списка убран: в его compose секретов нет (сверено 2026-07-25). memos и
|
||||||
|
calibre тоже чистые.
|
||||||
|
|
||||||
|
Такие переменные читает любой член группы `docker`, любой процесс с доступом к
|
||||||
`/proc/<pid>/environ`, и они попадают в `docker inspect`. Где приложение умеет
|
`/proc/<pid>/environ`, и они попадают в `docker inspect`. Где приложение умеет
|
||||||
`*_FILE`/конфиг-файл — постепенно перевести по образцу miniflux.
|
`*_FILE` или конфиг-файл — постепенно перевести по образцу miniflux.
|
||||||
|
|
||||||
Из sec-ревью (fable, 2026-07-13). Связка с [service-users-docker-group](service-users-docker-group.md).
|
Второй мотив, найденный позже: пока секрет стоит прямым значением в шаблоне,
|
||||||
|
`ansible-playbook --diff` печатает его в терминал при каждом изменении файла.
|
||||||
|
Вынос в отдельный файл чинит это структурно — дифф compose остаётся читаемым, а
|
||||||
|
секретный файл рендерит роль `secrets` под `no_log`. Но покрывает только
|
||||||
|
`environment:`; конфиги (authelia, bifrost, backups, remembos) и общий запрет на
|
||||||
|
вывод — в [no-secrets-in-playbook-output](no-secrets-in-playbook-output.md), и
|
||||||
|
она идёт первой: там дёшево и закрывает всё сразу.
|
||||||
|
|
||||||
Связано: files/outline, files/wakapi, files/tududi, files/gitea, files/gramps,
|
Из sec-ревью (fable, 2026-07-13). Связка с
|
||||||
files/tuwunel, files/miniflux (образец).
|
[service-users-docker-group](service-users-docker-group.md).
|
||||||
|
|
||||||
|
Связано: files/outline, files/wakapi, files/tududi, files/wanderer, files/gramps,
|
||||||
|
files/gitea, files/miniflux (образец), roles/secrets.
|
||||||
|
|||||||
Reference in New Issue
Block a user