Update docs

This commit is contained in:
Magnus Root 2026-06-10 18:04:34 +03:00
parent 5dd61df087
commit 96c07cdddd
4 changed files with 196 additions and 54 deletions

View file

@ -2,12 +2,33 @@
## Обзор
API Sync — однопроходная утилита (не демон), запускаемая по cron. При каждом запуске выполняет три последовательных этапа: синхронизацию данных из API в PostgreSQL, очистку устаревших записей и обновление whitelist-файла в Git-репозитории.
API Sync — однопроходная утилита (не демон), запускаемая по cron. Поддерживает два режима запуска: основной и проверка дублей по ключу `--cd`.
---
## Режимы запуска
```bash
./API_sync # основной запуск
./API_sync --cd # только проверка дублирующихся доменов/алиасов
```
Пример cron-конфигурации:
```
# Основной запуск каждые 15 минут
*/15 * * * * install cd /home/install && /home/install/API_sync >> /var/log/API_sync.log 2>&1
# Проверка дублей каждый час
0 * * * * install cd /home/install && /home/install/API_sync --cd >> /var/log/API_sync.log 2>&1
```
---
## Поток выполнения
### Основной режим
```
main()
@ -20,32 +41,48 @@ main()
├─ 4. Параллельная обработка SID (worker pool)
│ └─ processSID() для каждого SID
│ ├─ GET /l7/resource/{sid}/global → whois
│ │ └─ ошибка → алерт 🔴 + следующий SID
│ ├─ GET /l7/origin/global?l7ResourceId={sid} → origins
│ │ └─ ошибка → алерт 🔴 + следующий SID
│ ├─ GET /l7/alias/global?l7ResourceId={sid} → aliases
│ │ └─ ошибка → алерт 🔴 + следующий SID
│ ├─ DNS резолв домена → проверка AntiDDOS
│ └─ upsertAPIInfo() → сравнение + запись в API_info + алерты
├─ 5. Очистка устаревших SID из API_info (cleanupRemovedSIDs)
└─ 6. Обновление WAF whitelist (updateWAFWhitelist)
├─ 6. Проверка дублирующихся доменов/алиасов (если CHECK_DUPLICATES=true)
│ └─ checkDuplicateDomains() → алерт 🔴 если найдены дубли
└─ 7. Обновление WAF whitelist (updateWAFWhitelist)
├─ git pull
├─ Загрузка IP из API_info (auto)
├─ Загрузка IP из manual_info (ручные)
├─ Загрузка IP из API_info (auto-ресурсы)
├─ Загрузка IP из manual_info (ручные ресурсы)
├─ Объединение и дедупликация IP
├─ Фильтрация WAF-сетей (таблица ips)
├─ Сравнение с текущим whitelist-файлом
├─ Запись файла + git commit + git push
└─ Отправка алерта об изменениях
```
### Режим --cd
```
main() --cd
├─ 1. Подключение к PostgreSQL
└─ 2. checkDuplicateDomains() → алерт 🔴 если найдены дубли
```
---
## Модули
### main.go — точка входа и worker pool
Инициализирует подключение к БД, собирает все SID для обработки и запускает пул горутин. Количество воркеров задаётся через `MAX_CONCURRENT_WORKERS` (по умолчанию 5). SID передаются воркерам через буферизированный канал.
При запуске с ключом `--cd` выполняет только проверку дублей и завершается. В основном режиме инициализирует подключение к БД, собирает все SID и запускает пул горутин. Количество воркеров задаётся через `MAX_CONCURRENT_WORKERS` (по умолчанию 5). SID передаются воркерам через буферизированный канал. Все воркеры работают параллельно — DNS-резолв, запросы к API и запись в БД выполняются одновременно для разных SID.
После завершения всех воркеров последовательно запускаются очистка устаревших записей и обновление whitelist.
После завершения всех воркеров последовательно запускаются: очистка устаревших записей, проверка дублей (если не отключена через `CHECK_DUPLICATES=false`) и обновление whitelist.
### config.go — конфигурация
@ -53,7 +90,7 @@ main()
### api.go — HTTP-клиент и структуры API
Содержит единственный HTTP-клиент с таймаутом 20 секунд и функцию `apiGet()`, которая выполняет GET-запрос к API API с Bearer-авторизацией и десериализует JSON-ответ в переданную структуру.
Содержит единственный HTTP-клиент с таймаутом 20 секунд и функцию `apiGet()`, которая выполняет GET-запрос к API ServicePipe с Bearer-авторизацией и десериализует JSON-ответ в переданную структуру.
Определены структуры ответов API: `originItem`, `aliasItem`, `whoisReAPIonse`, `apiList`.
@ -63,25 +100,31 @@ main()
**`loadL7IDs()`** — загружает SID из `apps_settings` для конкретного клиента с `mode = 'auto'`.
**`processSID()`** — оркестрирует обработку одного SID: делает три запроса к API, резолвит домен для проверки AntiDDOS, вызывает `upsertAPIInfo()`.
**`processSID()`** — оркестрирует обработку одного SID: делает три запроса к API, резолвит домен для проверки AntiDDOS, вызывает `upsertAPIInfo()`. При ошибке любого API-запроса отправляет алерт через `sendAPIErrorAlert()` с указанием SID, endpoint и текста ошибки.
**`upsertAPIInfo()`** — сравнивает новые данные со старыми из БД и при наличии изменений формирует алерты:
- изменение домена → алерт + флаг необходимости обновления PT AF
- изменение origins (IP, mode, weight) — через `compareOrigins()`
- изменение aliases — через `compareAliases()`
**`sendAPIErrorAlert()`** — формирует и отправляет алерт 🔴 при ошибке запроса к API ServicePipe. Указывает SID, endpoint (`whois`, `origins`, `aliases`) и текст ошибки.
**`upsertAPIInfo()`** — в начале подтягивает из БД `client_title` (из `apps_settings`) и `waf_provider` (из `client_info`) для использования в алертах. Затем сравнивает новые данные со старыми и при наличии изменений формирует алерты:
- изменение домена → строка `Домен изменён: old -> new` + флаг необходимости обновления WAF
- изменение origins — через `compareOrigins()`
- изменение aliases — через `compareAliases()` + флаг обновления WAF
- изменение WAF-настроек (vendor, instance, enabled) — отдельный алерт
Сохранение выполняется через `INSERT ... ON CONFLICT DO UPDATE` (upsert по `sid`).
**`cleanupRemovedSIDs()`** — сравнивает все SID в `API_info` с актуальным списком активных SID. Записи, которых нет в активном списке (ресурс удалён из `apps_settings` или сменил `mode`), удаляются из `API_info`. По каждому удалённому ресурсу отправляется алерт с указанием WAF-вендора (берётся из `apps_settings` через LEFT JOIN).
**`wafVendorWarning()`** — формирует строку предупреждения в зависимости от вендора: `ptaf` → PT AF, `sw` → SW, `wmx` → WMX.
**`checkDuplicateDomains()`** — загружает все `domain_name` и `aliases` из `API_info` в единую карту. Если одно и то же значение встречается у двух и более разных SID в любой комбинации полей — считается дублем. При наличии дублей отправляется алерт 🔴 с перечислением всех совпадений и их SID. В основном режиме управляется флагом `CHECK_DUPLICATES`, при запуске с `--cd` выполняется всегда.
**`wafVendorName()`** — возвращает читаемое название вендора: `ptaf``PT AF`, `sw``SW`, `wmx``WMX`.
**`wafVendorWarning()`** — возвращает строку предупреждения для алерта об удалении ресурса с указанием конкретного WAF-вендора.
### compare.go — функции сравнения
**`compareOrigins()`** — сравнивает два списка origins по IP-адресу. Определяет добавленные, удалённые и изменённые (mode/weight) записи. Возвращает отформатированную строку для алерта с текущим состоянием origins.
**`compareOrigins()`** — сравнивает два списка origins по IP-адресу. Определяет добавленные, удалённые и изменённые (mode/weight) записи. Возвращает отформатированную строку с изменениями и текущим состоянием origins.
**`compareAliases()`** — сравнивает два отсортированных списка доменов. Возвращает строку с изменениями и булев флаг наличия изменений.
**`compareAliases()`** — сравнивает два отсортированных списка доменов. Добавленные aliases оформляются как HTML-ссылки. Возвращает строку с изменениями и булев флаг наличия изменений.
### dns.go — DNS-резолвер
@ -100,7 +143,7 @@ main()
**Mattermost** — POST на `/api/v4/posts` с Bearer-токеном. HTML-теги Telegram конвертируются в Markdown через `htmlToMarkdown()`. Отправляется только если заполнены все три переменные: `MATTERMOST_URL`, `MATTERMOST_BOT_TOKEN`, `MATTERMOST_CHANNEL_ID`.
**Email** — через стандартный `net/smtp`. Поддерживает несколько адресов получателей через запятую в `EMAIL_TO`. Авторизация опциональна — если `EMAIL_SMTP_USER` пуст, отправка идёт без аутентификации (relay). HTML-теги Telegram удаляются через `stripHTMLTags()`. Отправляется только если заполнены `EMAIL_SMTP_HOST`, `EMAIL_FROM`, `EMAIL_TO`.
**Email** — через стандартный `net/smtp`. Поддерживает несколько адресов получателей через запятую в `EMAIL_TO`. При `EMAIL_SKIP_TLS_VERIFY=true` использует STARTTLS с отключённой проверкой сертификата. Механизм аутентификации выбирается автоматически по возможностям сервера: если сервер поддерживает `LOGIN` — используется он, иначе `PLAIN`. Реализован собственный `loginAuth` для совместимости с корпоративными SMTP (Exchange, Outlook). Отправляется только если заполнены `EMAIL_SMTP_HOST`, `EMAIL_FROM`, `EMAIL_TO`.
### git.go — Git-операции
@ -118,6 +161,8 @@ main()
5. Сравнивает с текущим содержимым файла (`compareWhitelists`)
6. При наличии изменений — перезаписывает файл, коммитит и пушит, отправляет алерт
**`loadAllOrigins()`** — загружает все origin IP из `API_info` (auto-ресурсы).
**`loadManualOrigins()`** — загружает все записи из `manual_info` как есть, без дополнительных условий. Таблица заполняется администратором вручную и программой не изменяется.
**`filterNonWAFOrigins()`** — исключает из списка IP-адреса, которые принадлежат WAF-сетям из таблицы `ips`. Такие адреса являются узлами самого WAF и не должны попадать в whitelist.
@ -128,14 +173,50 @@ main()
Все алерты отправляются через единую функцию `sendAlert()`. Типы алертов:
| Событие | Содержание |
|---|---|
| Изменение origins | Добавленные/удалённые/изменённые backend IP + текущее состояние |
| Изменение aliases | Добавленные/удалённые домены |
| Изменение домена | Старое и новое значение + предупреждение об обновлении WAF |
| Изменение WAF-настроек | Изменения waf_enabled, waf_vendor, waf_instance |
| Удаление ресурса | SID, домен, предупреждение с указанием конкретного WAF-вендора |
| Обновление whitelist | Добавленные/удалённые IP с привязкой к SID и домену, итоговое количество |
| Событие | Эмодзи | Содержание |
|---|---|---|
| Изменение origins/aliases/домена | 🟡 | SID, домен, TENANT, WAF provider, детали изменений, предупреждение об обновлении WAF, время |
| Изменение WAF-настроек инстанса | ⚪ | SID, домен, TENANT, изменения waf_enabled/vendor/instance в формате `было -> стало`, время |
| Удаление ресурса из мониторинга | 🗑 | SID, домен, предупреждение с указанием конкретного WAF-вендора |
| Ошибка запроса к API ServicePipe | 🔴 | SID, endpoint, текст ошибки, время |
| Дублирующиеся домены/алиасы | 🔴 | Список дублей с указанием SID, время проверки |
| Обновление whitelist | 🔄 | Добавленные/удалённые IP с привязкой к SID и домену, итоговое количество |
Предупреждение об обновлении WAF формируется динамически в зависимости от вендора: `ptaf` → PT AF, `sw` → SW, `wmx` → WMX.
---
## Переменные окружения
| Переменная | По умолчанию | Описание |
|---|---|---|
| `BEARER_TOKEN` | — | Токен авторизации API ServicePipe |
| `DB_HOST` | localhost | Хост PostgreSQL |
| `DB_PORT` | 5432 | Порт PostgreSQL |
| `DB_USER` | — | Пользователь БД |
| `DB_PASSWORD` | — | Пароль БД |
| `DB_NAME` | waf_info | Имя БД |
| `TELEGRAM_BOT_TOKEN` | — | Токен Telegram-бота |
| `TELEGRAM_CHAT_ID` | — | ID чата для алертов |
| `TELEGRAM_HTTP_PROXY` | — | HTTP-прокси для Telegram |
| `TELEGRAM_SOCKS5_PROXY` | — | SOCKS5-прокси для Telegram |
| `TELEGRAM_SOCKS5_USER` | — | Логин SOCKS5-прокси |
| `TELEGRAM_SOCKS5_PASSWORD` | — | Пароль SOCKS5-прокси |
| `MATTERMOST_URL` | — | URL Mattermost-сервера |
| `MATTERMOST_BOT_TOKEN` | — | Токен Mattermost-бота |
| `MATTERMOST_CHANNEL_ID` | — | ID канала Mattermost |
| `EMAIL_SMTP_HOST` | — | SMTP-сервер |
| `EMAIL_SMTP_PORT` | 587 | SMTP-порт |
| `EMAIL_SMTP_USER` | — | Логин SMTP |
| `EMAIL_SMTP_PASSWORD` | — | Пароль SMTP |
| `EMAIL_FROM` | — | Адрес отправителя |
| `EMAIL_TO` | — | Адреса получателей (через запятую) |
| `EMAIL_SKIP_TLS_VERIFY` | false | Отключить проверку TLS-сертификата SMTP |
| `GIT_REPO_URL` | — | URL Git-репозитория whitelist |
| `GIT_REPO_PATH` | — | Локальный путь к репозиторию |
| `WHITELIST_FILE` | whitelist_ptaf.txt | Имя файла whitelist |
| `MAX_CONCURRENT_WORKERS` | 5 | Количество параллельных воркеров |
| `CHECK_DUPLICATES` | true | Включить проверку дублей в основном режиме |
---
@ -147,3 +228,4 @@ main()
| `github.com/joho/godotenv` | Загрузка `.env` файла |
| `golang.org/x/net` | SOCKS5 прокси для Telegram |
| `net/smtp` | Отправка email (стандартная библиотека) |
| `crypto/tls` | TLS для SMTP с отключённой проверкой сертификата |

View file

@ -5,7 +5,7 @@
## Структура проекта
```
├── main.go # Точка входа, worker pool
├── main.go # Точка входа, worker pool, режим --cd
├── config.go # Конфигурация и переменные окружения
├── api.go # HTTP клиент и API структуры
├── database.go # Работа с PostgreSQL
@ -37,8 +37,6 @@ go build -o API_sync
go build -ldflags="-s -w" -o API_sync
```
Go автоматически найдёт все `.go` файлы в директории и скомпилирует их в один бинарник.
## Конфигурация
### Шаг 1: Создать файл конфигурации
@ -83,14 +81,16 @@ EMAIL_SMTP_USER=user@example.com
EMAIL_SMTP_PASSWORD=your_password
EMAIL_FROM=API_sync@example.com
EMAIL_TO=admin@example.com,team@example.com
EMAIL_SKIP_TLS_VERIFY=false
# Git репозиторий whitelist
GIT_REPO_URL=https://token@svc-git.test.ru/wmx/waf_whitelist.git
GIT_REPO_URL=https://token@svc-git.cirex.ru/wmx/waf_whitelist.git
GIT_REPO_PATH=/home/install/waf_whitelist
WHITELIST_FILE=whitelist_ptaf.txt
# Параллельность
# Параллельность и функции
MAX_CONCURRENT_WORKERS=5
CHECK_DUPLICATES=true
```
### Шаг 3: Защитить файл конфигурации
@ -104,14 +104,23 @@ chmod 600 API_sync.env
### Запуск вручную
```bash
# Основной запуск
./API_sync
# Только проверка дублирующихся доменов/алиасов
./API_sync --cd
```
### Запуск через cron
```bash
# /etc/cron.d/API_sync
# Основной запуск каждые 15 минут
*/15 * * * * install cd /home/install && /home/install/API_sync >> /var/log/API_sync.log 2>&1
# Проверка дублей каждый час
0 * * * * install cd /home/install && /home/install/API_sync --cd >> /var/log/API_sync.log 2>&1
```
## Возможности
@ -132,6 +141,10 @@ chmod 600 API_sync.env
**Очистка устаревших данных** — автоматическое удаление из API_info ресурсов, пропавших из apps_settings
**Алерты об ошибках API** — уведомление при недоступности API API для конкретного SID
**Проверка дублей** — поиск совпадающих доменов/алиасов между разными SID, запускается в основном режиме (управляется `CHECK_DUPLICATES`) или отдельно через `--cd`
**Режим auto/manual** — только ресурсы с `mode = 'auto'` синхронизируются через API; ресурсы из manual_info используются как дополнительный источник IP для whitelist
## База данных
@ -173,32 +186,34 @@ CREATE TABLE manual_info (
origins JSONB
);
-- Добавить столбец mode в apps_settings (если ещё не добавлен)
-- Добавить столбцы в apps_settings (если ещё не добавлены)
ALTER TABLE apps_settings ADD COLUMN mode VARCHAR(10) DEFAULT 'auto';
-- Добавить столбец waf_vendor в apps_settings (если ещё не добавлен)
ALTER TABLE apps_settings ADD COLUMN waf_vendor VARCHAR(50);
-- Добавить столбец в client_info (если ещё не добавлен)
ALTER TABLE client_info ADD COLUMN waf_provider VARCHAR(50);
```
## Логи
Программа выводит детальные логи:
```
2025/12/26 10:00:00 Start sync API_info
2025/12/26 10:00:00 Total SIDs to process: 25
2025/12/26 10:00:00 Using 5 concurrent workers
2025/12/26 10:00:01 [Worker 0] Processing SID 10307
2025/12/26 10:00:01 [WAF Info] SID: 10307, WAF Enabled: 1, Vendor: ptaf, Instance: PTAFd_02_03
2025/12/26 10:00:01 [AntiDDOS Check] ✅ AntiDDOS is ENABLED for nationallottery.ru
2025/12/26 10:00:01 [Telegram] Using SOCKS5 proxy: proxy.example.com:1080
2025/12/26 10:00:15 Finish sync API_info
2025/12/26 10:00:15 Start cleanup of removed SIDs
2025/12/26 10:00:15 [Cleanup] No stale SIDs found in API_info
2025/12/26 10:00:15 Finish cleanup of removed SIDs
2025/12/26 10:00:15 Start WAF whitelist update
2025/12/26 10:00:16 [WAF Whitelist] No changes needed
2025/12/26 10:00:16 Finish WAF whitelist update
2026/06/10 10:00:00 Start sync API_info
2026/06/10 10:00:00 Total SIDs to process: 25
2026/06/10 10:00:00 Using 5 concurrent workers
2026/06/10 10:00:01 [Worker 0] Processing SID 10307
2026/06/10 10:00:01 [WAF Info] SID: 10307, WAF Enabled: 1, Vendor: ptaf, Instance: PTAFd_02_03
2026/06/10 10:00:01 [AntiDDOS Check] ✅ AntiDDOS is ENABLED for nationallottery.ru
2026/06/10 10:00:01 [Telegram] Using SOCKS5 proxy: proxy.example.com:1080
2026/06/10 10:00:15 Finish sync API_info
2026/06/10 10:00:15 Start cleanup of removed SIDs
2026/06/10 10:00:15 [Cleanup] No stale SIDs found in API_info
2026/06/10 10:00:15 Finish cleanup of removed SIDs
2026/06/10 10:00:15 Start duplicate domain/alias check
2026/06/10 10:00:15 [Duplicate Check] No duplicates found
2026/06/10 10:00:15 Finish duplicate domain/alias check
2026/06/10 10:00:15 Start WAF whitelist update
2026/06/10 10:00:16 [WAF Whitelist] No changes needed
2026/06/10 10:00:16 Finish WAF whitelist update
```
## Troubleshooting
@ -228,3 +243,11 @@ MAX_CONCURRENT_WORKERS=10
### Telegram не доступен напрямую
Заполните переменные прокси в конфигурации. Утилита автоматически попробует HTTP-прокси, затем SOCKS5, и только если оба недоступны — прямое подключение.
### Ошибка TLS при отправке email
Если сертификат SMTP-сервера просрочен или невалиден:
```env
EMAIL_SKIP_TLS_VERIFY=true
```

View file

@ -63,18 +63,21 @@ func processSID(db *sql.DB, sid int64) error {
// WHOIS
var whois whoisResponse
if err := apiGet(whoisURL+fmt.Sprint(sid)+"/global", &whois); err != nil {
sendAPIErrorAlert(sid, "whois", err)
return err
}
// ORIGINS
var originsResp apiList[originItem]
if err := apiGet(originURL+fmt.Sprint(sid), &originsResp); err != nil {
sendAPIErrorAlert(sid, "origins", err)
return err
}
// ALIASES
var aliasResp apiList[aliasItem]
if err := apiGet(aliasURL+fmt.Sprint(sid), &aliasResp); err != nil {
sendAPIErrorAlert(sid, "aliases", err)
return err
}
@ -331,7 +334,7 @@ func upsertSPInfo(
wafProviderClient,
strings.Join(changeDetails, "\n"),
ptafWarning,
now.Format("2006-01-02 15:04:05 UTC+3"),
now.Format("2006-01-02 15:04:05")+" UTC+3",
)
if err := sendAlert(message); err != nil {
@ -353,7 +356,7 @@ func upsertSPInfo(
domain,
clientTitle,
strings.Join(wafChangeDetails, "\n"),
now.Format("2006-01-02 15:04:05 UTC+3"),
now.Format("2006-01-02 15:04:05")+" UTC+3",
)
if err := sendAlert(wafMessage); err != nil {
@ -366,6 +369,24 @@ func upsertSPInfo(
return nil
}
func sendAPIErrorAlert(sid int64, endpoint string, err error) {
now := time.Now().In(time.FixedZone("UTC+3", 3*60*60))
message := fmt.Sprintf(
"🔴 <b>Ошибка запроса к API ServicePipe</b>\n\n"+
"<b>SID:</b> %d\n"+
"<b>Endpoint:</b> %s\n"+
"<b>Ошибка:</b> %s\n\n"+
"<b>Время:</b> %s",
sid,
endpoint,
err.Error(),
now.Format("2006-01-02 15:04:05")+" UTC+3",
)
if alertErr := sendAlert(message); alertErr != nil {
log.Printf("[API Error Alert] Failed to send alert for SID %d: %v", sid, alertErr)
}
}
func wafVendorName(vendor string) string {
switch strings.ToLower(vendor) {
case "ptaf":
@ -559,7 +580,7 @@ func checkDuplicateDomains(db *sql.DB) error {
}
now := time.Now().In(time.FixedZone("UTC+3", 3*60*60))
msg.WriteString(fmt.Sprintf("\n<b>Время проверки:</b> %s", now.Format("2006-01-02 15:04:05 UTC+3")))
msg.WriteString(fmt.Sprintf("\n<b>Время проверки:</b> %s", now.Format("2006-01-02 15:04:05")+" UTC+3"))
if err := sendAlert(msg.String()); err != nil {
log.Printf("[Duplicate Check] Failed to send alert: %v", err)

16
main.go
View file

@ -2,12 +2,28 @@ package main
import (
"log"
"os"
"sync"
_ "github.com/lib/pq" // PostgreSQL driver
)
func main() {
// Режим --cd: только проверка дублей
if len(os.Args) > 1 && os.Args[1] == "--cd" {
log.Println("Running duplicate domain/alias check only")
db, err := dbConnect()
if err != nil {
log.Fatal(err)
}
defer db.Close()
if err := checkDuplicateDomains(db); err != nil {
log.Printf("Duplicate check error: %v", err)
}
return
}
log.Println("Start sync sp_info")
db, err := dbConnect()