diff --git a/docs/backlog/README.md b/docs/backlog/README.md index af67b75..c972f38 100644 --- a/docs/backlog/README.md +++ b/docs/backlog/README.md @@ -17,13 +17,13 @@ _(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` стоит ровно в одном месте из четырнадцати ## Средний +- [Дампы бэкапов без группы `docker`](backup-dump-without-docker-group.md) — у miniflux/gitea/outline группа осталась: их backup.sh дампит через `docker compose exec` из-под самого приложения - [Захардить мониторинг-стек (netdata / dozzle / goaccess)](harden-monitoring-stack.md) — netdata/dozzle: лишние привилегии + docker.sock + доверие Remote-User в общей сети; socket-proxy + monitoring_network _(sec-ревью)_ - [Закрыть calibre-web forward-auth и проверить дефолтные креды](calibre-forward-auth.md) — домен без forward_auth, защита на встроенной авторизации с дефолтом `admin/admin123` _(sec-ревью)_ - [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_ diff --git a/docs/backlog/backup-dump-without-docker-group.md b/docs/backlog/backup-dump-without-docker-group.md new file mode 100644 index 0000000..7a380a0 --- /dev/null +++ b/docs/backlog/backup-dump-without-docker-group.md @@ -0,0 +1,33 @@ +# Дампы бэкапов без группы `docker` у сервисных пользователей + +**Приоритет:** средний + +Группу `docker` (= root на хосте) убрали у всех сервисных пользователей, кроме +трёх: `miniflux`, `gitea`, `outline`. Их `backup.sh` делает `docker compose exec` +(pg_dump, `gitea dump`), а `files/backups/backup-all.py` запускает такие скрипты +через `su --login `, то есть от имени самого приложения — без членства +в группе дамп упадёт. + +Дампить снаружи докера нельзя: postgres порт не публикует, `gitea dump` живёт +только внутри контейнера. + +Варианты, как развязать: + +- **A. sudo-обёртка на дамп.** Плейбук кладёт `/usr/local/sbin/backup--dump.sh` + (root:root, 0755 — сам app_user его не перепишет) с единственным + `docker compose exec …`, в sudoers — ` ALL=(root) NOPASSWD:` ровно на + этот файл, а `backup.sh` дёргает его через `sudo`. Пользователь получает право + на одну фиксированную команду вместо права на весь докер. Механизм в проекте + уже обкатан: так `playbook-backups.yml` разрешает `primary_user` запускать + `backup-all.py`. +- **B. root-скрипты в оркестраторе.** Новый контракт в `backup-all.py`: если у + приложения есть `backup-root.sh` с владельцем root — запускать его без `su`. + Механизм один на всех, но от root пойдёт скрипт целиком, включая + `keep-files.py`, и дампы станут root-овыми файлами в директориях приложения. +- **C. оставить как есть** — три аккаунта как осознанное исключение, + задокументированное в плейбуках. + +Предпочтение на момент записи — A. + +Связано: playbook-miniflux.yml, playbook-gitea.yml, playbook-outline.yml, +files/*/backup.template.sh, files/backups/backup-all.py. diff --git a/docs/backlog/service-users-docker-group.md b/docs/backlog/service-users-docker-group.md deleted file mode 100644 index a2ce6ab..0000000 --- a/docs/backlog/service-users-docker-group.md +++ /dev/null @@ -1,20 +0,0 @@ -# Убрать группу `docker` у сервисных пользователей - -**Приоритет:** высокий - -Все приложения заводят системного пользователя с `owner_extra_groups: ["docker"]` -(во всех `playbook-*.yml`), но docker.sock реально монтируют только netdata и -dozzle. Членство в группе `docker` эквивалентно root на хосте (`docker run -v -/:/host`), поэтому выделенные «непривилегированные» аккаунты (authelia, outline, -gitea и т.д.) фактически все имеют root — изоляция «пользователь на сервис», ради -которой их и заводили, обнуляется. Контейнерами рулит Ansible от root (become), -сервисам группа не нужна. Убрать `docker` из `owner_extra_groups` у всех, кроме -тех, кому он действительно нужен для самостоятельного управления своим compose (и -тем — лучше через socket-proxy). За `primary_user` группу оставить для ручных -операций (`inv ssh`). - -Из sec-ревью (fable, 2026-07-13). Усиливает риск из серии `secrets-file-*` -(секреты в `environment:` контейнеров): членство в группе `docker` — как раз тот -доступ, которым такие переменные и вычитываются. - -Связано: все playbook-*.yml (owner_extra_groups), roles/owner, playbook-docker.yml. diff --git a/playbook-apprise.yml b/playbook-apprise.yml index ad4ab48..97a294e 100644 --- a/playbook-apprise.yml +++ b/playbook-apprise.yml @@ -27,7 +27,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-authelia.yml b/playbook-authelia.yml index 987c450..d474c3f 100644 --- a/playbook-authelia.yml +++ b/playbook-authelia.yml @@ -24,7 +24,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create internal application directories" ansible.builtin.file: diff --git a/playbook-bifrost.yml b/playbook-bifrost.yml index d8ae041..eb2209c 100644 --- a/playbook-bifrost.yml +++ b/playbook-bifrost.yml @@ -21,7 +21,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-caddyproxy.yml b/playbook-caddyproxy.yml index cb5eb07..f620c32 100644 --- a/playbook-caddyproxy.yml +++ b/playbook-caddyproxy.yml @@ -23,7 +23,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create internal application directories" ansible.builtin.file: diff --git a/playbook-calibre.yml b/playbook-calibre.yml index 55ffdc8..254b632 100644 --- a/playbook-calibre.yml +++ b/playbook-calibre.yml @@ -19,7 +19,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-dashboard.yml b/playbook-dashboard.yml index 896917a..c7b1781 100644 --- a/playbook-dashboard.yml +++ b/playbook-dashboard.yml @@ -30,7 +30,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-dozzle.yml b/playbook-dozzle.yml index 51962d8..8e0be0e 100644 --- a/playbook-dozzle.yml +++ b/playbook-dozzle.yml @@ -17,7 +17,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create internal application directories" ansible.builtin.file: diff --git a/playbook-gitea.yml b/playbook-gitea.yml index 65657e0..e98524c 100644 --- a/playbook-gitea.yml +++ b/playbook-gitea.yml @@ -19,6 +19,10 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" + # Группа docker = root на хосте, поэтому её не выдаём никому, кроме + # приложений, чей backup.sh делает docker compose exec: оркестратор + # запускает такие скрипты через su от имени этого пользователя. + # См. docs/backlog/backup-dump-without-docker-group.md. owner_extra_groups: ["docker"] - name: "Create internal application directories" diff --git a/playbook-goaccess.yml b/playbook-goaccess.yml index 28a1cd8..5c4ae5f 100644 --- a/playbook-goaccess.yml +++ b/playbook-goaccess.yml @@ -20,7 +20,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create internal application directories" ansible.builtin.file: diff --git a/playbook-goatcounter.yml b/playbook-goatcounter.yml index e6b57a1..920f4f2 100644 --- a/playbook-goatcounter.yml +++ b/playbook-goatcounter.yml @@ -29,7 +29,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-gramps.yml b/playbook-gramps.yml index 19db788..3754c20 100644 --- a/playbook-gramps.yml +++ b/playbook-gramps.yml @@ -22,7 +22,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-homepage.yml b/playbook-homepage.yml index 973d80a..2b23435 100644 --- a/playbook-homepage.yml +++ b/playbook-homepage.yml @@ -23,7 +23,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-memos.yml b/playbook-memos.yml index abc94a7..bff837e 100644 --- a/playbook-memos.yml +++ b/playbook-memos.yml @@ -20,7 +20,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-miniflux.yml b/playbook-miniflux.yml index 2997b2e..57dbb60 100644 --- a/playbook-miniflux.yml +++ b/playbook-miniflux.yml @@ -21,6 +21,10 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" + # Группа docker = root на хосте, поэтому её не выдаём никому, кроме + # приложений, чей backup.sh делает docker compose exec: оркестратор + # запускает такие скрипты через su от имени этого пользователя. + # См. docs/backlog/backup-dump-without-docker-group.md. owner_extra_groups: ["docker"] - name: "Create internal directories" diff --git a/playbook-netdata.yml b/playbook-netdata.yml index a9d7f9d..77f8d2a 100644 --- a/playbook-netdata.yml +++ b/playbook-netdata.yml @@ -21,7 +21,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create internal application directories" ansible.builtin.file: diff --git a/playbook-outline.yml b/playbook-outline.yml index 99ab24e..1d7af80 100644 --- a/playbook-outline.yml +++ b/playbook-outline.yml @@ -25,6 +25,10 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" + # Группа docker = root на хосте, поэтому её не выдаём никому, кроме + # приложений, чей backup.sh делает docker compose exec: оркестратор + # запускает такие скрипты через su от имени этого пользователя. + # См. docs/backlog/backup-dump-without-docker-group.md. owner_extra_groups: ["docker"] - name: "Create internal directories" diff --git a/playbook-remembos.yml b/playbook-remembos.yml index b13d110..b955868 100644 --- a/playbook-remembos.yml +++ b/playbook-remembos.yml @@ -30,7 +30,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-rssbridge.yml b/playbook-rssbridge.yml index 0497098..1ff86b8 100644 --- a/playbook-rssbridge.yml +++ b/playbook-rssbridge.yml @@ -17,7 +17,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create internal application directories" ansible.builtin.file: diff --git a/playbook-transcriber.yml b/playbook-transcriber.yml index 3b45376..8a5c856 100644 --- a/playbook-transcriber.yml +++ b/playbook-transcriber.yml @@ -23,7 +23,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-tududi.yml b/playbook-tududi.yml index 35b294f..c97a80b 100644 --- a/playbook-tududi.yml +++ b/playbook-tududi.yml @@ -20,7 +20,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-tuwunel.yml b/playbook-tuwunel.yml index 1f0396d..b94e63b 100644 --- a/playbook-tuwunel.yml +++ b/playbook-tuwunel.yml @@ -23,7 +23,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-wakapi.yml b/playbook-wakapi.yml index 67e2e29..549893c 100644 --- a/playbook-wakapi.yml +++ b/playbook-wakapi.yml @@ -20,7 +20,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/playbook-wanderer.yml b/playbook-wanderer.yml index 10797f7..a45fb01 100644 --- a/playbook-wanderer.yml +++ b/playbook-wanderer.yml @@ -22,7 +22,6 @@ owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] - name: "Create application internal directories" ansible.builtin.file: diff --git a/roles/owner/README.md b/roles/owner/README.md index 288dbc9..768e69c 100644 --- a/roles/owner/README.md +++ b/roles/owner/README.md @@ -34,10 +34,18 @@ compose-шаблоны берут оттуда `owner_create_result.uid` и | `owner_uid` | да | Uid пользователя | | `owner_group` | нет | Имя группы, по умолчанию `owner_name` | | `owner_gid` | нет | Gid группы, по умолчанию `owner_uid` | -| `owner_extra_groups` | нет | Дополнительные группы (обычно `["docker"]`) | +| `owner_extra_groups` | нет | Дополнительные группы; по умолчанию пусто | | `owner_ssh_keys` | нет | Список публичных ключей для `authorized_keys` | | `owner_env` | нет | Словарь переменных окружения, попадающих в `~/.env` | +## Про группу `docker` + +Членство в группе `docker` равносильно root на хосте (`docker run -v /:/host`), +поэтому сервисным пользователям её не выдаём: контейнерами рулит Ansible от +root. Исключение — приложения, чей `backup.sh` дампит базу через +`docker compose exec`: оркестратор бэкапов запускает такие скрипты от имени +самого приложения. См. `docs/backlog/backup-dump-without-docker-group.md`. + ## Пример использования ```yaml @@ -48,5 +56,4 @@ compose-шаблоны берут оттуда `owner_create_result.uid` и owner_name: "{{ app_user }}" owner_uid: "{{ app_owner_uid }}" owner_gid: "{{ app_owner_gid }}" - owner_extra_groups: ["docker"] ```