Compare commits
7
Commits
6ff11061d9
...
7e64d76d02
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e64d76d02
|
||
|
|
6bb0d61183
|
||
|
|
304263e7b1
|
||
|
|
25886d1be0
|
||
|
|
b9be642c5c
|
||
|
|
9c75a52529
|
||
|
|
a909442d8f
|
@@ -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%
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,8 +151,17 @@ miniflux.vakhrushev.me {
|
|||||||
import access_log
|
import access_log
|
||||||
|
|
||||||
# /metrics наружу не отдаём. METRICS_ALLOWED_NETWORKS тут не помогает: за
|
# /metrics наружу не отдаём. METRICS_ALLOWED_NETWORKS тут не помогает: за
|
||||||
to miniflux_app:8080
|
# прокси Miniflux видит приватный IP контейнера Caddy, а не адрес клиента.
|
||||||
}
|
# Netdata скрейпит miniflux_app:8080/metrics напрямую по докер-сети, мимо Caddy.
|
||||||
|
handle /metrics {
|
||||||
|
respond 404
|
||||||
|
}
|
||||||
|
|
||||||
|
handle {
|
||||||
|
reverse_proxy {
|
||||||
|
to miniflux_app:8080
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wakapi.vakhrushev.me {
|
wakapi.vakhrushev.me {
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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_*"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user