Compare commits

..
7 Commits
Author SHA1 Message Date
av 7e64d76d02 ufw: правило для zabbix-агента и включение в all-setup
Linting / Ansible Lint (push) Canceled after 0s
Linting / YAML Lint (push) Canceled after 0s
- Порт 10050 (агент Timeweb) открыт только опросчикам хостера из Server= его
  конфига, а не всему интернету.
- playbook-ufw.yml добавлен в playbook-all-setup.yml перед docker: раньше он
  запускался только руками и после переезда на Timeweb не был прогнан вовсе —
  на сервере ufw до сих пор с ENABLED=no и пустым набором правил.
- В шапке плейбука зафиксировано, что ufw не закрывает порты, опубликованные
  докером: они идут мимо цепочки INPUT.
2026-08-22 18:00:24 +03:00
av 6bb0d61183 caddyproxy: admin API закрыт на loopback, метрики вынесены на :2020
- `admin :2019` слушал всю web_proxy_network: любой контейнер мог POST-ом в
  /config/ переписать маршрутизацию и снять forward_auth. Теперь
  `admin localhost:2019`.
- Метрики для netdata отдаёт отдельный site-блок `http://:2020 { metrics
  /metrics }`, netdata скрейпит новый порт.
- Порт для метрик именно 2020, а не 2019, как предлагала задача: сайт на :2019
  перехватывает IPv6-петлю (admin висит только на IPv4), из-за чего
  `caddy reload` внутри контейнера возвращает 0 и молча ничего не применяет —
  проверено на caddy:2.11.4.
2026-08-22 17:44:08 +03:00
av 304263e7b1 authelia: уровень логов понижен с debug до info
- debug-логи содержат детали аутентификации, имена пользователей и внутренности
  OIDC-потоков — в обычной эксплуатации не нужны, а через docker logs/dozzle это
  лишняя поверхность утечки.
- Задача убрана из беклога.
2026-08-22 17:38:56 +03:00
av 25886d1be0 miniflux: /metrics закрыт снаружи в Caddy
- METRICS_ALLOWED_NETWORKS не помогает: за прокси Miniflux видит IP Caddy,
  а netdata скрейпит по той же web_proxy_network — по IP их не различить
- задача убрана из беклога
2026-08-22 17:35:00 +03:00
av b9be642c5c gitea: ветка по умолчанию в новых репозиториях — master 2026-08-22 17:29:22 +03:00
av 9c75a52529 Dashboard: update to 2.1.2
Мажорное обновление homepage. Единственное ломающее изменение 2.0.0 —
встроенная аутентификация, она включается только через HOMEPAGE_AUTH_ENABLED,
так что дашборд остаётся за Authelia как прежде.
2026-08-22 13:39:01 +03:00
av a909442d8f образы приложений прописаны в docker-compose явно
- dashboard и wanderer брали имя образа и версию из переменных плейбука;
  теперь, как у остальных сервисов, они видны прямо в compose
- переменные homepage_image и wanderer_version удалены за ненадобностью
2026-08-22 13:38:36 +03:00
15 changed files with 67 additions and 61 deletions
-3
View File
@@ -17,7 +17,6 @@ _(sec-ревью)_ — три независимых security-ревью на м
## Высокий ## Высокий
- [Закрыть admin API Caddy (localhost вместо всей сети)](caddy-admin-api-localhost.md) — `admin :2019` слушает всю docker-сеть; любой контейнер может переписать маршрутизацию и снять forward_auth _(sec-ревью)_
- [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_ - [Убрать группу `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
@@ -26,9 +25,7 @@ _(sec-ревью)_ — три независимых security-ревью на м
## Средний ## Средний
- [Захардить мониторинг-стек (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-ревью)_
- [Ограничить эндпоинт `/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-ревью)_ - [Закрыть calibre-web forward-auth и проверить дефолтные креды](calibre-forward-auth.md) — домен без forward_auth, защита на встроенной авторизации с дефолтом `admin/admin123` _(sec-ревью)_
- [Понизить log level Authelia с debug до info](authelia-log-level.md) — `debug` в проде утекает детали аутентификации и OIDC через `docker logs`/dozzle _(sec-ревью)_
- [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_ - [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_
- [Запретить SSH-вход под root](ssh-disable-root-login.md) — `PermitRootLogin yes` пускает root по ключу; наименьшие привилегии + defense-in-depth (пароль уже off) _(sec-ревью)_ - [Запретить SSH-вход под root](ssh-disable-root-login.md) — `PermitRootLogin yes` пускает root по ключу; наименьшие привилегии + defense-in-depth (пароль уже off) _(sec-ревью)_
- [Gitea runner on-demand в Yandex Cloud](gitea-runner-on-demand.md) — раннер активен только во время сборки; webhook→Cloud Function стартует ВМ, probe/decide гасят по idle; экономия ~95% - [Gitea runner on-demand в Yandex Cloud](gitea-runner-on-demand.md) — раннер активен только во время сборки; webhook→Cloud Function стартует ВМ, probe/decide гасят по idle; экономия ~95%
-13
View File
@@ -1,13 +0,0 @@
# Понизить 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.
-16
View File
@@ -1,16 +0,0 @@
# Закрыть 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.
-16
View File
@@ -1,16 +0,0 @@
# Ограничить эндпоинт `/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.
+1 -1
View File
@@ -121,7 +121,7 @@ server:
## ##
log: log:
## Level of verbosity for logs: info, debug, trace. ## Level of verbosity for logs: info, debug, trace.
level: "debug" level: "info"
## Format the logs are written as: json, text. ## Format the logs are written as: json, text.
format: "json" format: "json"
+25 -1
View File
@@ -5,13 +5,28 @@
grace_period 15s grace_period 15s
# Admin API слушает только loopback контейнера: на :2019 его видела бы вся # Admin API слушает только loopback контейнера: на :2019 его видела бы вся
# web_proxy_network, и любое приложение могло бы переписать маршрутизацию
# или снять forward_auth. Метрики вынесены в отдельный site-блок ниже, и
# порт у него другой: сайт на :2019 перехватывал бы IPv6-петлю (admin висит
# только на IPv4), и `caddy reload` молча ничего не применял бы.
admin localhost:2019
# Enable metrics in Prometheus format # Enable metrics in Prometheus format
# https://caddyserver.com/docs/metrics # https://caddyserver.com/docs/metrics
metrics metrics
} }
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Metrics
# -------------------------------------------------------------------
# Prometheus-метрики для netdata (files/netdata/go.d/prometheus.conf).
# Наружу порт не публикуется — доступен только внутри web_proxy_network.
http://:2020 {
metrics /metrics
}
# -------------------------------------------------------------------
# Snippets # Snippets
# ------------------------------------------------------------------- # -------------------------------------------------------------------
@@ -136,9 +151,18 @@ miniflux.vakhrushev.me {
import access_log import access_log
# /metrics наружу не отдаём. METRICS_ALLOWED_NETWORKS тут не помогает: за # /metrics наружу не отдаём. METRICS_ALLOWED_NETWORKS тут не помогает: за
# прокси Miniflux видит приватный IP контейнера Caddy, а не адрес клиента.
# Netdata скрейпит miniflux_app:8080/metrics напрямую по докер-сети, мимо Caddy.
handle /metrics {
respond 404
}
handle {
reverse_proxy {
to miniflux_app:8080 to miniflux_app:8080
} }
} }
}
wakapi.vakhrushev.me { wakapi.vakhrushev.me {
tls anwinged@ya.ru tls anwinged@ya.ru
+3 -2
View File
@@ -7,7 +7,8 @@
services: services:
dashboard_app: dashboard_app:
image: "{{ homepage_image }}" # See versions: https://github.com/gethomepage/homepage/releases
image: ghcr.io/gethomepage/homepage:v2.1.2
container_name: dashboard_app container_name: dashboard_app
restart: unless-stopped restart: unless-stopped
user: "{{ app_owner_uid }}:{{ app_owner_gid }}" user: "{{ app_owner_uid }}:{{ app_owner_gid }}"
@@ -18,7 +19,7 @@ services:
- "/etc/localtime:/etc/localtime:ro" - "/etc/localtime:/etc/localtime:ro"
- "/etc/timezone:/etc/timezone:ro" - "/etc/timezone:/etc/timezone:ro"
environment: environment:
# homepage 1.x требует явный whitelist Host за реверс-прокси, иначе отдаёт # homepage требует явный whitelist Host за реверс-прокси, иначе отдаёт
# «Host validation failed». Пускаем только адрес из Caddy. # «Host validation failed». Пускаем только адрес из Caddy.
- HOMEPAGE_ALLOWED_HOSTS={{ homepage_domain }} - HOMEPAGE_ALLOWED_HOSTS={{ homepage_domain }}
+3
View File
@@ -17,6 +17,9 @@ services:
- "USER_GID={{ owner_create_result.group }}" - "USER_GID={{ owner_create_result.group }}"
- "GITEA__server__SSH_PORT=2222" - "GITEA__server__SSH_PORT=2222"
# Ветка по умолчанию в новых репозиториях (у Gitea по умолчанию main)
- "GITEA__repository__DEFAULT_BRANCH=master"
# Mailer # Mailer
- "GITEA__mailer__ENABLED=true" - "GITEA__mailer__ENABLED=true"
- "GITEA__mailer__PROTOCOL=smtp+starttls" - "GITEA__mailer__PROTOCOL=smtp+starttls"
@@ -33,6 +33,9 @@ services:
- OAUTH2_PROVIDER=oidc - OAUTH2_PROVIDER=oidc
- OAUTH2_REDIRECT_URL=https://miniflux.vakhrushev.me/oauth2/oidc/callback - OAUTH2_REDIRECT_URL=https://miniflux.vakhrushev.me/oauth2/oidc/callback
- OAUTH2_USER_CREATION=1 - OAUTH2_USER_CREATION=1
# Метрики скрейпит netdata по докер-сети (miniflux_app:8080/metrics).
# Сузить METRICS_ALLOWED_NETWORKS нельзя: netdata и Caddy сидят в одной
# web_proxy_network, по IP их не различить. Наружу /metrics закрыт в Caddy.
- METRICS_COLLECTOR=1 - METRICS_COLLECTOR=1
- METRICS_ALLOWED_NETWORKS=0.0.0.0/0 - METRICS_ALLOWED_NETWORKS=0.0.0.0/0
+1 -1
View File
@@ -3,7 +3,7 @@ update_every: 15
jobs: jobs:
- name: caddyproxy - name: caddyproxy
url: http://caddyproxy:2019/metrics url: http://caddyproxy:2020/metrics
selector: selector:
allow: allow:
- "caddy_http_*" - "caddy_http_*"
+2 -2
View File
@@ -27,7 +27,7 @@ services:
wanderer_db: wanderer_db:
container_name: wanderer_db container_name: wanderer_db
image: "flomp/wanderer-db:{{ wanderer_version }}" image: flomp/wanderer-db:v0.18.3
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}" user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
depends_on: depends_on:
wanderer_search: wanderer_search:
@@ -53,7 +53,7 @@ services:
wanderer_web: wanderer_web:
container_name: wanderer_web container_name: wanderer_web
image: "flomp/wanderer-web:{{ wanderer_version }}" image: flomp/wanderer-web:v0.18.3
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}" user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
depends_on: depends_on:
wanderer_search: wanderer_search:
+3
View File
@@ -2,6 +2,9 @@
- name: 'Configure system' - name: 'Configure system'
ansible.builtin.import_playbook: playbook-system.yml ansible.builtin.import_playbook: playbook-system.yml
- name: 'Configure firewall'
ansible.builtin.import_playbook: playbook-ufw.yml
- name: 'Configure docker' - name: 'Configure docker'
ansible.builtin.import_playbook: playbook-docker.yml ansible.builtin.import_playbook: playbook-docker.yml
+1 -3
View File
@@ -10,11 +10,9 @@
base_dir: "{{ (application_dir, app_name) | path_join }}" base_dir: "{{ (application_dir, app_name) | path_join }}"
config_dir: "{{ (base_dir, 'config') | path_join }}" config_dir: "{{ (base_dir, 'config') | path_join }}"
# gethomepage.dev — проверь актуальный тег перед обновлением.
homepage_image: "ghcr.io/gethomepage/homepage:v1.13.2"
# Внутренний порт контейнера; наружу не публикуется, ходим только через Caddy. # Внутренний порт контейнера; наружу не публикуется, ходим только через Caddy.
homepage_port: 3000 homepage_port: 3000
# Домен, на котором Caddy отдаёт дашборд. homepage 1.x требует явный whitelist # Домен, на котором Caddy отдаёт дашборд. homepage требует явный whitelist
# Host за реверс-прокси, иначе «Host validation failed». # Host за реверс-прокси, иначе «Host validation failed».
homepage_domain: "start.vakhrushev.me" homepage_domain: "start.vakhrushev.me"
+23
View File
@@ -1,7 +1,19 @@
--- ---
# ufw фильтрует только цепочку INPUT, то есть хостовые сервисы (sshd,
# zabbix-агент). Порты, опубликованные докером (80, 443, 2222), идут через
# PREROUTING/FORWARD мимо ufw — закрыть их можно только правкой `ports:` в
# docker-compose соответствующего приложения.
- name: "Configure UFW firewall" - name: "Configure UFW firewall"
hosts: all hosts: all
vars:
# Опросчики Zabbix у Timeweb: только им нужен доступ к агенту на хосте.
# Список — из Server= в /etc/zabbix/zabbix_agentd.conf.
zabbix_poller_ips:
- "92.53.116.12"
- "92.53.116.111"
- "92.53.116.119"
tasks: tasks:
- name: "Ensure UFW is installed" - name: "Ensure UFW is installed"
ansible.builtin.apt: ansible.builtin.apt:
@@ -49,6 +61,17 @@
port: "443" port: "443"
proto: udp proto: udp
# Агент хостера слушает 0.0.0.0:10050 и сам проверяет адрес источника,
# но держать порт открытым для всего интернета незачем.
- name: "Allow Zabbix agent on port 10050 from Timeweb pollers"
community.general.ufw:
rule: allow
src: "{{ item }}"
port: "10050"
proto: tcp
comment: "Timeweb zabbix poller"
loop: "{{ zabbix_poller_ips }}"
- name: "Enable UFW" - name: "Enable UFW"
community.general.ufw: community.general.ufw:
state: enabled state: enabled
-1
View File
@@ -12,7 +12,6 @@
backups_dir: "{{ (base_dir, 'backups') | path_join }}" backups_dir: "{{ (base_dir, 'backups') | path_join }}"
gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}" gobackup_config: "{{ (base_dir, 'gobackup.yml') | path_join }}"
wanderer_version: "v0.18.3"
wanderer_origin: "https://wanderer.vakhrushev.me" wanderer_origin: "https://wanderer.vakhrushev.me"
tasks: tasks: