Files
pet-project-server/docs/backlog/secrets-env-to-file.md
T
av f9a8c64377 backlog: матрица поддержки файловых секретов по приложениям
- Проверено по исходникам закреплённых версий: outline, wakapi, gitea,
  gramps и authelia умеют брать секреты из файла, tududi и wanderer — нет.
- Отмечено, что `secrets-env-to-file` переросла в эпик: разбивать по
  приложениям отдельным заходом.
2026-07-25 12:20:20 +03:00

92 lines
8.3 KiB
Markdown
Raw 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.
# Секреты приложений через `*_FILE`, а не inline `environment:`
**Приоритет:** средний
miniflux сделан правильно — роль `secrets` кладёт каждую переменную vault в
отдельный файл, каталог монтируется `:ro`, а compose ссылается на `*_FILE`
(`files/miniflux/docker-compose.template.yml`). Остальные держат секреты прямыми
значениями в `environment:`: их читает любой член группы `docker`, любой процесс
с доступом к `/proc/<pid>/environ`, и они попадают в `docker inspect`.
tuwunel из списка убран: в его compose секретов нет (сверено 2026-07-25). memos и
calibre тоже чистые.
## Матрица поддержки (проверено по исходникам, 2026-07-25)
Проверялись **закреплённые в compose версии**, не `main`.
| Приложение | Секрет | Файл | Механизм |
|---|---|---|---|
| **outline** `1.9.2` | `SECRET_KEY`, `UTILS_SECRET`, OIDC, SMTP | да | generic: `environment.ts` оборачивает `process.env` в Proxy, пустая переменная читается из `<NAME>_FILE`. С v1.7.0 |
| | пароль postgres | да, с оговоркой | `DATABASE_URL_FILE` выносит URL целиком; чтобы вынести только пароль — раздельные `DATABASE_HOST/PORT/NAME/USER` + `DATABASE_PASSWORD_FILE` (взаимоисключимы с `DATABASE_URL`) |
| | пароль сайдкара postgres | да | штатный `POSTGRES_PASSWORD_FILE` официального образа |
| **wakapi** `2.17.5` | salt, SMTP, OIDC | да | два пути: generic `_FILE` в `loadSecretFiles()` (с 2.17.2, в README список устарел) либо `config.yml` в `/app/config.yml` |
| **gitea** `1.27.0` | SMTP | да, частично | `GITEA__mailer__PASSWD__FILE`, работает для любого ключа `app.ini`, с 1.20 |
| **gramps** `26.7.0` | `GRAMPSWEB_SECRET_KEY` | да | entrypoint читает `/app/secret/secret`, если переменная не задана. **Путь уже смонтирован** в нашем compose |
| | SMTP | нет | обходной путь есть, но недокументированный: `config.cfg` исполняется Flask как Python, значит `EMAIL_HOST_PASSWORD = open(...).read()` сработает |
| **authelia** `4.39.20` | jwt-секрет, session, ключ хранилища, hmac OIDC, SMTP | да | `AUTHELIA_..._FILE` |
| | jwks-ключ, `client_secret` × 4 | нет через `_FILE` | `IsSecretKey()` отвергает ключи с `[]`. Покрывается template-фильтром: `X_AUTHELIA_CONFIG_FILTERS=template` + `{{ secret "путь" }}` (с 4.38) |
| **tududi** `1.2.4` | все четыре | **нет** | всё через `process.env`; ни `_FILE`, ни конфига, ни CLI |
| **wanderer** `0.18.3` | мастер-ключ meilisearch | **нет** | только CLI/env/инлайн в `config.toml`; апстрим отклонил запрос в 2023 |
| | ключ шифрования pocketbase | **нет** | собственная переменная wanderer, не штатный `--encryptionEnv` pocketbase; ENTRYPOINT в exec-форме, подстановку не сделать |
## Что из этого следует
Самое дешёвое — **gramps**: каталог `/app/secret` уже смонтирован, достаточно
записать туда значение и убрать переменную из `environment:`. Оба сервиса
(`gramps_app` и `gramps_celery`) читают тот же файл через общий entrypoint.
**Authelia** выгоднее переводить не на `_FILE`, а целиком на template-фильтр:
`_FILE` покрывает пять полей из семи, фильтр — все разом, и смешивать их в одном
поле нельзя (`errFmtSecretAlreadyDefined`). Отдельная находка: четыре
`client_secret` штатно хранятся **хэшем** `$pbkdf2-sha512$...` (у нас уже есть
`inv authelia-gen-secret-and-hash`), плейнтекст с 4.38 вызывает warning
валидатора. То есть эти поля можно вообще перестать считать секретами.
**Gitea** даёт частичный выигрыш: секрет уходит из `environment:`, `docker
inspect` и окружения процесса, но `environment-to-ini` пишет его открытым
текстом в `app.ini` на диске — открытая проблема апстрима (#35316).
**tududi и wanderer не умеют ничего.** Для них остаётся либо принять env, либо
перенести переменные в файл на диске: у tududi `backend/app.js` зовёт `dotenv`
при рабочей директории `/app/backend`, поэтому смонтированный туда `.env` уберёт
секреты хотя бы из `docker inspect` — в отличие от `env_file:`, где они попадают
в `Config.Env`. У wanderer доступен только `env_file:`, то есть косметика.
Попутная находка в закомментированном OIDC-блоке wakapi (ловушка на будущее, не
живой баг): имена `WAKAPI_OIDC_PROVIDER_*` устарели, в 2.17.5 разбирается
`WAKAPI_OIDC_PROVIDERS_(\d+)_(...)`; кроме того `renameEnvVars()` режет строку по
первому `=`, так что секрет с base64-паддингом через env поедет битым. Оба
довода — за `config.yml`.
## Docker secrets — разобрано, покрытия не расширяет
`secrets:` в compose вне swarm — это bind-mount файла в `/run/secrets/<name>`,
то есть **тот же способ доставки**, что и `*_FILE`. Приложение, умеющее только
переменную окружения, из `/run/secrets` не прочитает — ограничение в приложении,
а не в доставке. На таблицу выше не влияет.
Что даёт: секрет объявляется на сервис, а не монтируется каталогом. У miniflux
сейчас `{{ secrets_dir }}:/secrets:ro` висит на обоих сервисах, и postgres видит
все шесть файлов, включая секреты OIDC, хотя нужен ему один пароль. Улучшение
поверх образца, а не замена ему. `uid`/`gid`/`mode` вне swarm игнорируются —
права остаются с хост-файла, их ставит роль `secrets`.
## Границы задачи
Она про то, где секрет **лежит в рантайме** (env против файла). Про то, чтобы он
не печатался в терминал при деплое, — [no-secrets-in-playbook-output](no-secrets-in-playbook-output.md),
и она идёт первой: `no_log` дёшев, закрывает все приложения разом и не зависит от
этой матрицы. Для tududi и wanderer он вообще единственный ответ.
Матрица показала, что задача переросла в эпик: каждая строка — отдельное
приложение со своим механизмом, и каждое мерджится независимо и само по себе
даёт пользу. Договорились разбить по приложениям отдельным заходом (2026-07-25);
до этого задачу в работу не брать целиком.
Из 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/authelia, files/miniflux (образец), roles/secrets.