diff --git a/docs/backlog/README.md b/docs/backlog/README.md index 7b56d47..f2cae6b 100644 --- a/docs/backlog/README.md +++ b/docs/backlog/README.md @@ -36,6 +36,8 @@ _(sec-ревью)_ — три независимых security-ревью на м - [Уведомления о размере приложений и заполнении диска](disk-usage-alerts.md) — алерты на заполнение диска и аномальный рост data-томов; сервер ресурсно ограничен _(tududi)_ - [Handlers рестарта для оставшихся приложений](handlers-remaining-apps.md) — apprise/dashboard/transcriber/miniflux монтируют конфиг в контейнер, но рестарта нет вовсе; правка конфига не подхватывается - [Composable-роль `backup`](ansible-backup-role.md) — бэкап — самый чистый шов для extraction (одинаков у всех, различается только список targets) _(ансибл-ревью)_ +- [Локальная сборка образов вместо Yandex Container Registry](local-image-build.md) — homepage/transcriber/remembos тянут образы из cr.yandex, ради `docker login` в vault лежит OAuth-токен всего аккаунта; роль `app_image` уже обкатана в umbar +- [Вывести из оборота секреты с широкими правами](narrow-secret-scopes.md) — один `yc_oauth_token` открывает весь аккаунт Yandex Cloud; после инцидента 24 июля цена утечки одной переменной должна быть ограничена одним сервисом ## Низкий diff --git a/docs/backlog/local-image-build.md b/docs/backlog/local-image-build.md new file mode 100644 index 0000000..10936d0 --- /dev/null +++ b/docs/backlog/local-image-build.md @@ -0,0 +1,54 @@ +# Локальная сборка образов вместо Yandex Container Registry + +**Приоритет:** Средний + +Три приложения деплоятся через Yandex Container Registry: homepage, transcriber, +remembos. Схема одинаковая — образ собирается локально и пушится в +`cr.yandex/...` (`playbook-homepage-registry.yml`, +`playbook-transcriber-registry.yml`), плейбук деплоя делает `docker_login` с +`yc_oauth_token`, compose тянет образ по registry-пути с зашитым в git тегом +(`transcriber:2fc5a56-1761210134`, `remembos:v0.2.0`, `homepage-nginx:latest`). +У remembos registry-плейбука нет вовсе — образ пушится руками. + +Роль `app_image` (канон в `~/projects/private/ansible-roles`) делает то же самое +без registry: собирает образ на control-хосте командой самого приложения +(контракт — `task image`, получает `BUILD_ID` через окружение), везёт его на +сервер через `docker save`/`load` и отдаёт факт `app_image_tag` для compose. +Обкатана в umbar на jellybit и trackers. + +Что станет иначе: из деплоя уходят внешний реестр и OAuth-токен, тег образа +перестаёт быть константой в git, а `inv pl -- homepage` становится полным +деплоем — сейчас сборка живёт в `task deploy` репозитория homepage, который сам +зовёт `ansible-playbook`. + +Шаги: + +- завендорить роль `app_image` — в `roles/` сейчас только `owner`, `eget`, + `secrets`; механика переноса — [shared-roles-sync](shared-roles-sync.md) +- homepage: завести в Taskfile таск `image` с тегом из `$BUILD_ID`, перевести + плейбук на роль, удалить `playbook-homepage-registry.yml` и registry-переменные + из `vars/homepage.yml` +- remembos: контракта сборки нет (есть Dockerfile, нет Taskfile) — завести таск + `image`, убрать зашитый тег из compose +- transcriber: исходников на этой машине нет — до начала решить, живой ли сервис + и откуда его собирать; иначе он останется единственным держателем registry и + токен из оборота не выйдет +- убрать `docker_login`-таски и registry-переменные из vault — дальше по + [narrow-secret-scopes](narrow-secret-scopes.md) + +Готово, когда ни один плейбук не делает `docker_login`, `cr.yandex` не +встречается в репозитории, а все три приложения деплоятся одним +`inv pl -- `. + +Цена размена: дедупликации по слоям больше нет — каждый деплой везёт полный +образ, и для сборки нужен локальный docker с тулчейном. Для ручного нечастого +деплоя это осознанный выбор (так решено в самой роли), но у transcriber образ +может оказаться крупным — это стоит проверить до перевода. + +Пересекается с [gitea-runner-on-demand](gitea-runner-on-demand.md): если сборки +уедут в CI, доставка образа на сервер снова становится отдельным вопросом. + +Связано: playbook-homepage.yml, playbook-transcriber.yml, playbook-remembos.yml, +playbook-homepage-registry.yml, playbook-transcriber-registry.yml, +vars/homepage.yml, vars/transcriber.yml, vars/transcriber.images.yml, +files/{homepage,transcriber,remembos}/docker-compose.template.yml. diff --git a/docs/backlog/narrow-secret-scopes.md b/docs/backlog/narrow-secret-scopes.md new file mode 100644 index 0000000..ea21d5e --- /dev/null +++ b/docs/backlog/narrow-secret-scopes.md @@ -0,0 +1,42 @@ +# Вывести из оборота секреты с широкими правами + +**Приоритет:** Средний + +`yc_oauth_token` в vault — OAuth-токен аккаунта Yandex Cloud: реестр, виртуалки, +биллинг. Нужен он ради одной операции — `docker login` в плейбуках homepage, +transcriber и remembos. Радиус прав несопоставим с задачей, и 24 июля он вместе +с ещё 52 переменными ушёл открытым текстом в транскрипт: цена одной такой утечки +— весь аккаунт, а не один сервис. + +Принцип, к которому идём: секрет даёт доступ ровно к тому, ради чего заведён. +Всё, что шире, — либо заменяется на скоупированный доступ (сервисный аккаунт с +минимальными ролями вместо токена аккаунта, отдельный ключ на бакет вместо +общего), либо выводится из оборота совсем, либо риск принимается явно и +записывается. + +Ядро задачи закрывается сборкой без реестра: после +[local-image-build](local-image-build.md) `yc_oauth_token` не нужен ни в одном +плейбуке и удаляется из vault. До неё эта задача не начинается — иначе деплой +трёх приложений просто ляжет. + +Дальше — ревизия остального: пройти по `group_vars/all/secrets.yml` и проставить +каждой переменной радиус («один сервис» / «весь аккаунт у провайдера» / «все +бэкапы»). Кандидаты в широкие видны уже по назначению — ключи S3 для restic, +доступ к Postbox, — но разбирать их надо в этой задаче, а не заранее. + +Готово, когда у каждой переменной vault записан радиус, а каждая широкая либо +заменена на скоупированную, либо помечена принятым риском с причиной. + +Держать в уме: [gitea-runner-on-demand](gitea-runner-on-demand.md) заводит новый +доступ к API Yandex Cloud (старт и остановка ВМ). Если она поедет раньше — сразу +сервисный аккаунт с минимальными ролями, а не переиспользование OAuth-токена, +иначе задача вернётся тем же текстом. + +Ротация уже утёкших значений — отдельный сюжет, он ведётся в +`SECRETS-ROTATION.md` (файл вне git). Здесь речь не о смене значения, а о +сокращении того, что это значение открывает. + +Связано: group_vars/all/secrets.yml, playbook-homepage.yml, +playbook-transcriber.yml, playbook-remembos.yml, +[vault-password-storage](vault-password-storage.md), +[secrets-env-to-file](secrets-env-to-file.md).