31 KiB
Архитектура Magos
Обзор системы
Magos - это система управления WAF (Web Application Firewall) контейнерами на базе Nginx/Angie, которая:
- Автоматически разворачивает и управляет Docker-контейнерами с Nginx
- Настраивает балансировщик Angie для распределения трафика
- Интегрируется с API ServicePipe для получения конфигурации
- Поддерживает graceful shutdown при масштабировании и миграции
- Обеспечивает изоляцию клиентов через отдельные контейнеры
- Поддерживает два WAF-вендора: PTAF (Docker-контейнеры) и SW/SolidWall (нативный процесс)
Ключевые особенности:
- Многоклиентовая архитектура - каждый клиент имеет свои контейнеры
- Поддержка двух вендоров - PTAF (Docker) и SolidWall (нативный nginx)
- Динамическое управление портами - автоматическое выделение портов из пулов с учётом всех состояний сокетов
- Кэширование данных - минимизация обращений к API
- Failover БД - автоматическое переключение между primary/secondary
- Graceful draining - безопасное удаление контейнеров без потери трафика
- Умный lock-файл - автоматическая очистка при падении процесса
Компоненты системы
1. Основные файлы программы
main.go # Точка входа, основной цикл обработки
config.go # Управление конфигурацией
db.go # Работа с PostgreSQL
api.go # Интеграция с ServicePipe API
types.go # Структуры данных
utils.go # Утилиты и вспомогательные функции
2. Управление контейнерами
containers_part1.go # Создание и настройка контейнеров
containers_part2.go # Управление жизненным циклом контейнеров
drain.go # Graceful shutdown и миграция
3. Генерация конфигураций
nginx.go # Генерация конфигов Nginx и Angie
ports.go # Управление портами
ptaf_processor.go # Обработка PTAF-клиентов
sw_processor.go # Обработка SW/SolidWall-клиентов
Поддерживаемые WAF-вендоры
PTAF (Positive Technologies Application Firewall)
Схема проксирования:
Клиент → Angie (хост) → Docker-контейнер (PTAF nginx) → Origin
- Контейнеры поднимаются через docker-compose
- Angie — фронтенд-балансировщик, конфиги в
/etc/angie/http.d/angie-ptaf-{clientTitle}.conf - Nginx внутри контейнера — WAF-агент, конфиги в
/home/install/conf/ptaf-nginx/ - Порты выделяются автоматически из диапазонов 18000-20999 (HTTP) и 21000-23999 (HTTPS)
SW / SolidWall
Схема проксирования:
Клиент → solidwall-nginx (WAF, публичный) → solidwall-nginx (внутренний) → Origin
- Работает без Docker, напрямую на хосте
- Двойное проксирование через внутренние порты 8000 (HTTP) и 9000 (HTTPS)
- Конфиги:
/etc/solidwall-nginx/sites-available/solidwall-{clientTitle}-{l7ResourceID}.conf - Симлинки в
sites-enabled/ - Утилита не трогает:
nginx.conf, директориюssl/,default.conf
Архитектура данных
База данных PostgreSQL
Таблица instances_new
Связь hostname → instance (waf_instance)
hostname | instance
------------|------------
waf-host-01 | instance-a
waf-host-01 | instance-b
waf-host-02 | instance-c
Таблица client_info
Основная информация о клиентах
Поля:
- client_title # Уникальный идентификатор клиента
- containers_count # Количество контейнеров (0 = drain всех)
- ptaf_config # Строка подключения к PTAF-серверу (CONNECTION_STRING)
- fluent_bit_port # Порт для FluentBit логирования
- waf_instance # Instance для привязки к хосту
- docker_image # Docker-образ для контейнеров клиента (переопределяет глобальный)
- docker_image_download # URL для скачивания образа
- debug # Режим отладки (bool, влияет на лог-уровень)
- shm_size # Размер /dev/shm в GB (дефолт: 1)
Важно: waf_instance определяет на каком хосте должен работать клиент
containers_count = 0: все контейнеры клиента помечаются для drain, Angie-конфиг удаляется
Таблица apps_settings
Настройки каждого L7 ресурса (домена)
Основные поля:
- l7resourceid # ID ресурса из ServicePipe API
- client_title # Связь с client_info
- waf_enabled # Включен ли WAF
- waf_vendor # 'ptaf' или 'sw'
- mode # auto/manual/disabled
Порты:
- custom_input_http_ports[] # HTTP порты для прослушивания (Angie/SW)
- custom_input_https_ports[] # HTTPS порты для прослушивания (Angie/SW)
- custom_output_http_ports[] # HTTP порты для проксирования к origin
- custom_output_https_ports[] # HTTPS порты для проксирования к origin
Кастомизация Nginx (PTAF):
- upstream_nginx_custom # Кастомные upstream директивы
- server_nginx_custom # Кастомный server блок / SERVER_BLOCK
- location_nginx_custom # Кастомные location директивы
- server_directives_nginx_custom # Дополнительные server директивы
Кастомизация Angie:
- upstream_angie_custom # Кастомные upstream директивы
- server_angie_custom # Кастомный server блок / SERVER_BLOCK
- location_angie_custom # Кастомные location директивы
- server_directives_angie_custom # Дополнительные server директивы
- custom_angie_ssl # Полная замена SSL директив
- custom_sw_nginx_ssl # Кастомные SSL директивы для SW
Специальные настройки:
- sni # 0 = keepalive on, 1 = keepalive off
- ssl_enabled # true/false (по умолчанию true)
- max_fails # Попыток до пометки сервера недоступным (дефолт 5)
- fail_timeout # Время восстановления сервера (дефолт 15с)
- balancing_method # Метод балансировки: least_conn, ip_hash, random и др.
- proxy_next_upstream_codes # HTTP-коды для proxy_next_upstream (дефолт: 500,502,503,504)
- ptaf_fallback_code # Код ответа ptaf_fallback: число или 'pass' (дефолт: 418)
Таблица sp_info (кэш API данных)
Кэширует данные из ServicePipe API
Поля:
- sid # l7resourceid
- domain_name # Основной домен
- aliases # JSON массив алиасов
- origins # JSON массив origin серверов
- updated_at # Timestamp обновления
TTL кэша: 10 минут
Таблица manual_info
Ручное управление (альтернатива API)
Поля:
- sid # l7resourceid
- domain_name # Основной домен
- aliases # JSON массив алиасов
- origins # JSON массив origin серверов
Примечание: Данные из manual_info не имеют TTL и всегда актуальны
Таблица ips
Настройки IP сетей
Поля:
- id (always = 1)
- antibot_networks[] # Массив CIDR для antibot
- waf_networks[] # Массив CIDR для WAF (фильтруются из origins)
Процесс работы
1. Инициализация
main() → initConfig() → connectToDB()
Шаги:
- Создание lock-файла
/var/lock/ptaf-main.lockс PID (умная проверка — автоматически удаляет если процесс уже не существует) - Подключение к БД (primary → secondary fallback)
- Загрузка IP сетей из таблицы
ips - Получение hostname текущего сервера
- Определение instances для этого хоста
- Для PTAF-хостов: проверка/скачивание Docker-образов (по одному на клиента)
2. Основной цикл обработки
┌─────────────────────────────────────────┐
│ Глобальная проверка drain-контейнеров │
│ (processAllDrainingContainersGlobally) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Для каждого instance на этом хосте │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Получить всех клиентов для instance │
│ (getClientInfoByInstance) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Определить vendor (ptaf/sw) │
│ → processPTAFClient / processSWClient │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Финальная проверка drain Angie-конфигов│
│ (processDrainingAngieConfigs) │
└─────────────────────────────────────────┘
3. Обработка PTAF-клиента
1. Проверка принадлежности хоста к WAF instance
├─ Не принадлежит → handleClientMigration (drain всех контейнеров + Angie конфига)
└─ Принадлежит → продолжаем
2. containers_count = 0?
└─ Да → drain всех контейнеров + Angie конфига, выход
3. cleanStaleDrainMarkers — очистка устаревших маркеров
4. fetchPTAFResourcesData — получение данных ресурсов из API/кеша
5. resolvePortsForClient — загрузка/выделение портов
├─ .ports.json существует → переиспользуем порты
├─ Scale up → сохраняем старые порты, добавляем новые
└─ Нет файла → выделяем все заново
6. generateAngieConfigsWithoutReload — генерация Angie конфига
7. setupContainers — генерация конфигов и управление контейнерами
├─ Запущен недавно, порты не слушают → пересоздаём
├─ Compose изменился → пересоздаём
├─ Конфиг изменился → nginx -s reload
└─ Новый → запускаем (с ожиданием освобождения портов)
8. Проверка портов → reload Angie если нужно
9. processDrainingContainers — drain лишних контейнеров
4. Обработка SW-клиента
1. Проверка принадлежности хоста к WAF instance
└─ Не принадлежит → пропускаем (миграция не реализована для SW)
2. Создание директорий: sites-available, sites-enabled, /var/log/solidwall
3. Для каждого ресурса:
- Генерация конфига solidwall-{clientTitle}-{l7ResourceID}.conf
- Создание симлинка в sites-enabled
4. solidwall-nginx -t + solidwall-nginx -s reload (если конфиги изменились)
5. Сбор данных о ресурсах
Для каждого ресурса определяется режим работы через поле mode:
Режим auto (по умолчанию)
1. Проверка кэша sp_info (TTL 10 мин)
├─ Данные свежие → использовать
└─ Данные устарели → API запрос
2. API запросы (с retry × 3):
├─ GET /l7/resource/{id}/global → domain_name
├─ GET /l7/alias/global?l7ResourceId={id} → aliases
└─ GET /l7/origin/global?l7ResourceId={id} → origins
3. Фильтрация origins (удалить WAF IP)
4. Сохранение в sp_info
Режим manual
1. Чтение из таблицы manual_info
2. Фильтрация origins (удалить WAF IP)
3. API и кэш игнорируются
Режим disabled
Ресурс полностью пропускается
Управление портами
Диапазоны портов
const (
HTTPPortStart = 18000 // HTTP: 18000-20999 (3000 портов)
HTTPPortEnd = 20999
HTTPSPortStart = 21000 // HTTPS: 21000-23999 (3000 портов)
HTTPSPortEnd = 23999
)
Важно: На хостах необходимо зарезервировать этот диапазон чтобы ядро не использовало его для исходящих ephemeral-соединений:
sysctl -w net.ipv4.ip_local_reserved_ports=18000-23999
Алгоритм выделения портов
┌────────────────────────────────────────────────┐
│ 1. Получить занятые порты (ss -tuanl) │
│ Включает: LISTEN, ESTAB, TIME-WAIT │
└────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────┐
│ 2. Загрузить сохраненные порты │
│ из .ports.json (если есть) │
│ ├─ Scale up → сохранить старые, добавить │
│ └─ Нет файла → выделить все заново │
└────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────┐
│ 3. Для каждого ресурса × контейнера: │
│ - Определить нужные порты │
│ - Выделить свободные Docker порты │
└────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────┐
│ 4. Сохранить в .ports.json │
└────────────────────────────────────────────────┘
Структура портов
type PortMapping struct {
HTTPPorts map[int]int // map[customPort]dockerPort
HTTPSPorts map[int]int // map[customPort]dockerPort
}
Docker-образ
Образ задаётся на двух уровнях с приоритетом клиента над глобальным:
| Уровень | Источник |
|---|---|
| Глобальный | DOCKER_IMAGE в /etc/magos/magos.env |
| Клиентский | docker_image в таблице client_info |
Если образ отсутствует локально — скачивается tar-архив по URL из docker_image_download (или DOCKER_IMAGE_URL) и загружается через docker load. Одинаковые образы проверяются только один раз за запуск.
Режим отладки (debug)
Поле debug в client_info. При debug = true:
docker-compose.yml:
- PTAF_DEBUG=True
- ERROR_LOG_LEVEL=debug
nginx.conf:
env PTAF_LOG_LEVEL=DEBUG;
env DEBUG=true;
error_log stderr debug;
Angie server-блоки:
client_body_buffer_size 1m;
Важно: включение debug режима вызывает пересоздание контейнера.
Graceful Shutdown и миграция
Типы drain операций
1. Масштабирование вниз (Scale Down)
Timeout: 90 секунд
Триггер: containers_count уменьшен в БД
Действие: лишние контейнеры помечаются маркером /tmp/ptaf-drain-ptaf_{client}_{num}
Через 90с: docker-compose down + удаление файлов
2. Отключение клиента (containers_count = 0)
Timeout: 90 секунд
Триггер: containers_count = 0
Действие: все контейнеры + Angie-конфиг помечаются для drain
Через 90с: удаление контейнеров и конфига Angie
3. Миграция между хостами
Timeout: 3600 секунд (1 час)
Триггер: waf_instance изменился в БД
Действие: контейнеры и Angie-конфиг помечаются маркером :migration
Через 1ч: проверка актуальности → удаление если клиент не вернулся
Формат маркеров
/tmp/ptaf-drain-ptaf_{clientTitle}_{001} — контейнер (содержит timestamp или timestamp:migration)
/tmp/ptaf-angie-drain-{clientTitle} — Angie конфиг (аналогичный формат)
Кастомизация конфигов через SERVER_BLOCK
Формат для полей server_nginx_custom и server_angie_custom:
[SERVER_BLOCK:443:example.com:www.example.com]
proxy_ssl_certificate /etc/ssl/certs/example.crt;
proxy_ssl_certificate_key /etc/ssl/private/example.key;
proxy_ssl_server_name on;
[/SERVER_BLOCK]
Синтаксис: [SERVER_BLOCK:{порт}:{server_name}:{alias1}:{alias2}...]
- Поддерживает произвольное количество алиасов
- Несколько SERVER_BLOCK в одном поле — поддерживается
- Для Angie: SERVER_BLOCK создаёт отдельный server-блок с проксированием на контейнеры
- Для nginx: SERVER_BLOCK создаёт отдельный server-блок с проксированием на origin
Метод балансировки
Поле balancing_method в apps_settings. Записывается без точки с запятой:
UPDATE apps_settings SET balancing_method = 'least_conn' WHERE l7resourceid = 12345;
Допустимые значения: least_conn, ip_hash, random, random two least_conn и др.
Применяется первой строкой в upstream-блоке для Angie, nginx и SW.
proxy_next_upstream
Поле proxy_next_upstream_codes в apps_settings. Записывается через запятую без http_ префикса:
-- Убрать 500 из списка (бэкенд вернул 500, Angie не должен делать retry)
UPDATE apps_settings SET proxy_next_upstream_codes = '502,503,504' WHERE l7resourceid = 12345;
Дефолт: 500,502,503,504. Итоговая строка в конфиге:
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
Запуск контейнеров
Ожидание освобождения портов
Перед запуском контейнера утилита проверяет занятость портов через ss -tuanl (все состояния включая TIME-WAIT и ESTAB). Порт считается занятым если:
- Есть
LISTENсокет - Есть
ESTABотdocker-proxy(старый контейнер ещё не остановился)
После остановки контейнера при пересоздании:
- Ожидаем закрытия ESTAB соединений Angie (до 30 сек)
- Ожидаем полного освобождения портов (до 180 сек)
- Запуск нового контейнера (до 3 попыток с паузой 3 сек)
Проверка после запуска
Если контейнер запущен менее 5 минут и порты не слушают — автоматическое пересоздание.
Константы и лимиты
Производительность
DefaultWorkerProcesses = 4 // Nginx worker processes
DefaultWorkerConnections = 64000 // Соединений на worker
worker_rlimit_nofile = 1048576 // Лимит файловых дескрипторов
Timeouts
ContainerDrainTimeout = 90 // Секунд для scale down / отключения
MigrationDrainTimeout = 3600 // Секунд для миграции (1 час)
APITimeout = 120 // Секунд для API запросов
CacheTTL = 600 // Секунд (10 минут) для sp_info
MaxLockAge = 15 мин // После этого lock считается устаревшим
Буферы (Angie и nginx)
proxy_buffers 8 64k;
proxy_buffer_size 64k;
proxy_busy_buffers_size 128k;
large_client_header_buffers 4 128k;
shm_size
По умолчанию 1gb. Управляется через поле shm_size в client_info.
Безопасность
Фильтрация IP адресов
WAF Networks - исключаются из origins (предотвращение routing loops)
Antibot Networks - используются для идентификации antibot трафика
SSL/TLS
Дефолтные настройки (Angie):
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
Кастомизация через custom_angie_ssl — полная замена дефолтов для Angie
Кастомизация через custom_sw_nginx_ssl — полная замена дефолтов для SW
mTLS
Автоматическая замена путей:
/etc/ssl/certs/ → /opt/ptaf/conf/main/
/etc/ssl/private/ → /opt/ptaf/conf/main/
Применяется во всех custom директивах, содержащих proxy_ssl_certificate
Lock-файл
При старте утилита:
- Читает PID из существующего lock-файла
- Если процесс не существует → удаляет устаревший lock и продолжает
- Если процесс работает > 15 минут → предупреждение и подсказка как убить
- При завершении → lock удаляется через
defer
Логирование
Структура логов nginx
/var/log/ptaf_nginx/{client-title}/ptaf-agent001/
├── {sid}_{domain}_access.log
└── {sid}_{domain}_error.log
Формат access лога (JSON)
{
"SID": 12345,
"server_name": "example.com",
"request_id": "abc123",
"app_name": "example.com",
"proxyed_to": "192.168.1.1:443",
"upstream_status": "200",
"response_status_code": 200,
"request_time": 0.056
}
Поле request_id берётся из заголовка X-Request-ID.
Уровни логирования программы
✓ — Успешная операция
○ — Информация (без изменений)
⚠ — Предупреждение
→ — Действие/переход
└─ — Детали операции
❌ — Критическая ошибка
ℹ — Информационное сообщение
Мониторинг и логирование
FluentBit интеграция
Активируется через поле fluent_bit_port в client_info:
environment:
- EVENT_SENDER_ENABLED=1
- EVENT_SENDER_HOST=172.17.0.1
- EVENT_SENDER_PORT={fluent_bit_port}
- EVENT_SENDER_TIMEOUT=3
Troubleshooting
Проблема: Контейнер не запускается
Проверить:
docker logs ptaf_{client}_{num}- Наличие конфигов в
/home/install/conf/ptaf-nginx/ - Занятость портов:
ss -tuanl | grep {port} - Права на
/var/log/ptaf_nginx/(должны быть 0777) - Зомби docker-proxy процессы:
ps aux | grep docker-proxy | grep -v grep
Проблема: Порты конфликтуют
Действия:
- Проверить
ss -tuanl | grep :{port}— все состояния сокетов - Проверить
cat /proc/net/tcp | grep -i "$(printf '%04X' {port})"— таблица ядра - Убедиться что
ip_local_reserved_ports=18000-23999установлен на хосте - Удалить
.ports.json— порты будут перевыделены при следующем запуске
Проблема: Lock-файл не удалился
Действия:
- Magos автоматически удалит lock если процесс уже не существует
- Если процесс завис:
kill {PID} && rm /var/lock/ptaf-main.lock - PID записан в lock-файле:
cat /var/lock/ptaf-main.lock
Проблема: Клиент не мигрирует
Проверить:
waf_instanceвclient_info- Записи в
instances_new - Наличие маркеров
/tmp/ptaf-drain-* - Логи программы на обоих хостах
Проблема: SW nginx не перезагружается
Проверить:
solidwall-nginx -t- Наличие симлинков в
sites-enabled/ - Синтаксис в
sites-available/*.conf
Расширение системы
Добавление нового клиента (PTAF)
INSERT INTO client_info (client_title, containers_count, waf_instance)
VALUES ('new-client', 2, 'instance-a');
INSERT INTO apps_settings (l7resourceid, client_title, waf_vendor, waf_enabled, mode)
VALUES (12345, 'new-client', 'ptaf', true, 'auto');
Добавление нового клиента (SW)
INSERT INTO client_info (client_title, containers_count, waf_instance)
VALUES ('new-sw-client', 0, 'sw-instance-a'); -- containers_count игнорируется для SW
INSERT INTO apps_settings (l7resourceid, client_title, waf_vendor, waf_enabled, mode)
VALUES (12346, 'new-sw-client', 'sw', true, 'auto');
Включение debug-режима для клиента
UPDATE client_info SET debug = true WHERE client_title = 'CLIENT001';
Задать индивидуальный Docker-образ для клиента
UPDATE client_info
SET
docker_image = 'ptaf-core-nginx-agent:release-4.3.1.431839-debian-bullseye-nginx-1.28.0',
docker_image_download = 'https://example.com/image.tar'
WHERE client_title = 'CLIENT001';
Увеличить shared memory для контейнера
UPDATE client_info SET shm_size = 8 WHERE client_title = 'CLIENT001';
Настроить балансировку
UPDATE apps_settings SET balancing_method = 'least_conn' WHERE l7resourceid = 12345;
Настроить ptaf_fallback
-- Пропускать трафик если WAF недоступен
UPDATE apps_settings SET ptaf_fallback_code = 'pass' WHERE l7resourceid = 12345;
-- Вернуть 503 вместо 418
UPDATE apps_settings SET ptaf_fallback_code = '503' WHERE l7resourceid = 12345;