Backlog: security-ревью (fable) — 13 находок по приоритетам
This commit is contained in:
+18
-5
@@ -7,29 +7,42 @@
|
||||
переезжает в ADR/спеку и пункт беклога удаляется.
|
||||
|
||||
Приоритет — грубая оценка «ценность / стоимость», не обязательство к порядку.
|
||||
Пункты с пометкой _(ансибл-ревью)_ пришли из ревью плейбуков
|
||||
[`docs/drafts/ansible-review.md`](../drafts/ansible-review.md) (2026-05-25);
|
||||
_(tududi)_ — перенесены из инбокса задач в tududi (проект Pet Project Server).
|
||||
Источники пунктов помечены:
|
||||
_(ансибл-ревью)_ — ревью плейбуков [`docs/drafts/ansible-review.md`](../drafts/ansible-review.md) (2026-05-25);
|
||||
_(tududi)_ — перенос из инбокса задач tududi (проект Pet Project Server);
|
||||
_(sec-ревью)_ — три независимых security-ревью на модели fable (2026-07-13).
|
||||
|
||||
Драфты `timeweb.md` и `timeweb-migration-log.md` — исторические логи уже
|
||||
завершённой миграции в Timeweb (cutover 2026-05-23), в беклог не входят.
|
||||
|
||||
## Высокий
|
||||
|
||||
- [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata на restart-loop/unhealthy
|
||||
- [Закрыть admin API Caddy](caddy-admin-api-localhost.md) — `admin :2019` слушает всю docker-сеть; любой контейнер может переписать маршрутизацию и снять forward_auth _(sec-ревью)_
|
||||
- [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_
|
||||
- [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_
|
||||
- [Запретить SSH-вход под root](ssh-disable-root-login.md) — `permit_root_login: yes` на открытом 22; root — цель брутфорса, компрометация ключа = uid 0 _(sec-ревью)_
|
||||
- [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata
|
||||
- [Рестарт контейнеров через handlers](ansible-handlers-restart.md) — `state: restarted` выполняется безусловно на каждом прогоне (лишний downtime), нет ни одного handler _(ансибл-ревью)_
|
||||
|
||||
## Средний
|
||||
|
||||
- [Секреты через `*_FILE`, а не inline `environment:`](secrets-env-to-file.md) — outline/wakapi/tududi/gitea/gramps/tuwunel держат секреты в env; видны в `docker inspect` и `/proc` _(sec-ревью)_
|
||||
- [Захардить мониторинг-стек](harden-monitoring-stack.md) — netdata/dozzle: лишние привилегии + docker.sock + доверие Remote-User в общей сети; socket-proxy + monitoring_network _(sec-ревью)_
|
||||
- [Ограничить `/metrics` Miniflux](miniflux-metrics-restrict.md) — `METRICS_ALLOWED_NETWORKS=0.0.0.0/0` + прокси без ограничения пути отдаёт метрики публично _(sec-ревью)_
|
||||
- [Закрыть calibre-web forward-auth](calibre-forward-auth.md) — домен без forward_auth, защита на встроенной авторизации с дефолтом `admin/admin123` _(sec-ревью)_
|
||||
- [Понизить log level Authelia до info](authelia-log-level.md) — `debug` в проде утекает детали аутентификации и OIDC через `docker logs`/dozzle _(sec-ревью)_
|
||||
- [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_
|
||||
- [Gitea runner on-demand в Yandex Cloud](gitea-runner-on-demand.md) — раннер активен только во время сборки; webhook→Cloud Function стартует ВМ, probe/decide гасят по idle; экономия ~95%
|
||||
- [Синхронизация общих Ansible-ролей](shared-roles-sync.md) — `owner`/`eget`/`secrets` дублируются между репозиториями и дрейфуют; канон в ansible-shared + rsync-таски invoke
|
||||
- [Уведомления о размере приложений и диске](disk-usage-alerts.md) — алерты на заполнение диска и аномальный рост data-томов; сервер ресурсно ограничен _(tududi)_
|
||||
- [Composable-роль `backup`](ansible-backup-role.md) — бэкап — самый чистый шов для extraction (одинаков у всех, различается только список targets) _(ансибл-ревью)_
|
||||
- [Точечные фиксы идемпотентности и баг имени play](ansible-quick-fixes.md) — быстрые правки: имя play в wanderer, `changed_when` в netdata/eget, backup-targets через template _(ансибл-ревью)_
|
||||
- [Аудит безопасности сервера](security-audit.md) — разовый проход по экспозиции, Authelia, правам пользователей и секретам; находки дробятся в беклог _(tududi)_
|
||||
|
||||
## Низкий
|
||||
|
||||
- [Мелкий хардненинг Caddy](caddy-hardening-nits.md) — HSTS-заголовок, убрать лишний `NET_ADMIN`, убрать leftover-листенер `:29999` _(sec-ревью)_
|
||||
- [Мелкий хардненинг контейнеров](container-hardening-nits.md) — `no-new-privileges`, non-root `user:`, homepage off `:latest`, соглашение о `127.0.0.1:` портах _(sec-ревью)_
|
||||
- [Унифицировать ACL техпанелей Authelia](authelia-acl-unify.md) — rssbridge (SSRF) и dashboard за `one_factor` без subject; свести к `group:admins` + `two_factor` _(sec-ревью)_
|
||||
- [`vars_files` → `group_vars/all/`](ansible-group-vars.md) — убрать повторяющийся boilerplate `vars_files` из всех плейбуков _(ансибл-ревью)_
|
||||
- [Причесать роль `owner` под конвенции](ansible-owner-role-cleanup.md) — `assert` вместо `fail`+`when`, `loop` вместо `with_*`, добавить `meta`/README _(ансибл-ревью)_
|
||||
- [Инвентарь: `host_vars`, группы, точечный `become`](ansible-inventory-hostvars.md) — хост-специфику в `host_vars/server.yml`, хост в именованную группу, глобальный root → точечный become _(ансибл-ревью)_
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Унифицировать ACL техпанелей Authelia
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
В `files/authelia/configuration.template.yml` `rssbridge` и `start` (dashboard)
|
||||
закрыты `one_factor` вообще без `subject`, тогда как
|
||||
netdata/dozzle/goaccess/bifrost/wanderbase/remembos — `two_factor` +
|
||||
`group:admins`. rssbridge умеет ходить по произвольным URL (SSRF-поверхность) и при
|
||||
этом живёт за одним фактором, доступен любому пользователю из `users.yml`. Для
|
||||
одного пользователя риск невелик, но логичнее свести техпанели к `group:admins` +
|
||||
`two_factor`, а `one_factor` без субъекта оставить только там, где это осознанно.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: files/authelia/configuration.template.yml.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Понизить log level Authelia с debug до info
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
`files/authelia/configuration.template.yml` — `log.level: "debug"`. Debug-логи
|
||||
Authelia содержат детали запросов аутентификации, имена пользователей, заголовки и
|
||||
внутренние подробности OIDC-потоков — они не нужны в обычной эксплуатации и
|
||||
расширяют поверхность утечки через `docker logs`/dozzle (который сам открыт
|
||||
админам). Поставить `info` (или `warn`), debug включать точечно при отладке.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: files/authelia/configuration.template.yml.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Изолировать/запаролить сессионный redis Authelia
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
`authelia_redis` — стор сессий SSO — живёт в общей `monitoring_network` без пароля
|
||||
(`files/authelia/docker-compose.template.yml`), а в той же сети сидят transcriber,
|
||||
gramps_redis, outline_redis/postgres, miniflux_postgres, netdata. Компрометация
|
||||
любого из них даёт запись в стор сессий Authelia → hijack SSO для всех сервисов.
|
||||
Вынести пару authelia_app+authelia_redis в собственную internal-сеть (как уже
|
||||
сделано у gramps/outline/miniflux с их `*_network`) и/или включить `requirepass`;
|
||||
из `monitoring_network` redis убрать, если netdata его реально не скрейпит.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: files/authelia/docker-compose.template.yml, playbook-docker.yml.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Закрыть admin API Caddy (localhost вместо всей сети)
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
`files/caddyproxy/Caddyfile.template:8` — `admin :2019`: admin API слушает на всех
|
||||
интерфейсах контейнера, а в `web_proxy_network` сидят все приложения. Любой
|
||||
скомпрометированный контейнер (rssbridge, tududi, calibre, ...) может POST-ом в
|
||||
`http://caddyproxy:2019/config/` переписать маршрутизацию, снять `forward_auth`,
|
||||
перехватить трафик или сертификаты. Порт реально нужен только netdata для метрик
|
||||
(`files/netdata/go.d/prometheus.conf`). Фикс: вернуть `admin localhost:2019`, а
|
||||
метрики отдать отдельным site-блоком `:2019 { metrics }` — netdata продолжит
|
||||
скрейпить, admin API станет недоступен из сети.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: files/caddyproxy/Caddyfile.template, files/netdata/go.d/prometheus.conf.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Мелкий хардненинг Caddy
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
- **HSTS**: заголовок `Strict-Transport-Security` не выставляется нигде в
|
||||
`files/caddyproxy/Caddyfile.template`. Одна строка `header
|
||||
Strict-Transport-Security "max-age=31536000"` в общем сниппете (рядом с
|
||||
`access_log`) закрывает downgrade/SSL-strip для всех поддоменов разом.
|
||||
- **Лишний `NET_ADMIN`** (`files/caddyproxy/docker-compose.template.yml`) —
|
||||
reverse-proxy эта capability не нужна (требуется лишь для тюнинга UDP-буферов
|
||||
HTTP/3, работает и без неё с warning). Убрать у edge-контейнера, торчащего в
|
||||
интернет.
|
||||
- **Leftover-листенер `:29999`** в `Caddyfile.template` (site `status.vakhrushev.me,
|
||||
:29999`) — наследие прямого доступа к netdata; убрать, оставив только доменный
|
||||
site-блок.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: files/caddyproxy/Caddyfile.template, files/caddyproxy/docker-compose.template.yml.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Закрыть calibre-web forward-auth и проверить дефолтные креды
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
`calibre.vakhrushev.me` проксируется на `calibre_web_app:8083` без блока
|
||||
`forward_auth` (`files/caddyproxy/Caddyfile.template`), правила на этот домен нет и
|
||||
в ACL Authelia — защита целиком на встроенной аутентификации calibre-web, которая
|
||||
из коробки идёт с `admin`/`admin123`. Если пароль не сменён вручную, панель
|
||||
управления библиотекой доступна в интернете с дефолтными кредами. Либо закрыть
|
||||
домен `forward_auth` + правилом `group:admins`, либо подтвердить смену пароля
|
||||
админа. Заодно перепроверить memos — не отключена ли публичная регистрация
|
||||
первым-админом (`files/memos/docker-compose.template.yml`).
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: files/caddyproxy/Caddyfile.template, files/authelia/configuration.template.yml,
|
||||
files/calibre, files/memos.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Мелкий хардненинг контейнеров
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
- **`no-new-privileges`**: ни один compose-шаблон не задаёт `security_opt:
|
||||
["no-new-privileges:true"]`. Добавить всем — одна строка, режет эскалацию через
|
||||
setuid-бинарники в образах.
|
||||
- **non-root `user:`**: без него работают caddyproxy, gramps (данные в
|
||||
`/root/.gramps`), rssbridge, dozzle, homepage и valkey/postgres-сайдкары gramps.
|
||||
Дотянуть там, где образ позволяет (rssbridge, valkey) — продолжение уже принятой
|
||||
в проекте практики.
|
||||
- **homepage на `:latest`** (`vars/homepage.yml`) — плавающий тег + ночной `docker
|
||||
image prune` может подтянуть другое содержимое без отката. Перевести на
|
||||
иммутабельный `<sha>-<timestamp>` по образцу transcriber
|
||||
(`vars/transcriber.images.yml`).
|
||||
- **Соглашение о портах**: зафиксировать в AGENTS.md, что новые `ports:` — только
|
||||
с `127.0.0.1:`. Docker публикует порты мимо UFW, поэтому дисциплина
|
||||
localhost-биндинга — единственный контроль (сейчас соблюдается: apprise, wanderer).
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: все files/*/docker-compose*.template.yml, vars/homepage.yml, AGENTS.md.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Захардить мониторинг-стек (netdata / dozzle / goaccess)
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Мониторинг-контейнеры — самые привилегированные на хосте и при этом торчат в общую
|
||||
`web_proxy_network` рядом с приложениями:
|
||||
|
||||
- **netdata** (`files/netdata/docker-compose.template.yml`) — `SYS_ADMIN` +
|
||||
`SYS_PTRACE` + `apparmor:unconfined`, плюс весь `/` и docker.sock: фактически
|
||||
root-контейнер. Для метрик обычно хватает `SYS_PTRACE`; `SYS_ADMIN` и
|
||||
`apparmor:unconfined` попробовать убрать (netdata деградирует мягко).
|
||||
- **dozzle** (`files/dozzle/docker-compose.template.yml`) — RW docker.sock +
|
||||
`DOZZLE_AUTH_PROVIDER: forward-proxy` (верит заголовку `Remote-User` от кого
|
||||
угодно). Любой контейнер в `web_proxy_network` может напрямую сходить на
|
||||
`dozzle_app:8080` с поддельным `Remote-User` и читать логи всех контейнеров (в
|
||||
логах бывают токены/ссылки).
|
||||
- docker.sock обоим пустить через `tecnativa/docker-socket-proxy` (только нужные
|
||||
GET), напрямую не монтировать.
|
||||
- перенести netdata/dozzle/goaccess в существующую `monitoring_network`
|
||||
(`playbook-docker.yml`), а Caddy подключить к обеим сетям — уводит их из общей
|
||||
сети с приложениями и устраняет lateral movement.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: files/netdata, files/dozzle, files/goaccess, files/caddyproxy, playbook-docker.yml.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Ограничить эндпоинт `/metrics` Miniflux
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
В `files/miniflux/docker-compose.template.yml` заданы `METRICS_COLLECTOR=1` и
|
||||
`METRICS_ALLOWED_NETWORKS=0.0.0.0/0`, а Caddy проксирует весь
|
||||
`miniflux.vakhrushev.me` без ограничения пути. За прокси Miniflux видит приватный
|
||||
IP контейнера Caddy (попадает в `0.0.0.0/0`), поэтому
|
||||
`https://miniflux.vakhrushev.me/metrics` отдаётся публично без аутентификации —
|
||||
утечка эксплуатационной статистики. Сузить `METRICS_ALLOWED_NETWORKS` до подсети
|
||||
`monitoring_network` (netdata скрейпит по внутреннему имени) или заблокировать
|
||||
путь `/metrics` в Caddy.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: files/miniflux/docker-compose.template.yml, files/caddyproxy/Caddyfile.template.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Секреты приложений через `*_FILE`, а не inline `environment:`
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
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`, любой процесс с доступом к
|
||||
`/proc/<pid>/environ`, и они попадают в `docker inspect`. Где приложение умеет
|
||||
`*_FILE`/конфиг-файл — постепенно перевести по образцу miniflux.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13). Связка с [service-users-docker-group](service-users-docker-group.md).
|
||||
|
||||
Связано: files/outline, files/wakapi, files/tududi, files/gitea, files/gramps,
|
||||
files/tuwunel, files/miniflux (образец).
|
||||
@@ -1,14 +0,0 @@
|
||||
# Аудит безопасности сервера
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Провести аудит безопасности сервера и конфигураций (Fable / `/security-review`):
|
||||
поверхность экспозиции наружу (Caddy, открытые порты, UFW), аутентификация и
|
||||
границы Authelia, права системных пользователей приложений, обращение с
|
||||
секретами (vault, env-файлы, docker socket), настройки docker-compose. Разовый
|
||||
проход; найденное дробится на отдельные пункты беклога.
|
||||
|
||||
Из tududi (проект Pet Project Server).
|
||||
|
||||
Связано: playbook-ufw.yml, playbook-caddyproxy.yml, playbook-authelia.yml,
|
||||
roles/owner, roles/secrets.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Включить автообновления безопасности
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
`playbook-system.yml:35` — `security_autoupdate_enabled: "no"`: хост с открытыми в
|
||||
интернет 22/80/443/2222 получает патчи только при ручном запуске
|
||||
`playbook-upgrade.yml`, т.е. окна в недели между публикацией CVE и патчем
|
||||
openssh/libssl. Соразмерный фикс — включить unattended-upgrades только для
|
||||
security-репозитория (одна переменная роли), без автоматических перезагрузок.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: playbook-system.yml, galaxy.roles/geerlingguy.security, playbook-upgrade.yml.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Убрать группу `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-env-to-file](secrets-env-to-file.md).
|
||||
|
||||
Связано: все playbook-*.yml (owner_extra_groups), roles/owner, playbook-docker.yml.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Запретить SSH-вход под root
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
`playbook-system.yml:34` переопределяет дефолт роли на
|
||||
`security_ssh_permit_root_login: "yes"`. Парольная аутентификация выключена (вход
|
||||
только по ключу), но root — единственный аккаунт, который брутфорсят все боты на
|
||||
открытом 22/tcp, и компрометация ключа сразу даёт uid 0 без следа sudo. Деплой
|
||||
идёт под отдельным непривилегированным пользователем с become (`production.yml`),
|
||||
root-логин для Ansible не нужен. Поставить `"no"`, либо `prohibit-password`, если
|
||||
root-ключ нужен для аварийного доступа (то же поведение по ключу, но явный запрет
|
||||
пароля на будущее).
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13).
|
||||
|
||||
Связано: playbook-system.yml, production.yml, galaxy.roles/geerlingguy.security.
|
||||
Reference in New Issue
Block a user