Commit 6c0e9492 authored by Vitaly Lipatov's avatar Vitaly Lipatov

docs: add infrastructure documentation (bugzilla, mail, nginx, sales, conntrack)

parent 0eaa3fca
# Bugzilla — bugs.etersoft.ru
Наша инсталляция Bugzilla (трекер Etersoft). Версия **5.2.0** (ручная установка, НЕ из rpm), поддерживает REST API и webservices. Используется для всех внутренних задач; с ней интегрирован `bugs-watcher` (см. memory `reference_bugs_watcher.md`) и kanban.
## Подключение
| Куда | Как |
|---|---|
| **SSH (root)** | `ssh -p32 root@bugs.etersoft.ru` (hostname `bugs`; `ssh -p32 lav@bugs.etersoft.ru` тоже работает) |
| **Web** | https://bugs.etersoft.ru/ (алиасы: `bugs.etersoft.com`, `bugs.etersoft.org`, `bugzilla.etersoft.ru/com`) |
| **IP** | 91.232.225.24 (CNAME `bugzilla.etersoft.ru`) |
| **Короткий URL бага** | https://bugs.etersoft.ru/17116 (nginx rewrite → `show_bug.cgi?id=17116`) |
| **REST API** | https://bugs.etersoft.ru/rest/... (см. «REST API и авторизация» ниже) |
| **Дашборд bugs-watcher** | https://bugs.etersoft.ru/bugs-watcher/ (static, отдельный репо `git@gitlab.eterfund.ru:etersoft/bugs-watcher.git`) |
⚠️ Каталог инсталляции `/var/www/bugs.etersoft.ru` принадлежит `root:apache2` с режимом `0750`**правки файлов только под root**, apache их читает.
## Архитектура
```
браузер → nginx :443 (ssl http2, LE-серт)
├─ / ─┐
├─ /rest/ │ → Apache httpd2 127.0.0.1:8080 (mod_perl `perl_module`) → Bugzilla
├─ /buglist.cgi │
├─ /attachment.cgi │
├─ /server-status ─┘ (etersoft-only)
├─ /bugs-watcher/ → alias /var/www/html/bugs-watcher/dashboard/ (static, nginx)
├─ /kanban/ → alias /var/www/html/kanban/
└─ /socket.io → Node `bugswatcher.service` 127.0.0.1:17003 (websocket, CORS *)
```
- **Веб-сервер приложений:** Apache httpd2 на `127.0.0.1:8080` с mod_perl (Bugzilla крутится под mod_perl, не как CGI). nginx — только reverse-proxy + TLS.
- **ДБ:** MySQL, хост `mysql.dmz.etersoft.ru`**10.20.30.191** (office MySQL 8.0, см. `reference_mysql_servers.md`; **НЕ** путать с mariadb.dmz .195 — там RT). БД `eter_bugs`, юзер `eter_bugs`, пароль в `localconfig` (`$db_pass`).
- **Кэш:** memcached `192.168.3.188:11211`, namespace `bugzilla:`.
- **nginx vhost:** `/etc/nginx/sites-enabled.d/bugs.etersoft.ru.conf` (+ `-80.conf` для HTTP→HTTPS).
## Конфиг (data/params.json)
Только избранные ключи (полный файл — `data/params.json`, ~80 ключей):
- `sslbase = https://bugs.etersoft.ru/`; `urlbase = http://...`; `ssl_redirect = 0` (TLS терминирует nginx); `strict_transport_security = this_domain_only`.
- **Авторизация:** `user_info_class = CGI`, `user_verify_class = DB` (аккаунты в БД, **без** LDAP/RADIUS — все поля LDAP/RADIUS пустые).
- `rememberlogin = on` (persistent login cookies после web-логина).
- `cookiepath = /` (**cookies покрывают `/rest`**), `cookiedomain = ""` (host-only).
- `requirelogin = 0` (сайт открыт анонимно, но **большинство багов в группе Etersoft → restricted**, REST без auth возвращает `{"bugs":[]}`).
- Группы: `insidergroup = Etersoft`, `timetrackinggroup = Etersoft`, `comment_taggers/chartgroup/querysharegroup = editbugs`, `or_groups = 0`.
- Прочее: `maintainer = lav@etersoft.ru`, `mailfrom = bugs-noreply@etersoft.ru`, `mail_delivery_method = Sendmail`, `defaultpriority = P4`, `defaultseverity = minor`, `useclassification/useqacontact/usetargetmilestone/usestatuswhiteboard = 1`.
## REST API и авторизация (ВАЖНО — root cause багов bugs-watcher)
⚠️ **REST НЕ honour-ит session-cookies.** `WebService/Server/REST.pm:203` выставляет `auth_no_automatic_login=1`, и `Auth::Login::Cookie.pm:38` пропускает чтение `Bugzilla_login`/`Bugzilla_logincookie`. Поэтому «я залогинен на сайте» **не помогает** для `/rest/` — нужны явные credentials. (Это относится только к REST/JSON-RPC; веб-страницы cookies читают как обычно.)
Два рабочих способа auth для REST:
1. **`?token=<login_token>`** — токен из `POST /rest/login` (login+password). Это logincookie (`userid-logincookie`), живёт `MAX_LOGINCOOKIE_AGE=30` дней, **валидация по `cookie+userid+(ipaddr match OR NULL)`** (`Cookie.pm:90`). При невалидности → `invalid_cookies_or_token`**32000**. IP-locking только если при логине передан `restrict_login=1` (дашборд его не шлёт).
2. **`?api_key=<key>`** — долгоживущий per-user API-ключ (User Prefs → API keys), **не протухает**, не IP-locked (`Auth::Login::APIKey`). bugs-watcher пока **не использует**.
`/rest/login` возвращает `{token,id}` и **НЕ** ставит session-cookie — токен живёт в localStorage вызывающего и независимо протухает.
Поведение для restricted-багов (группа Etersoft и т.п.) — проверено 2026-07-26:
| Запрос | Ответ |
|---|---|
| `/rest/bug?id=X` без auth | `{"bugs":[]}` (Bugzilla прячет сам факт существования) |
| `/rest/bug?id=X&token=ПРОТУХШИЙ` | `{"code":32000,"error":true,"message":"The cookies or token provide were not valid or have expired..."}` |
| `/rest/bug?id=X&token=СВЕЖИЙ` (из `/rest/login`) | полный объект бага (проверено на 17116: summary + `blocks:[15661,16987]`) |
| `/rest/bug?id=X` + session-cookies (без token) | `{"bugs":[]}`**cookies для REST игнорируются** |
**Вывод для bugs-watcher:** enrichment падает, потому что dashboard-токен в localStorage протух (30 дней), cookie-fallback бесполезен (REST их не читает). Решение — свежий токен (ре-логин), api_key (навсегда), или серверный proxy. Подробности и коммиты — в memory `reference_bugs_watcher.md`.
## Расширения и кастомизация (etersoft)
- **Расширения** (`extensions/`): `BmpConvert`, `Example`, `MoreBugUrl`, `OldBugMove`, `Voting` (+ `create.pl`). Auth-расширений нет.
- **etersoft-слой** (`template/en/default/etersoft/`, `js/etersoft/`):
- `bugswatcher.html.tmpl` — PROCESS'ится в `bug/edit.html.tmpl`, инжектит worker-клиент bugs-watcher (`#workers`, `#useremail`).
- `timersplash.html.tmpl` — виджет учёта времени на странице бага (`#timespent`, readonly).
- `bugsWatcher.js` — worker-клиент bugs-watcher (**ВНЕ** git-репо bugs-watcher; бэкапы `*.bak-20260725`, `*~`).
- `focusManager.js`, `timer.js`, `timer_common.js`, `timersplash.css`, `bugs-watcher.css`.
## Эксплуатация
- **Изменение params:** через админку (`editparams.cgi`) или правкой `data/params.json` (с осторожностью).
- **После правки Perl/шаблонов:** `systemctl reload httpd2` (mod_perl кэширует).
- **checksetup** (миграции БД и т.п.): `cd /var/www/bugs.etersoft.ru && ./checksetup.pl`.
- **Логи:** nginx — `access_ssl.log` / `access_rest.log` / `access_buglist.log` / `error_ssl.log`; apache — свои; bugs-watcher — `journalctl -u bugswatcher` + `/home/bugs-watcher/bugs-watcher.log`.
- **bugswatcher.service:** `/etc/systemd/system/bugswatcher.service``/usr/bin/node /var/www/html/bugs-watcher/server/index.js` (слушает 127.0.0.1:17003).
# Почтовая инфраструктура Etersoft
## Быстрая диагностика (сразу проверять при жалобах на почту)
```bash
# 1. Нагрузка на всех серверах
ssh -p32 root@mail.etersoft.ru "uptime"
ssh root@10.20.30.66 "uptime"
ssh root@mysql.auth.etersoft.ru "uptime"
# 2. Сервисы
ssh -p32 root@mail.etersoft.ru "systemctl status cyrus-imapd postfix --no-pager | grep Active"
# 3. Брутфорс (главная причина тормозов)
ssh -p32 root@mail.etersoft.ru "fail2ban-client status postfix-sasl"
# 4. Подключения
ssh -p32 root@mail.etersoft.ru "ss -tnp | grep -E ':993|:465|:143|:587|:25' | wc -l"
# 5. Зависшие запросы (photo = gravatar таймаут)
ssh root@10.20.30.66 "curl -s http://localhost/server-status | tr '\n' ' ' | grep -oP '<tr><td>.*?</tr>' | grep 'W'"
# 6. Photo-запросы (если >5s — gravatar plugin виноват)
ssh root@10.20.30.66 "grep 'photo' /var/log/httpd2/roundcube_access.log | tail -5 | awk '{print \$4, \$7, \$9, \$NF}'"
```
## Архитектура
```
Клиенты (IMAP/SMTP)
mail.etersoft.ru (CT 120, 91.232.225.46)
├─ Cyrus IMAP (993, 143)
├─ Postfix (25, 465, 587)
├─ fail2ban (postfix-sasl, postfix-etersoft)
└─ nginx (443, autoconfig)
roundcube.eterhost.ru (10.20.30.66)
├─ nginx + PHP-FPM
└─ Roundcube 1.6.15
mysql.auth.etersoft.ru (CT 219, 10.20.30.202)
└─ MySQL (БД mail, пользователи mailro/mail)
```
## Доступ
| Сервер | SSH | Примечание |
|--------|-----|------------|
| mail.etersoft.ru | `ssh -p32 root@mail.etersoft.ru` | CT 120 на border |
| roundcube | `ssh root@10.20.30.66` | Порт 22 |
| mysql.auth | `ssh root@mysql.auth.etersoft.ru` | Порт 22 |
| as.office (rspamd) | `ssh root@10.20.30.210` | Milter |
## Fail2ban (критично для производительности)
Конфиг: `/etc/fail2ban/jail.d/postfix.conf`
```ini
[postfix-sasl]
enabled = true
port = smtp,465,submission
logpath = /var/log/mail/all
backend = polling
action = eterban[name=postfix-sasl] # бан через Redis→ipset на priv
filter = postfix-sasl
maxretry = 1 # ← БЫЛО 3, менять на 1 при брутфорсе
findtime = 1200
bantime = 3600 # ← БЫЛО 5 сек, мало! Ставить 3600+
[postfix-etersoft]
enabled = true
maxretry = 20 # backstop, не трогать
bantime = 1
```
**Проблема:** брутфорс SASL LOGIN (~7000 попыток/день, разные IP по 1-2 раза). При maxretry=3 fail2ban не успевает банить → нагрузка 15+.
**Проверка:**
```bash
fail2ban-client status postfix-sasl # Currently failed/banned
fail2ban-client get postfix-sasl maxretry
fail2ban-client get postfix-sasl bantime
```
**Горячий фикс:**
```bash
sed -i '/postfix-sasl/,/bantime/s/maxretry = 3/maxretry = 1/' /etc/fail2ban/jail.d/postfix.conf
sed -i '/postfix-sasl/,/bantime/s/bantime = 5/bantime = 3600/' /etc/fail2ban/jail.d/postfix.conf
fail2ban-client reload
```
## Eterban (бан через Redis)
Цепочка: fail2ban → ban.py → Redis → eterban_switcher → ipset → iptables DNAT
```
mail.etersoft.ru priv (91.232.225.1)
fail2ban (postfix-sasl) eterban_switcher.py
↓ ↓ (subscribe 'ban')
ban.py → publish('ban', ip) ipset add eterban_1 <ip>
↓ ↓
Redis (10.20.30.101) iptables DNAT → 91.232.225.67
```
### Проверка всей цепочки
```bash
# 1. ban.py на mail (публикует в Redis)
ssh -p32 root@mail.etersoft.ru "/usr/share/eterban/ban.py 198.51.100.99 test"
# 2. Redis доступен
ssh -p32 root@mail.etersoft.ru "python3 -c 'import redis; r=redis.Redis(host=\"10.20.30.101\"); print(r.ping())'"
# 3. ipset на priv (IP должен появиться через 1-2 сек)
ssh -p32 lav@priv.etersoft.ru "sudo ipset list eterban_1 | grep 198.51.100.99"
# 4. iptables DNAT на priv
ssh -p32 lav@priv.etersoft.ru "sudo /usr/sbin/iptables -t nat -L PREROUTING -n | grep eterban_1"
# Удалить тестовый IP:
ssh -p32 lav@priv.etersoft.ru "sudo ipset del eterban_1 198.51.100.99"
```
### Конфиги
- mail: `/etc/eterban/settings.ini` (redis_server=10.20.30.101, hostname=mail)
- priv: `/etc/eterban/settings.ini` (redis_server=10.20.30.101, hostname=priv, i_interfaces, ban_server=91.232.225.67)
- fail2ban action: `/etc/fail2ban/action.d/eterban.conf``/usr/share/eterban/ban.py <ip> <name>`
### Типичные проблемы
- ipset на priv пуст → eterban_service не работает (`systemctl status eterban`)
- ban.py молча завершается → Redis недоступен (проверить `redis-cli -h 10.20.30.101 ping`)
- IP в ipset но DNAT не работает → iptables правила пропали после ребута (нужен PATH fix в override.conf)
## Cyrus IMAP
- Путь: `/var/spool/imap/domain/o/office.etersoft.ru/user/`
- Лог: `/var/log/maillog`, `/var/log/messages`
- Конфиг: `/etc/imapd.conf`
- Autocreate: `autocreate_inbox_folders: Archive|Drafts|Junk|Sent|Trash` (разделитель `|`)
- Восстановление mailbox: `/usr/lib/cyrus/reconstruct -r -f 'user.NAME@DOMAIN'`
## Postfix
- Конфиг: `/etc/postfix/main.cf`
- SASL: `/etc/sasl2/*.conf`
- MySQL maps: `/etc/postfix/mysql-{mydestination,virtual}.cf`
- TLS cert: `/etc/postfix/tls/mail.etersoft.ru_full.pem`
- Лог: `/var/log/mail/all`
## MySQL
- Сервер: mysql.auth.etersoft.ru (CT 219, 10.20.30.202)
- БД: `mail` (таблицы: accountuser, virtual, domain)
- Чтение: `ssh root@mysql.auth.etersoft.ru maildb -e 'SELECT ...'`
- Запись: пользователь `mail@%`, пароль в `/root/.my.cnf.root` на mysql
- Пользователи: `mailro` (SELECT), `mail` (INSERT/UPDATE)
## Roundcube
- Сервер: 10.20.30.66
- БД: `mysql.dmz.etersoft.ru/eter_roundcube`
- Конфиг: `/etc/roundcube/config.inc.php`
- Лог: `/var/log/roundcube/errors.log`
## rspamd (as.office.etersoft.ru)
- Milter: 10.20.30.210:11332
- Web UI: 10.20.30.210:11334 (пароль simsimopen)
- Параллельно с amavis, только заголовки (не reject)
## TLS сертификат
- Домен: mail.etersoft.ru + 15 SAN
- Живой: `/etc/letsencrypt/live/mail.etersoft.ru/`
- Deploy hook: `/etc/letsencrypt/renewal-hooks/deploy/update_pem.sh`
- **Важно:** nginx должен работать для webroot auth certbot!
## Типичные проблемы
| Симптом | Причина | Решение |
|---------|---------|---------|
| Высокая нагрузка (15+) | Внешний IMAP брутфорс | iptables блокирует внешний IMAP (993,143), allow 10.20.30.0/24 |
| Почта не открывается | Cyrus/Postfix упал | `systemctl restart cyrus-imapd postfix` |
| TLS ошибка | Сертификат протух | `certbot renew` (nginx должен работать!) |
| Медленный Roundcube | См. раздел Roundcube ниже | strace + server-status |
| Не приходит почта | Postfix reject | `grep reject /var/log/mail/all` |
## Roundcube — зависшие запросы (РЕШЕНО)
**Корень: `modcss` action** — Roundcube проксирует внешний CSS из HTML-писем. Если внешний URL недоступен → 60-секундный таймаут → блокирует Apache worker.
- `_action=modcss` вызывается при открытии preview письма
- Пытается скачать CSS из ссылок в HTML-контенте (рассылки, реклама)
- Все modcss запросы: ровно 60 секунд (nginx log `%D` = 60,123,579 μs)
- Gravatar plugin — был отдельной проблемой того же типа (external HTTP timeout)
**Фикс:** отключить gravatar + рассмотреть отключение modcss или shorter timeout
# nginx — настройка в инфраструктуре Etersoft
Справка по принятым у нас паттернам nginx. Опирается на реальный сетап
(azbykar = CT 102 фронтенд TLS, бэкенды на CT в DMZ, RT, remnawave и т.д.).
## Цепочка reverse-proxy (часто 2 nginx)
Типичный путь для HTTPS-сервиса в DMZ:
```
Browser ──https──▶ ФРОНТЕНД (azbykar CT 102, listen 443 ssl) ──proxy_pass http://<CT>:80──▶ БЭКЕНД nginx (:80) ──fastcgi_pass──▶ приложение (rt-fcgi, php-fpm …)
```
- **TLS терминируется на фронтенде.** Бэкенд слушает голый :80 (внутри DMZ).
- Поэтому приложению надо **отдельно сказать, что исходный запрос был по HTTPS**
(иначе оно генерит `http://`-ссылки — см. грабли ниже).
- realip на бэкенде: `set_real_ip_from <frontend-IP>; real_ip_header X-Forwarded-For;`
(на CT 412 для RT — `set_real_ip_from 10.20.30.23`).
## Конвенция `include/` на фронтенде (azbykar)
Shared-настройки лежат в `/etc/nginx/include/` и подключаются `include`-ом —
**НЕ пишутся инлайн**. Правило: новый proxy-заголовок/лимит → класть в `.inc`, не в server.
| Файл | Что делает |
|---|---|
| `trans-proxy.conf` | обёртка: `include trans-proxy.inc` + `proxy_set_header Host $host` + `include limits/dynamic.inc`. Подключать в `location` с `proxy_pass`. |
| `trans-proxy.inc` | **общие proxy-заголовки**: `X-Real-IP $remote_addr`, `X-Forwarded-For $proxy_add_x_forwarded_for`, **`X-Forwarded-Proto $scheme`**, `Connection close`, `Accept-Encoding ""`, `proxy_buffering on` + размеры. |
| `limits/dynamic.inc` | `limit_req zone=reqlim burst=80 nodelay;` (динамика). Пары: `static.inc`, `media.inc`, `cached.inc`, `admin.inc`. |
| `ssl.conf` / `sslhsts.conf` / `sslonly.conf` | TLS-настройки, HSTS, принудительный HTTPS. |
| `letsencrypt.conf` | `/.well-known/acme-challenge/` для certbot. |
| `set-mainhost.conf` | задаёт `$mainhost`. |
| `static-fallback.conf` / `static-stub.conf` | раздача статики с fallback на бэкенд. |
| `stat.conf` / `stat-apache.conf` | статус-страницы nginx. |
Хорошая ссылка-образец «как правильно проксировать с заголовками» — `remnawave.conf`
(CT 412): полный набор `Host/X-Real-IP/X-Forwarded-For/X-Forwarded-Proto/X-Forwarded-Host/X-Forwarded-Port`.
## Схема запроса (http vs https) — главная грабля за TLS-прокси
Приложение за прокси не знает свой реальный scheme **и/или порт**, если их не передать.
Симптом (RT 6.0, 2026-07-23) — **две стадии одной баги**:
- **Scheme:** RT плодил `http://` self-URLs → `SecurityError: pushState` (http vs https origin).
- **Port:** после `fastcgi_param HTTPS on;` (схема починена) RT продолжал клеить `:80` — бэкенд
слушает :80, и `fastcgi_param SERVER_PORT $server_port` = 80 утекал в URL →
`https://rt.etersoft.ru:80` ≠ origin `:443` → та же `SecurityError`, только по порту.
Итог (стадии «scheme» и «port в URL»): `htmx:swapError`**страница не перерисовывается** после
delete/update (статус остаётся старым; серверно action проходит). Фикс генерируемых URL — метод 1
(`$WebBaseURL`).
**Третья стадия того же корня** — баннер RT 6.0 «inline edit does not work under the current
domain». CSRF/whitelist-проверка (`IsRefererCSRFWhitelisted(GetWebURLFromRequest())`) берёт URL
запроса **напрямую из `SERVER_PORT`**, а не из `$WebBaseURL` → видит `rt.etersoft.ru:80`, не
попадает в `@ReferrerWhitelist` (`:443`). Лечится только правкой порта на fastcgi-хопе:
```nginx
fastcgi_param SERVER_PORT 443; # публичный порт, НЕ $server_port (бэкенд :80)
```
(`nginx -t && serv reload nginx``fastcgi_param` шлётся per-request, воркеры рестартить не надо.)
⚠️ Это переименовывает сессионную куку (`…:.80``…:.443`) → всех разлогинит один раз.
**Урок: scheme/port бэкенда утекают во ВСЕ места, где приложение строит свой URL из запроса —
чинить надо каждое** (scheme → HTTPS-env, порт → SERVER_PORT, каноника → $WebBaseURL).
Способы передать схему (по возрастанию «архитектурной чистоты»):
1. **Конфиг приложения (детерминированно).** Для RT: `Set($WebBaseURL, "https://rt.etersoft.ru");`
в `/etc/rt/RT_SiteConfig.pm`. Не зависит от проброса заголовков через N хопов — самый надёжный
вариант, **именно его мы и применили** (2026-07-23): покрывает scheme+host+port разом, включая
порт бэкенда (:80), который не лечится scheme-only фиксом (метод 2).
2. **Проброс `X-Forwarded-Proto` на fastcgi-хопе.** Фронтенд уже шлёт заголовок (через
`trans-proxy.inc` `X-Forwarded-Proto $scheme`). На бэкенд-nginx добавить в fastcgi-параметры:
```nginx
# map в http{} (конфиг nginx.conf / conf.d):
map $http_x_forwarded_proto $fcgi_https { default off; https on; }
# в RT-fcgi (рядом с fastcgi_params):
fastcgi_param HTTPS $fcgi_https;
```
(Голое `fastcgi_param HTTPS on;` тоже работает, если ВЕСЬ трафик через https-фронтенд — но
грубовато: прямое обращение к :80 тоже отметится как HTTPS.)
3. **`proxy_set_header X-Forwarded-Proto $scheme;`** на каждом proxy-хопе (для цепочек nginx→nginx→app).
Для PHP: на fastcgi-хопе `fastcgi_param HTTPS on;` (или `SetEnvIf X-Forwarded-Proto https HTTPS=on`
для apache) — иначе `$_SERVER['HTTPS']` пустой, куки/редиректы едут по http.
## fastcgi-параметры
У RT на CT 412 — `/etc/nginx/RT-fcgi` (плоский файл, подключается `include /etc/nginx/RT-fcgi;`
вместо дефолтного `fastcgi_params`). Содержит стандартные `QUERY_STRING/REQUEST_METHOD/...`,
`REMOTE_ADDR`, `PATH_INFO=$uri` (важно для Mason/FCGI). **Не несёт scheme/HTTPS** — отсюда баг выше.
## Проверка/применение
- `nginx -t` — проверить синтаксис (после любого изменения).
- `serv reload nginx` — применить без обрыва соединений (на ALT; `serv`, не `systemctl` напрямую).
- Логи: access/error локально в server-блоке (`/var/log/nginx/rt_access.log`).
- При репорте 5xx: фронтенд (azbykar) error-log + бэкенд nginx error-log + лог приложения
(RT: `journalctl -u 'rt-fcgi@*'` + `/var/log/rt/rt.log`).
## Соседство сервисов
На одном nginx (один CT) могут жить несколько server_name (SNI). Пример: CT 412 держит
одновременно `rt.etersoft.ru` (→ rt-fcgi) и `seti.eterfund.ru` (→ remnawave:3000/:3010) —
это **разные server_name/порты**, конфликта нет, но прод-сервисы лучше не мешать с прочими
на одном контейнере (влияет на апгрейды/рестарты/безопасность).
См. также: [host03.md](host03.md) (nginx+apache), [nfs.md](nfs.md),
[memory: reference_rt_etersoft_ru](../memory/reference_rt_etersoft_ru.md),
[memory: lesson_restart_container_verify_autostart](../memory/lesson_restart_container_verify_autostart.md).
# Sales infrastructure
## Серверы
- **sales.etersoft.ru** (VM 442, LXC на border, IP 91.232.225.25) — основной, production
- **oldsales.etersoft.ru** (VM 226, LXC на border, IP 10.20.30.52) — старый
- **newsales.office.etersoft.ru** (VM 518, LXC на gefest, IP 10.20.30.215) — тестовый, не используется
## SSH
На sales SSH слушает на **порту 32** (стандартный 22 не используется).
Пользователь для подключения — **builder** (не root).
### Прямой root-доступ lav (с 2026-07-17)
`ssh -p32 root@sales.etersoft.ru` — работает ключом lav (`~/.ssh/id_ed25519`).
Ключ lav добавлен в `/root/.ssh/authorized_keys` на **sales (VM 442, прод)**.
⚠️ **Грабли при правке authorized_keys через rootfs border** (непривилегированный LXC):
- live-rootfs CT 442 на border: **`/zfs/pool0/subvol-442-disk-0`** (storage `zpool`, mountpoint `/zfs/pool0`; pct config показывает `zpool:subvol-442-disk-0`).
- **владелец = `100000:100000`** (host-uid для root внутри непривилег. CT), НЕ `root root` (uid 0) — иначе sshd StrictModes отвергает ключ и pubkey-логин не работает. `chown 100000:100000`.
- **сначала дописать `\n`** к существующему файлу: если последняя строка без перевода, `>> key` приклеит её к чужому ключу → сломаются оба. Безопаснее переписать весь файл целиком (каждый ключ на своей строке).
- `PermitRootLogin prohibit-password` — pubkey для root разрешён. `pct exec 442 -- …` падает на taint (`-T switch`) из ssh-сессии — читать/писать конфиги лучше прямо через rootfs, не через pct exec.
## Доступ через PVE
Все три сервера — LXC на border. Для прямого управления (без SSH):
```bash
ssh root@border "pct exec <VMID> -- <команда>"
```
Или через корневую ФС:
```bash
ssh root@border "cat /zfs/pool0/subvol-<VMID>-disk-*/etc/..."
```
Доступ к oldsales через SSH напрямую не настроен (нет ключей на builder64), только через border.
## Каталог /var/www/site/downloads
Общий каталог на sales, используется для .task файлов (задания на сборку/установку).
Содержимое:
- `*.task` / `*.task.failed` / `*.task.broken` — файлы заданий
- `old-tasks/` — архив старых .task файлов (перенесённых с oldsales)
- `scripts/`, `rerun.sh`, `README.txt`, `SALESDIR` — вспомогательные файлы
## Монтирования (sshfs)
### Builder (builder64)
```
sales:/var/www/site/downloads → /srv/builder/sales
```
Работает от пользователя builder (uid=500), sshfs с опциями reconnect,sshfs_sync,cache=yes,compression=yes,uid=500,gid=500.
### Oldsales
```
sales:/var/www/site/downloads → /var/www/site/downloads
```
fstab:
```
sshfs#builder@sales.etersoft.ru:/var/www/site/downloads /var/www/site/downloads fuse sshfs port=32,reconnect,sshfs_sync,compression=yes,nonempty,allow_other,uid=504,gid=504,_netdev 0 0
```
- SSH ключ: ed25519 root@oldsales добавлен в authorized_keys builder'а на sales
- /dev/fuse проброшен в CT 226 через `pct set 226 -dev0 /dev/fuse`
- `allow_other` требует `user_allow_other` в `/etc/fuse.conf` на oldsales
- `uid=504,gid=504` — builder внутри oldsales (для прав на запись)
- SSH на sales идёт через порт 32 (настроен в `/root/.ssh/config` на oldsales: Host sales → Port 32)
## LXC ограничения
- guest_exec (qemu-guest-agent) не работает — только SSH
- fuse нужно пробрасывать через `pct set VMID -dev0 /dev/fuse`
- modprobe внутри CT не работает (нет /lib/modules)
# Conntrack NAT logging
Логирование NAT-трансляций на шлюзах и VPN-серверах для расследования инцидентов
(например, алертов CERT-Bund).
## Узлы
| Узел | Внутренние сети | Выходной IP |
|---|---|---|
| hetzner.egw.eterhost.ru | 10.27.x (OpenVPN), 10.28.x (OpenConnect), 10.32.x (GRE/офис), 10.10.20.x (IPsec) | 135.181.95.108 |
| vpn.eterfund.ru | 10.233.x, 10.235.x (OpenVPN) | 91.232.225.180 |
| evpn.eterfund.ru | 10.234.x (OpenVPN) | 91.232.225.181 |
| vpn.office | 192.168.10.x, 192.168.11.x, 192.168.0.x (OpenVPN) | 91.232.225.22 |
| server | 192.168.0.x, 192.168.8.x (per-host SNAT) | 91.232.225.201-219 |
## Как работает
Сервис `conntrack-log.service` слушает conntrack events через netlink (минимальная нагрузка,
event-based). Логирует только NEW-соединения от внутренних VPN-сетей. Пишет в файл.
- **Сервис**: `/etc/systemd/system/conntrack-log.service`
- **Лог**: `/var/log/conntrack/nat.log`
- **Ротация**: logrotate, ежедневно, 30 дней с компрессией
## Формат лога
```
[timestamp] [NEW] proto num ttl state src=ВНУТРЕННИЙ_IP dst=ВНЕШНИЙ_IP sport=PORT dport=PORT [UNREPLIED] src=ВНЕШНИЙ_IP dst=NAT_IP sport=PORT dport=NAT_PORT
```
## Поиск по инциденту
Скрипт `search-nat-log.sh` ищет по всем узлам:
```bash
# По IP
router/search-nat-log.sh 178.162.202.97
# По IP и порту
router/search-nat-log.sh 178.162.202.97 80
```
Ручной поиск на конкретном узле:
```bash
ssh -p32 root@hetzner.egw.eterhost.ru "grep '178.162.202.97' /var/log/conntrack/nat.log"
```
## Управление
```bash
# Статус
ssh -p32 root@УЗЕЛ "systemctl status conntrack-log"
# Перезапуск
ssh -p32 root@УЗЕЛ "systemctl restart conntrack-log"
# Текущий размер лога
ssh -p32 root@УЗЕЛ "wc -l /var/log/conntrack/nat.log"
```
## TODO
- [ ] **priv.etersoft.ru (91.232.225.1)** — главный маршрутизатор, через него проходит весь
транзитный трафик. Сейчас conntrack не загружен. Для логирования нужно загрузить модуль
nf_conntrack, что добавит overhead на каждый пакет (connection tracking). При 8 ГБ RAM и
~50 пользователях нагрузка должна быть приемлемой, но требуется аккуратное тестирование
(желательно в окно обслуживания). Без conntrack альтернатива — nftables log на SYN-пакеты,
но это ловит и мусорные SYN (сканы портов), в отличие от conntrack.
## История
- 2026-02-25: настроено после алерта CERT-Bund (ipidea malware, src 135.181.95.108)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment