From 3b846536f9786c42e0e297bd06543c1d4b18ba88 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sat, 25 Jul 2026 12:03:08 +0300 Subject: [PATCH] =?UTF-8?q?backlog:=20=D0=B7=D0=B0=D0=B2=D0=B5=D0=B4=D1=91?= =?UTF-8?q?=D0=BD=20=D0=B7=D0=B0=D0=BF=D1=80=D0=B5=D1=82=20=D1=81=D0=B5?= =?UTF-8?q?=D0=BA=D1=80=D0=B5=D1=82=D0=BE=D0=B2=20=D0=B2=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D0=B5=20=D0=BF=D0=BB=D0=B5=D0=B9=D0=B1=D1=83?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `inv pl` идёт с `--diff`, и 14 задач в 12 плейбуках печатают отрендеренные секреты в терминал; `no_log` есть только в `roles/secrets`. Инвентарь, рычаги (`no_log`, `diff: false`, вынос в отдельный файл) и порядок — в задаче. - Актуализирована `secrets-env-to-file`: список приложений сверен с шаблонами (tuwunel чистый, у gitea только SMTP), добавлен мотив с диффом. --- docs/backlog/README.md | 1 + docs/backlog/no-secrets-in-playbook-output.md | 80 +++++++++++++++++++ docs/backlog/secrets-env-to-file.md | 39 ++++++--- 3 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 docs/backlog/no-secrets-in-playbook-output.md diff --git a/docs/backlog/README.md b/docs/backlog/README.md index f2cae6b..c9f4826 100644 --- a/docs/backlog/README.md +++ b/docs/backlog/README.md @@ -21,6 +21,7 @@ _(sec-ревью)_ — три независимых security-ревью на м - [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_ - [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_ - [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata +- [Никаких секретов в выводе плейбуков](no-secrets-in-playbook-output.md) — `inv pl` всегда с `--diff`, и каждая задача, рендерящая файл с секретом, печатает его в терминал; `no_log` стоит ровно в одном месте из четырнадцати ## Средний diff --git a/docs/backlog/no-secrets-in-playbook-output.md b/docs/backlog/no-secrets-in-playbook-output.md new file mode 100644 index 0000000..8774c79 --- /dev/null +++ b/docs/backlog/no-secrets-in-playbook-output.md @@ -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). diff --git a/docs/backlog/secrets-env-to-file.md b/docs/backlog/secrets-env-to-file.md index d3a1681..dca02d1 100644 --- a/docs/backlog/secrets-env-to-file.md +++ b/docs/backlog/secrets-env-to-file.md @@ -2,16 +2,35 @@ **Приоритет:** средний -miniflux сделан правильно — секреты через `*_FILE` + монтирование каталога -secrets `:ro` (`files/miniflux/docker-compose.template.yml`). Но outline -(DATABASE_URL с паролём, `SECRET_KEY`, `UTILS_SECRET`, OIDC secret, SMTP), wakapi -(salt, SMTP), tududi (session secret, OIDC secret, ключ LLM), gitea (SMTP), -gramps, tuwunel кладут секреты прямыми значениями в `environment:`. Такие -переменные читает любой член группы `docker`, любой процесс с доступом к +miniflux сделан правильно — роль `secrets` кладёт каждую переменную vault в +отдельный файл, каталог монтируется `:ro`, а compose ссылается на `*_FILE` +(`files/miniflux/docker-compose.template.yml`). Остальные держат секреты прямыми +значениями в `environment:`: + +- **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//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, -files/tuwunel, files/miniflux (образец). +Из sec-ревью (fable, 2026-07-13). Связка с +[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.