fr1: вебхук Маруси — vhost astra.ruzzy.dev и пользователь туннеля marusia-tunnel
- nginx: vhost astra.ruzzy.dev (LE), только POST /marusia/webhook/ → 127.0.0.1:18788, limit_req 1r/s, 64k, без access log, error_log crit (секрет в пути); ссылка zz-, чтобы не перехватить ответ на неизвестный SNI у git - sshd: Match User marusia-tunnel — только remote forward 127.0.0.1:18788; добавлен в AllowUsers - живые конфиги fr1 в stacks/astra-marusia/ вместо nginx-location.example - fr1.md: эффективные настройки sshd проверены под root, отпечатки host-ключей - туннель ещё не поднят, интеграция не завершена (хвост в fr1.md) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3159921a4d
commit
e2bd908198
+15
-6
@@ -15,10 +15,11 @@
|
|||||||
- ⚠️ **Группа `docker` = root-эквивалент.** `docker:x:988:rus,tgclaude` — любой её член монтирует `/` в контейнер и делает что угодно от рута (этим же способом чистились root-овы файлы 2026-09-08). Поэтому «у `tgclaude` нет sudo» **не** означает ограничения прав на хосте.
|
- ⚠️ **Группа `docker` = root-эквивалент.** `docker:x:988:rus,tgclaude` — любой её член монтирует `/` в контейнер и делает что угодно от рута (этим же способом чистились root-овы файлы 2026-09-08). Поэтому «у `tgclaude` нет sudo» **не** означает ограничения прав на хосте.
|
||||||
- Вход с Mac: `ssh rus-161` (алиас в `~/.ssh/config`) → ключ `~/.ssh/id_161_97_93_252` (ed25519).
|
- Вход с Mac: `ssh rus-161` (алиас в `~/.ssh/config`) → ключ `~/.ssh/id_161_97_93_252` (ed25519).
|
||||||
- root-доступ по SSH закрыт; для root изнутри — `sudo -i`.
|
- root-доступ по SSH закрыт; для root изнутри — `sudo -i`.
|
||||||
|
- Пользователь `marusia-tunnel` (uid 1002, с 2026-10-11) — **только обратный SSH-туннель** от rpi для вебхука Маруси (`stacks/astra-marusia/`). Shell `/usr/sbin/nologin`, пароль заблокирован, групп кроме своей нет. `~/.ssh/authorized_keys` принадлежит root (сам пользователь его не поменяет), ключ с `restrict,port-forwarding,permitlisten="127.0.0.1:18788"`. Ограничения продублированы в sshd: `/etc/ssh/sshd_config.d/70-marusia-tunnel.conf` (`Match User`: `AllowTcpForwarding remote`, `PermitListen 127.0.0.1:18788`, `PermitOpen none`, `PermitTTY no`, `MaxSessions 0`, только publickey).
|
||||||
- **Пуш-ключ `metrics-push@rpi`** (с 2026-10-11) в `~rus/.ssh/authorized_keys` с `restrict,command="/home/rus/bin/pushgw-ingest rpi"`: этим ключом домашняя малина может только залить метрики в pushgateway — шелла, форвардинга и pty нет. Бэкап прежнего файла — `~/.ssh/authorized_keys.bak-20261011`.
|
- **Пуш-ключ `metrics-push@rpi`** (с 2026-10-11) в `~rus/.ssh/authorized_keys` с `restrict,command="/home/rus/bin/pushgw-ingest rpi"`: этим ключом домашняя малина может только залить метрики в pushgateway — шелла, форвардинга и pty нет. Бэкап прежнего файла — `~/.ssh/authorized_keys.bak-20261011`.
|
||||||
|
|
||||||
## SSH (`/etc/ssh/sshd_config.d/00-hardening.conf`)
|
## SSH (`/etc/ssh/sshd_config.d/00-hardening.conf`)
|
||||||
⚠️ В основном `/etc/ssh/sshd_config` строкой 42 стоит `PermitRootLogin yes` (правка шаблона Contabo). Побеждает всё равно дроп-ин: `Include /etc/ssh/sshd_config.d/*.conf` стоит строкой 12, а sshd берёт **первое** встреченное значение. То есть root-вход закрыт, но держится на порядке строк — уберёшь дроп-ин, и root-вход по ключу включится. ⏳ Рядом лежит `50-cloud-init.conf`, который из-под `rus` не читается: под root стоит проверить `sshd -T | grep -i permitrootlogin`.
|
⚠️ В основном `/etc/ssh/sshd_config` строкой 42 стоит `PermitRootLogin yes` (правка шаблона Contabo). Побеждает всё равно дроп-ин: `Include /etc/ssh/sshd_config.d/*.conf` стоит строкой 12, а sshd берёт **первое** встреченное значение. То есть root-вход закрыт, но держится на порядке строк — уберёшь дроп-ин, и root-вход по ключу включится. Рядом лежит `50-cloud-init.conf` (root 600) — эффективные значения проверены под root 2026-10-11, см. ниже.
|
||||||
|
|
||||||
```
|
```
|
||||||
PermitRootLogin no
|
PermitRootLogin no
|
||||||
@@ -27,9 +28,12 @@ PubkeyAuthentication yes
|
|||||||
KbdInteractiveAuthentication no
|
KbdInteractiveAuthentication no
|
||||||
ChallengeResponseAuthentication no
|
ChallengeResponseAuthentication no
|
||||||
MaxAuthTries 3
|
MaxAuthTries 3
|
||||||
AllowUsers rus tgclaude
|
AllowUsers rus tgclaude marusia-tunnel
|
||||||
```
|
```
|
||||||
Через socket-activation (`ssh.socket`).
|
Через socket-activation (`ssh.socket`), слушает `0.0.0.0:22` и `[::]:22`.
|
||||||
|
- `marusia-tunnel` дописан в `AllowUsers` 2026-10-11 (было `rus tgclaude`), бэкап — `00-hardening.conf.bak-20261011-171345`. ⚠️ Новому пользователю SSH нужен и здесь: без строки в `AllowUsers` sshd отказывает молча, клиент видит просто `Permission denied`.
|
||||||
|
- Проверено под root 2026-10-11: `50-cloud-init.conf` содержит `PasswordAuthentication yes`, но эффективное значение — `no` (`00-hardening` читается первым); `sshd -T` → `permitrootlogin no`, `passwordauthentication no`. ⚠️ То же «держится на порядке файлов»: переименуй `00-hardening` во что-то после `50-`, и вход по паролю включится.
|
||||||
|
- Отпечатки host-ключей: ED25519 `SHA256:M5J9nH+svyA3+beD7ZllUn1tayMMO2cxw/t3Z/kj6KU`, ECDSA `SHA256:fMZEytu7XI3W+Mzo4WQ6WZHDn0nYiagtbKb4JyUIX5w`, RSA `SHA256:53olW/dhoPclFgp8X6OWh0euoOfSmwXWZb6c026OGAw`.
|
||||||
|
|
||||||
## Сеть
|
## Сеть
|
||||||
- Управляется **netplan → systemd-networkd**, конфиг `/etc/netplan/50-cloud-init.yaml` (наследие cloud-init, сейчас просто статический файл).
|
- Управляется **netplan → systemd-networkd**, конфиг `/etc/netplan/50-cloud-init.yaml` (наследие cloud-init, сейчас просто статический файл).
|
||||||
@@ -101,8 +105,11 @@ backend=systemd banaction=ufw
|
|||||||
- nginx 1.24 (apt) + certbot 2.9 (`python3-certbot-nginx`), автопродление через systemd-таймер certbot.
|
- nginx 1.24 (apt) + certbot 2.9 (`python3-certbot-nginx`), автопродление через systemd-таймер certbot.
|
||||||
- vhost `/etc/nginx/sites-available/mon.ruzzy.dev.conf` → проксирует на Grafana (`127.0.0.1:3000`), HTTPS-редирект, websocket.
|
- vhost `/etc/nginx/sites-available/mon.ruzzy.dev.conf` → проксирует на Grafana (`127.0.0.1:3000`), HTTPS-редирект, websocket.
|
||||||
- vhost'ы `share.ruzzy.dev.conf` и `www.ruzzy.dev.conf` — статика с **общим** корнем `/var/www/share.ruzzy.dev` (`autoindex off`, `try_files $uri =404`, `server_tokens off`), у каждого свой сертификат. Используется для раздачи клиентских конфигов OpenVPN — сейчас там `marina-de.ovpn`, `masha-de.ovpn`.
|
- vhost'ы `share.ruzzy.dev.conf` и `www.ruzzy.dev.conf` — статика с **общим** корнем `/var/www/share.ruzzy.dev` (`autoindex off`, `try_files $uri =404`, `server_tokens off`), у каждого свой сертификат. Используется для раздачи клиентских конфигов OpenVPN — сейчас там `marina-de.ovpn`, `masha-de.ovpn`.
|
||||||
- vhost `default` — catch-all `server_name _`, `listen 80 default_server`, отдаёт дефолтную страницу `/var/www/html/index.nginx-debian.html`. На `:443` `default_server` **не задан**, поэтому TLS-запрос с неизвестным SNI попадает на первый по алфавиту vhost (`git.ruzzy.dev`) и получает его сертификат.
|
- vhost `default` — catch-all `server_name _`, `listen 80 default_server`, отдаёт дефолтную страницу `/var/www/html/index.nginx-debian.html`. На `:443` `default_server` **не задан**, поэтому TLS-запрос с неизвестным SNI попадает на первый по алфавиту vhost (`git.ruzzy.dev`) и получает его сертификат. Так отвечают и `https://ruzzy.dev`, и `https://fr1.ruzzy.dev` — своих vhost'ов и сертификатов у них нет, браузер видит несовпадение имени (проверено 2026-10-11).
|
||||||
- Сертификаты LE: `mon`, `git`, `share`, `www`. `cam.ruzzy.dev` удалён 2026-09-08 вместе с ipcam.
|
- vhost `astra.ruzzy.dev` (с 2026-10-11) — вебхук Маруси для разговорного сервиса на rpi, конфиг в репо `stacks/astra-marusia/`. Только `POST /marusia/webhook/…` → `127.0.0.1:18788/webhook/…` (SSH-туннель от rpi, см. пользователя `marusia-tunnel`), остальное — 404. Зона `limit_req` `marusia_webhook` в `/etc/nginx/conf.d/marusia-webhook.conf` (1 r/s, burst 5, 429); тело до 64k, таймауты 1s/5s/5s.
|
||||||
|
- ⚠️ Ссылка называется `sites-enabled/zz-astra.ruzzy.dev.conf`, а не по имени домена: иначе она встала бы по алфавиту раньше `git` и перехватила ответ на неизвестный SNI. Новые vhost'ы на букву раньше `g` подключать с тем же расчётом.
|
||||||
|
- ⚠️ Секрет вебхука — часть URL. Для location выключен access log, а `error_log /var/log/nginx/marusia-webhook.error.log crit`: на уровне `error` nginx пишет туда строку запроса с секретом (502 при упавшем туннеле, срабатывание `limit_req`). Повышать уровень лога нельзя.
|
||||||
|
- Сертификаты LE: `mon`, `git`, `share`, `www`, `astra` (с 2026-10-11, `certbot certonly --nginx`, продление тем же таймером). `cam.ruzzy.dev` удалён 2026-09-08 вместе с ipcam.
|
||||||
|
|
||||||
## Мониторинг (`~/monitoring`, stack в репо `stacks/monitoring/`)
|
## Мониторинг (`~/monitoring`, stack в репо `stacks/monitoring/`)
|
||||||
- Стек: **Prometheus + Grafana + node-exporter + cAdvisor + Alertmanager + Pushgateway** (docker compose).
|
- Стек: **Prometheus + Grafana + node-exporter + cAdvisor + Alertmanager + Pushgateway** (docker compose).
|
||||||
@@ -253,7 +260,9 @@ backend=systemd banaction=ufw
|
|||||||
- `~tgclaude/.openclaw-auth-profile-secrets` — права **777** на каталоге для секретов (сейчас пуст), стоит `chmod 700`.
|
- `~tgclaude/.openclaw-auth-profile-secrets` — права **777** на каталоге для секретов (сейчас пуст), стоит `chmod 700`.
|
||||||
- Не решено, нужны ли на fr1: `~rus/backups` (5.4 ГБ архивов с выведенного de1) и спящий openclaw (947 МБ дерева + 2.0 ГБ состояния + образ-снимок 1.55 ГБ).
|
- Не решено, нужны ли на fr1: `~rus/backups` (5.4 ГБ архивов с выведенного de1) и спящий openclaw (947 МБ дерева + 2.0 ГБ состояния + образ-снимок 1.55 ГБ).
|
||||||
- Страховочные файлы, оставленные после работ: `~rus/imgbot-repo-pre-merge-20260908-133444.tar.gz` (369 КБ), `~rus/imgbot-legacy-archive-rows-20260908.json` (808 КБ), `~rus/gitea/docker-compose.yml.bak-20260908`.
|
- Страховочные файлы, оставленные после работ: `~rus/imgbot-repo-pre-merge-20260908-133444.tar.gz` (369 КБ), `~rus/imgbot-legacy-archive-rows-20260908.json` (808 КБ), `~rus/gitea/docker-compose.yml.bak-20260908`.
|
||||||
- ⏳ Дроп-ин `/etc/ssh/sshd_config.d/50-cloud-init.conf` не читается из-под `rus` — под root проверить `sshd -T | grep -iE "permitrootlogin|passwordauth"`, чтобы знать эффективные значения, а не собранные из файлов вручную.
|
- ⏳ **Вебхук Маруси не доведён** (2026-10-11): ключ туннеля от rpi ещё не положен в `~marusia-tunnel/.ssh/authorized_keys`, туннель не поднят. Пока POST на `astra.ruzzy.dev/marusia/webhook/…` отдаёт 502. Дальше: туннель, проверка цепочки, skill_id и проверка на настоящей колонке (`stacks/astra-marusia/README.md`).
|
||||||
|
- Снимок `nginx -T`, `sshd -T`, `ss -lntp` до работ над вебхуком лежит в `~rus/marusia-audit/` (600). Скрипты `/tmp/marusia-stage{1,2,3}.sh` — одноразовые, удалить после завершения.
|
||||||
|
- Если IP малины не в `ignoreip` fail2ban, ошибки авторизации туннеля при переподключении могут забанить её на 1 ч (jail `sshd`), а повторные баны (`recidive`) — на неделю.
|
||||||
- ⚠️ Описания **других** серверов не сверялись: `servers/de1.md` и `servers/ru1.md` датируются временем до вывода de1, так что могут врать.
|
- ⚠️ Описания **других** серверов не сверялись: `servers/de1.md` и `servers/ru1.md` датируются временем до вывода de1, так что могут врать.
|
||||||
|
|
||||||
## Как это описание поддерживается
|
## Как это описание поддерживается
|
||||||
|
|||||||
@@ -22,12 +22,36 @@ StateDirectory. Не подключать этот сервис к админи
|
|||||||
Повтор webhook с теми же session_id/message_id получает кешированный ответ.
|
Повтор webhook с теми же session_id/message_id получает кешированный ответ.
|
||||||
Не более двух обращений к модели одновременно, не более 50 разговоров.
|
Не более двух обращений к модели одновременно, не более 50 разговоров.
|
||||||
|
|
||||||
Для завершения нужны: приватный навык в аккаунте VK владельца, его skill_id
|
## Публичный вход на fr1 (с 2026-10-11)
|
||||||
в env, публичный HTTPS URL с проксированием на rpi (например nginx на fr1
|
|
||||||
и отдельный ограниченный SSH-туннель). Доступа SSH к fr1 у astra на rpi сейчас нет;
|
Цепочка: VK → `https://astra.ruzzy.dev/marusia/webhook/<секрет>` → nginx на fr1 →
|
||||||
ключ Gitea — не ключ сервера. Не открывать домашний порт в интернет напрямую.
|
`127.0.0.1:18788` (обратный SSH-туннель, поднимает rpi) → rpi `127.0.0.1:8788/webhook/<секрет>`.
|
||||||
На публичном прокси отключить access log webhook (секрет в URL), ограничить
|
Домашние порты наружу не открыты, 18788 на fr1 слушает только loopback.
|
||||||
размер тела 64 KiB, POST и частоту запросов; таймаут 5 секунд.
|
|
||||||
|
- `fr1-nginx-astra.ruzzy.dev.conf` → `/etc/nginx/sites-available/astra.ruzzy.dev.conf`,
|
||||||
|
ссылка `sites-enabled/zz-astra.ruzzy.dev.conf`. ⚠️ Префикс `zz-` нужен: на :443 у fr1
|
||||||
|
нет `default_server`, и первый по алфавиту vhost отвечает на неизвестный SNI
|
||||||
|
(`ruzzy.dev`, `fr1.ruzzy.dev`) — без префикса им стал бы этот vhost вместо git.
|
||||||
|
- `fr1-nginx-conf.d-marusia-webhook.conf` → `/etc/nginx/conf.d/marusia-webhook.conf`
|
||||||
|
(зона `limit_req` 1 r/s, burst 5, отказ — 429).
|
||||||
|
- `fr1-sshd-70-marusia-tunnel.conf` → `/etc/ssh/sshd_config.d/70-marusia-tunnel.conf`;
|
||||||
|
плюс `marusia-tunnel` дописан в `AllowUsers` в `00-hardening.conf` — ⚠️ без этого
|
||||||
|
sshd пользователя молча не пустит («Permission denied» без причины на клиенте).
|
||||||
|
- Пользователь fr1 `marusia-tunnel`: nologin, пароль заблокирован, групп нет;
|
||||||
|
`authorized_keys` принадлежит root, ключ с `restrict,port-forwarding,permitlisten="127.0.0.1:18788"`.
|
||||||
|
- Отпечаток host-ключа fr1 (ED25519): `SHA256:M5J9nH+svyA3+beD7ZllUn1tayMMO2cxw/t3Z/kj6KU`.
|
||||||
|
|
||||||
|
⚠️ Секрет — часть пути, поэтому на fr1 для этого location `access_log off`, а
|
||||||
|
`error_log` отдельный и только `crit`: на уровне `error` nginx пишет строку запроса
|
||||||
|
(с секретом) при 502 от упавшего туннеля и при срабатывании `limit_req`.
|
||||||
|
Не поднимать уровень этого лога «для отладки» — секрет окажется на диске.
|
||||||
|
По HTTP `/marusia/` отдаёт 404 без лога и без редиректа.
|
||||||
|
|
||||||
|
Ожидаемые ответы до заполнения skill_id: GET → 403 (nginx), POST с неверным
|
||||||
|
секретом → 404, с верным → 403 (сервис), туннель лежит → 502.
|
||||||
|
|
||||||
|
Ключ Gitea — не ключ сервера; для туннеля отдельный ed25519 на rpi.
|
||||||
|
Не открывать домашний порт в интернет напрямую.
|
||||||
Регистрация/проверка: https://platform.vk.com/ и
|
Регистрация/проверка: https://platform.vk.com/ и
|
||||||
https://skill-tester.marusia.mail.ru/ (актуальность интерфейса проверить при входе).
|
https://skill-tester.marusia.mail.ru/ (актуальность интерфейса проверить при входе).
|
||||||
До заполнения skill_id все POST закрыты (403), знание случайного пути обязательно.
|
До заполнения skill_id все POST закрыты (403), знание случайного пути обязательно.
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Публичный вход в разговорный сервис «Астра» для Маруси (stacks/astra-marusia).
|
||||||
|
# Цепочка: VK → nginx → 127.0.0.1:18788 (обратный SSH-туннель от rpi, пользователь marusia-tunnel) → rpi 127.0.0.1:8788.
|
||||||
|
# ⚠️ Секрет — часть пути. Поэтому в location нет access_log, а error_log только crit:
|
||||||
|
# на уровне error nginx пишет строку запроса (с секретом) при 502/limit_req.
|
||||||
|
server {
|
||||||
|
server_name astra.ruzzy.dev;
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
location ^~ /marusia/webhook/ {
|
||||||
|
access_log off;
|
||||||
|
error_log /var/log/nginx/marusia-webhook.error.log crit;
|
||||||
|
|
||||||
|
client_max_body_size 64k;
|
||||||
|
client_body_timeout 5s;
|
||||||
|
|
||||||
|
limit_except POST {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
limit_req zone=marusia_webhook burst=5 nodelay;
|
||||||
|
limit_req_status 429;
|
||||||
|
|
||||||
|
proxy_connect_timeout 1s;
|
||||||
|
proxy_send_timeout 5s;
|
||||||
|
proxy_read_timeout 5s;
|
||||||
|
|
||||||
|
# /marusia/webhook/<секрет> → /webhook/<секрет>
|
||||||
|
proxy_pass http://127.0.0.1:18788/webhook/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen 443 ssl;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/astra.ruzzy.dev/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/astra.ruzzy.dev/privkey.pem;
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name astra.ruzzy.dev;
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
# по HTTP секрет уже утёк в сеть; хотя бы не пишем его в лог и не редиректим
|
||||||
|
location ^~ /marusia/ {
|
||||||
|
access_log off;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Вебхук Маруси (vhost astra.ruzzy.dev): не более 1 запроса/с с адреса
|
||||||
|
limit_req_zone $binary_remote_addr zone=marusia_webhook:1m rate=1r/s;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Только удалённый проброс 127.0.0.1:18788 для туннеля Маруси с rpi (stacks/astra-marusia)
|
||||||
|
Match User marusia-tunnel
|
||||||
|
AuthenticationMethods publickey
|
||||||
|
PasswordAuthentication no
|
||||||
|
KbdInteractiveAuthentication no
|
||||||
|
AllowTcpForwarding remote
|
||||||
|
PermitListen 127.0.0.1:18788
|
||||||
|
PermitOpen none
|
||||||
|
GatewayPorts no
|
||||||
|
AllowAgentForwarding no
|
||||||
|
AllowStreamLocalForwarding no
|
||||||
|
PermitTunnel no
|
||||||
|
X11Forwarding no
|
||||||
|
PermitTTY no
|
||||||
|
PermitUserRC no
|
||||||
|
MaxSessions 0
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Include inside the existing HTTPS server block on fr1 AFTER creating an SSH tunnel.
|
|
||||||
# Reverse tunnel must bind only to fr1 127.0.0.1:18788 -> rpi 127.0.0.1:8788.
|
|
||||||
# No public port 8788/18788; VK uses the existing HTTPS listener.
|
|
||||||
location ^~ /marusia/webhook/ {
|
|
||||||
access_log off;
|
|
||||||
client_max_body_size 64k;
|
|
||||||
limit_except POST { deny all; }
|
|
||||||
proxy_connect_timeout 1s;
|
|
||||||
proxy_read_timeout 5s;
|
|
||||||
proxy_pass http://127.0.0.1:18788/webhook/;
|
|
||||||
}
|
|
||||||
# Add a limit_req zone in nginx http{} and apply its limit here before exposure.
|
|
||||||
Reference in New Issue
Block a user