owner: группа docker убрана у сервисных пользователей
Linting / YAML Lint (push) Canceled after 0s
Linting / Ansible Lint (push) Canceled after 0s

- 20 приложений её не получают: контейнерами рулит ansible от root, а
  членство в группе docker равносильно root на хосте
- у gitea, miniflux и outline группа осталась — их backup.sh дампит базу
  через docker compose exec из-под самого приложения
- задача service-users-docker-group закрыта, остаток вынесен в новую
  backup-dump-without-docker-group
This commit is contained in:
av
2026-08-22 19:40:57 +03:00
parent 856bf72560
commit c5217607b1
27 changed files with 55 additions and 43 deletions
+1 -1
View File
@@ -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-ревью)_ - [Изолировать/запаролить сессионный 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` стоит ровно в одном месте из четырнадцати - [Никаких секретов в выводе плейбуков](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-ревью)_ - [Захардить мониторинг-стек (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-ревью)_ - [Закрыть calibre-web forward-auth и проверить дефолтные креды](calibre-forward-auth.md) — домен без forward_auth, защита на встроенной авторизации с дефолтом `admin/admin123` _(sec-ревью)_
- [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_ - [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_
@@ -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 <app_user>`, то есть от имени самого приложения — без членства
в группе дамп упадёт.
Дампить снаружи докера нельзя: postgres порт не публикует, `gitea dump` живёт
только внутри контейнера.
Варианты, как развязать:
- **A. sudo-обёртка на дамп.** Плейбук кладёт `/usr/local/sbin/backup-<app>-dump.sh`
(root:root, 0755 — сам app_user его не перепишет) с единственным
`docker compose exec …`, в sudoers — `<app_user> 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.
@@ -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.
-1
View File
@@ -27,7 +27,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -24,7 +24,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -21,7 +21,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -23,7 +23,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -19,7 +19,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -30,7 +30,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -17,7 +17,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
ansible.builtin.file: ansible.builtin.file:
+4
View File
@@ -19,6 +19,10 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" 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"] owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
-1
View File
@@ -20,7 +20,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -29,7 +29,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -22,7 +22,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -23,7 +23,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -20,7 +20,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
+4
View File
@@ -21,6 +21,10 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" 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"] owner_extra_groups: ["docker"]
- name: "Create internal directories" - name: "Create internal directories"
-1
View File
@@ -21,7 +21,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
ansible.builtin.file: ansible.builtin.file:
+4
View File
@@ -25,6 +25,10 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" 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"] owner_extra_groups: ["docker"]
- name: "Create internal directories" - name: "Create internal directories"
-1
View File
@@ -30,7 +30,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -17,7 +17,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create internal application directories" - name: "Create internal application directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -23,7 +23,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -20,7 +20,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -23,7 +23,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -20,7 +20,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
-1
View File
@@ -22,7 +22,6 @@
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories" - name: "Create application internal directories"
ansible.builtin.file: ansible.builtin.file:
+9 -2
View File
@@ -34,10 +34,18 @@ compose-шаблоны берут оттуда `owner_create_result.uid` и
| `owner_uid` | да | Uid пользователя | | `owner_uid` | да | Uid пользователя |
| `owner_group` | нет | Имя группы, по умолчанию `owner_name` | | `owner_group` | нет | Имя группы, по умолчанию `owner_name` |
| `owner_gid` | нет | Gid группы, по умолчанию `owner_uid` | | `owner_gid` | нет | Gid группы, по умолчанию `owner_uid` |
| `owner_extra_groups` | нет | Дополнительные группы (обычно `["docker"]`) | | `owner_extra_groups` | нет | Дополнительные группы; по умолчанию пусто |
| `owner_ssh_keys` | нет | Список публичных ключей для `authorized_keys` | | `owner_ssh_keys` | нет | Список публичных ключей для `authorized_keys` |
| `owner_env` | нет | Словарь переменных окружения, попадающих в `~/.env` | | `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 ```yaml
@@ -48,5 +56,4 @@ compose-шаблоны берут оттуда `owner_create_result.uid` и
owner_name: "{{ app_user }}" owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}" owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}" owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
``` ```