# Magos - Схема базы данных --- ## Обзор Magos работает с PostgreSQL базой данных `waf_info`, которая содержит конфигурации WAF платформы. Программа использует 5 основных таблиц для получения информации о хостах, клиентах, приложениях и backend серверах. ### Используемые таблицы 1. **instances** - информация о хостах/серверах 2. **client_info** - данные клиентов и контейнеров 3. **apps_settings** - настройки приложений и кастомные директивы 4. **origins** - backend серверы (получаются через REST API) 5. **aliases** - дополнительные домены (получаются через REST API) 6. **ips** - IP адреса для WAF и antibot сетей --- ## Подключение к БД ### Параметры подключения ```bash DB_USER=login DB_PASSWORD=password DB_NAME=waf_info DB_PORT=5432 PRIMARY_DB_HOST=10.10.10.8 SECONDARY_DB_HOST=10.10.10.5 ``` ### Failover логика 1. Попытка подключения к Primary DB (10.10.10.8) 2. Если неудача → подключение к Secondary DB (10.10.10.5) 3. Если обе недоступны → программа останавливается **Функция:** `connectToDB()` --- ## Таблица: instances ### Назначение Хранит информацию о хостах/серверах в кластере PTAF. ### Структура | Поле | Тип | Описание | Пример | |------|-----|----------|--------| | `hostname` | TEXT | Hostname сервера | `host01.example.com` | | `instance` | TEXT | Идентификатор инстанса | `waf-prod-01` | ### Используемые запросы #### 1. Получение instance по hostname ```sql SELECT instance FROM instances WHERE hostname = $1; ``` **Функция:** `getInstanceByHostname(hostname string)` **Использование:** При старте программы для определения какой это инстанс. **Пример:** ```go hostname := "host01.example.com" instance, err := getInstanceByHostname(db, hostname) // instance = "waf-prod-01" ``` --- #### 2. Проверка принадлежности хоста к инстансу ```sql SELECT COUNT(*) FROM instances WHERE hostname = $1 AND instance = $2; ``` **Функция:** `checkHostBelongsToInstance(hostname, instance string)` **Использование:** Валидация что хост принадлежит данному инстансу. **Пример:** ```go hostname := "host01.example.com" instance := "waf-prod-01" err := checkHostBelongsToInstance(db, hostname, instance) // err == nil если хост принадлежит инстансу ``` --- ### Примеры данных ```sql INSERT INTO instances (hostname, instance) VALUES ('host01.example.com', 'waf-prod-01'), ('host02.example.com', 'waf-prod-01'), ('host03.example.com', 'waf-prod-02'); ``` --- ## Таблица: client_info ### Назначение Хранит информацию о клиентах: количество контейнеров, привязка к инстансу. ### Структура | Поле | Тип | Описание | Пример | |------|-----|----------|--------| | `client_title` | TEXT | Идентификатор клиента | `CLIENT001` | | `containers_count` | INTEGER | Количество контейнеров | `3` | | `ptaf_config` | TEXT | Конфигурация PTAF (JSON) | `{"key": "value"}` | | `fluent_bit_port` | INTEGER | Порт Fluent Bit | `24224` | | `waf_instance` | TEXT | Instance для apps_settings | `waf-prod-01` | ### Используемые запросы #### 1. Получение клиентов по instance ```sql SELECT containers_count, ptaf_config, client_title, fluent_bit_port, waf_instance FROM client_info WHERE waf_instance = $1; ``` **Функция:** `getClientInfoByInstance(instance string)` **Использование:** Получение всех клиентов для данного инстанса. **Пример:** ```go instance := "waf-prod-01" clients, err := getClientInfoByInstance(db, instance) // clients = [{ContainersCount: 3, ClientTitle: "CLIENT001", ...}, ...] ``` --- #### 2. Получение клиента по client_title ```sql SELECT containers_count, ptaf_config, client_title, fluent_bit_port, waf_instance FROM client_info WHERE client_title = $1; ``` **Функция:** `getClientInfoByClientTitle(clientTitle string)` **Использование:** Получение информации о конкретном клиенте. --- ### Примеры данных ```sql INSERT INTO client_info (client_title, containers_count, ptaf_config, fluent_bit_port, waf_instance) VALUES ('CLIENT001', 3, '{"version": "1.0"}', 24224, 'waf-prod-01'), ('CLIENT002', 5, '{"version": "1.0"}', 24225, 'waf-prod-01'), ('CLIENT003', 2, '{"version": "1.0"}', 24226, 'waf-prod-02'); ``` --- ### Ключевое поле: containers_count **Назначение:** Определяет сколько Docker контейнеров нужно создать для клиента. **Логика:** - `containers_count = 3` → создать ptaf_client_001, ptaf_client_002, ptaf_client_003 - При увеличении → создаются новые контейнеры - При уменьшении → лишние контейнеры помечаются на graceful shutdown (90s) **Пример изменения:** ```sql -- Увеличить до 5 контейнеров UPDATE client_info SET containers_count = 5 WHERE client_title = 'CLIENT001'; -- Manager автоматически создаст ptaf_client_004 и ptaf_client_005 ``` --- ## Таблица: apps_settings ### Назначение Основная таблица с настройками приложений и кастомными директивами Nginx/Angie. ### Структура | Поле | Тип | Описание | Значение по умолчанию | |------|-----|----------|----------------------| | `l7resourceid` | INTEGER | ID ресурса | Первичный ключ | | `client_title` | TEXT | Клиент (FK) | `CLIENT001` | | `waf_enabled` | BOOLEAN | WAF включен | `true` | | `sni` | INTEGER | SNI режим (0=keepalive on, 1=keepalive off) | `0` | | `custom_input_http_ports` | INTEGER[] | HTTP порты для прослушивания | `{80, 8080}` | | `custom_input_https_ports` | INTEGER[] | HTTPS порты для прослушивания | `{443, 8443}` | | `custom_output_http_ports` | INTEGER[] | HTTP порты к backend | `{80}` | | `custom_output_https_ports` | INTEGER[] | HTTPS порты к backend | `{443}` | | `upstream_angie_custom` | TEXT | Кастомные upstream директивы (Angie) | `NULL` | | `upstream_nginx_custom` | TEXT | Кастомные upstream директивы (Nginx) | `NULL` | | `server_angie_custom` | TEXT | Полная замена server блока (Angie) | `NULL` | | `server_nginx_custom` | TEXT | Полная замена server блока (Nginx) | `NULL` | | `server_directives_angie_custom` | TEXT | Кастомные server директивы (Angie) | `NULL` | | `server_directives_nginx_custom` | TEXT | Кастомные server директивы (Nginx) | `NULL` | | `location_angie_custom` | TEXT | Кастомные location блоки (Angie) | `NULL` | | `location_nginx_custom` | TEXT | Кастомные location блоки (Nginx) | `NULL` | --- ### Используемые запросы #### Получение настроек по client_title ```sql SELECT l7resourceid, waf_enabled, custom_input_http_ports, custom_input_https_ports, custom_output_http_ports, custom_output_https_ports, upstream_angie_custom, server_nginx_custom, server_angie_custom, client_title, upstream_nginx_custom, location_angie_custom, location_nginx_custom, server_directives_angie_custom, server_directives_nginx_custom, sni FROM apps_settings WHERE client_title = $1 ORDER BY l7resourceid ASC; ``` **Функция:** `getAppsSettingsByClientTitle(clientTitle string)` **Использование:** Получение всех настроек приложений для клиента. --- ### Примеры данных ```sql INSERT INTO apps_settings (l7resourceid, client_title, waf_enabled, sni, custom_input_http_ports, custom_input_https_ports, custom_output_http_ports, custom_output_https_ports) VALUES (12323, 'CLIENT001', true, 0, '{80}', '{443}', '{80}', '{443}'), (12324, 'CLIENT001', true, 0, '{80,8080}', '{443,8443}', '{80}', '{443}'), (15000, 'CLIENT002', true, 0, '{80}', '{443}', '{80}', '{443}'); ``` --- ### Кастомные поля (подробно) #### upstream_angie_custom / upstream_nginx_custom **Режим:** REPLACE (полная замена keepalive директив) **NULL значение:** ```nginx upstream backend { server 127.0.0.1:21000; # Default upstream directives keepalive 60; keepalive_timeout 70s; } ``` **НЕ NULL значение:** ```nginx upstream backend { server 127.0.0.1:21000; # Custom upstream directives (replace defaults) keepalive 200; keepalive_timeout 90s; least_conn; } ``` **Пример SQL:** ```sql -- Изменить балансировку на least_conn UPDATE apps_settings SET upstream_angie_custom = 'keepalive 60; keepalive_timeout 70s; least_conn;' WHERE l7resourceid = 12323; -- Вернуть дефолты UPDATE apps_settings SET upstream_angie_custom = NULL WHERE l7resourceid = 12323; ``` --- #### server_directives_angie_custom / server_directives_nginx_custom **Режим:** REPLACE (замена else блока в server) **NULL значение:** ```nginx server { listen 443 ssl; # Default directives keepalive_timeout 80s; send_timeout 60s; proxy_connect_timeout 60s; proxy_read_timeout 120s; proxy_send_timeout 120s; large_client_header_buffers 4 128k; proxy_buffers 4 32k; proxy_buffer_size 16k; proxy_busy_buffers_size 32k; } ``` **НЕ NULL значение:** ```nginx server { listen 443 ssl; # Custom directives (replace defaults) keepalive_timeout 120s; client_max_body_size 100m; # Нужно указать ВСЕ директивы, включая буферы! } ``` **Пример SQL:** ```sql -- Увеличить таймауты UPDATE apps_settings SET server_directives_angie_custom = 'keepalive_timeout 120s; send_timeout 90s; proxy_connect_timeout 90s; proxy_read_timeout 180s; proxy_send_timeout 180s; large_client_header_buffers 4 128k; proxy_buffers 4 32k; proxy_buffer_size 16k;' WHERE l7resourceid = 12323; ``` --- #### server_angie_custom / server_nginx_custom **Режим:** SERVER_BLOCK (полная замена всего server блока) **NULL значение:** Используется стандартный шаблон server блока **НЕ NULL значение:** Полная замена server блока **Пример SQL:** ```sql -- Создать полностью кастомный server блок UPDATE apps_settings SET server_angie_custom = 'server { listen 9999; server_name special.example.com; location / { return 200 "Custom response"; } }' WHERE l7resourceid = 12323; ``` --- #### location_angie_custom / location_nginx_custom **Режим:** APPEND (добавление к дефолтному location) **NULL значение:** Только стандартный `location / { proxy_pass }` **НЕ NULL значение:** Добавляются дополнительные location блоки **Пример SQL:** ```sql -- Добавить location для API UPDATE apps_settings SET location_nginx_custom = 'location /api { proxy_pass http://api.backend.com; proxy_set_header X-API-Key secret; } location /static { alias /var/www/static; }' WHERE l7resourceid = 12323; ``` --- #### sni (Server Name Indication) **Значения:** - `0` - Keepalive ВКЛЮЧЕН (дефолт) - `1` - Keepalive ОТКЛЮЧЕН **Когда sni = 1:** - Директивы `keepalive` и `keepalive_timeout` НЕ добавляются в upstream - Используется для SNI passthrough (прямая передача без терминации SSL) **Пример SQL:** ```sql -- Отключить keepalive для SNI passthrough UPDATE apps_settings SET sni = 1 WHERE l7resourceid = 12323; ``` --- #### custom_input_*_ports и custom_output_*_ports **custom_input_http_ports / custom_input_https_ports:** - Порты на которых Angie и Nginx контейнеры будут слушать - Angie слушает на этих портах (80, 443, 8080, 8443) - Nginx контейнеры слушают на этих же портах **custom_output_http_ports / custom_output_https_ports:** - Порты к которым Nginx контейнеры будут проксировать на backend - Обычно {80} для HTTP и {443} для HTTPS - Могут быть нестандартные порты (8080, 8443) **Пример:** ```sql -- Приложение слушает на портах 80, 8080 (HTTP) и 443, 8443 (HTTPS) -- Backend работает на порту 443 UPDATE apps_settings SET custom_input_http_ports = '{80, 8080}', custom_input_https_ports = '{443, 8443}', custom_output_http_ports = '{80}', custom_output_https_ports = '{443}' WHERE l7resourceid = 12323; ``` **Результат:** ```nginx # Angie server { listen 80; # custom_input_http_ports[0] listen 8080; # custom_input_http_ports[1] listen 443 ssl; # custom_input_https_ports[0] listen 8443 ssl; # custom_input_https_ports[1] } # Nginx upstream upstream backend { server 185.22.65.148:443; # custom_output_https_ports[0] } ``` --- ## Таблица: ips ### Назначение Хранит IP адреса для WAF и antibot сетей (белые списки). ### Структура | Поле | Тип | Описание | |------|-----|----------| | `antibot_networks` | TEXT[] | IP сети для antibot | | `waf_networks` | TEXT[] | IP сети для WAF | ### Используемые запросы ```sql SELECT antibot_networks, waf_networks FROM ips; ``` **Функция:** `loadIPsFromDB()` **Использование:** При старте программы для загрузки IP сетей. **Пример данных:** ```sql INSERT INTO ips (antibot_networks, waf_networks) VALUES ( '{172.16.0.0/12, 10.0.0.0/8}', '{172.16.0.0/12, 10.100.0.0/16}' ); ``` **Использование в конфигах:** ```nginx server { # Доверенные IP для получения реального IP клиента set_real_ip_from 172.16.0.0/12; set_real_ip_from 10.100.0.0/16; real_ip_header X-Forwarded-For; } ``` --- ## REST API (origins и aliases) ### Origins (Backend серверы) **Не хранятся в БД**, получаются через REST API. **Эндпоинт:** `/api/waf-proxy/get-apps-and-resourses` **Функция:** `makeAPIRequest()`, `getResourceDataFromCache()` **Пример ответа:** ```json { "data": { "result": { "items": [ { "id": 1, "ip": "185.22.65.148", "weight": 100, "mode": "active", "comment": "Primary backend" }, { "id": 2, "ip": "185.22.65.149", "weight": 50, "mode": "backup", "comment": "Backup backend" } ] } } } ``` **Использование в конфигах:** ```nginx upstream backend { server 185.22.65.148:443 weight=100; server 185.22.65.149:443 weight=50 backup; } ``` --- ### Aliases (Дополнительные домены) **Не хранятся в БД**, получаются через REST API. **Эндпоинт:** `/api/waf-proxy/get-apps-and-resourses` **Пример ответа:** ```json { "data": { "result": { "items": [ { "id": 1, "alias": "www.example.com" }, { "id": 2, "alias": "app.example.com" } ] } } } ``` **Использование в конфигах:** ```nginx server { listen 443 ssl; server_name example.com www.example.com app.example.com; } ``` --- ## Связи между таблицами ``` instances ↓ (hostname → instance) client_info (waf_instance) ↓ (client_title) apps_settings (client_title) ↓ (l7resourceid через API) Origins (от REST API) Aliases (от REST API) ``` **Поток данных:** 1. Program запускается на хосте с hostname = `host01.example.com` 2. Запрос к `instances` → получаем `instance = "waf-prod-01"` 3. Запрос к `client_info` WHERE `waf_instance = "waf-prod-01"` → получаем клиентов 4. Для каждого клиента запрос к `apps_settings` WHERE `client_title = "CLIENT001"` 5. Для каждого ресурса API запрос → получаем origins и aliases 6. Генерируем конфиги на основе всех данных --- ## Примеры типичных запросов ### Получить все ресурсы клиента ```sql SELECT a.l7resourceid, a.custom_input_https_ports, a.upstream_angie_custom, c.containers_count FROM apps_settings a JOIN client_info c ON a.client_title = c.client_title WHERE a.client_title = 'CLIENT001' ORDER BY a.l7resourceid; ``` --- ### Изменить количество контейнеров ```sql -- Увеличить до 5 UPDATE client_info SET containers_count = 5 WHERE client_title = 'CLIENT001'; -- Проверка SELECT client_title, containers_count FROM client_info WHERE client_title = 'CLIENT001'; ``` --- ### Добавить кастомную балансировку ```sql UPDATE apps_settings SET upstream_nginx_custom = 'keepalive 60; keepalive_timeout 70s; least_conn;' WHERE l7resourceid = 12323; -- Проверка SELECT l7resourceid, upstream_nginx_custom FROM apps_settings WHERE l7resourceid = 12323; ``` --- ### Включить SNI passthrough ```sql UPDATE apps_settings SET sni = 1 WHERE l7resourceid = 12323; -- Проверка SELECT l7resourceid, sni FROM apps_settings WHERE sni = 1; ``` --- ### Добавить дополнительные порты ```sql UPDATE apps_settings SET custom_input_https_ports = '{443, 8443, 9443}', custom_output_https_ports = '{443, 8443, 9443}' WHERE l7resourceid = 12323; -- Проверка SELECT l7resourceid, custom_input_https_ports, custom_output_https_ports FROM apps_settings WHERE l7resourceid = 12323; ``` --- ## Индексы (рекомендуемые) ```sql -- Для быстрого поиска по hostname CREATE INDEX idx_instances_hostname ON instances(hostname); -- Для быстрого поиска клиентов по instance CREATE INDEX idx_client_info_waf_instance ON client_info(waf_instance); CREATE INDEX idx_client_info_client_title ON client_info(client_title); -- Для быстрого поиска настроек по client_title CREATE INDEX idx_apps_settings_client_title ON apps_settings(client_title); CREATE INDEX idx_apps_settings_l7resourceid ON apps_settings(l7resourceid); ``` --- ## Миграции и изменения схемы ### Добавление нового кастомного поля ```sql -- Добавить новое поле для header манипуляций ALTER TABLE apps_settings ADD COLUMN headers_custom TEXT DEFAULT NULL; -- Обновить код для чтения этого поля ``` --- ### Изменение типа поля ```sql -- Изменить тип sni на boolean (если сейчас integer) ALTER TABLE apps_settings ALTER COLUMN sni TYPE BOOLEAN USING (sni::integer = 0); ``` --- ## Best Practices ### 1. NULL vs пустая строка **Правильно:** ```sql UPDATE apps_settings SET upstream_angie_custom = NULL -- NULL для дефолтов WHERE l7resourceid = 12323; ``` **Неправильно:** ```sql UPDATE apps_settings SET upstream_angie_custom = '' -- Пустая строка может вызвать ошибки WHERE l7resourceid = 12323; ``` --- ### 2. Массивы портов **Правильно:** ```sql UPDATE apps_settings SET custom_input_https_ports = '{443, 8443}' -- PostgreSQL array WHERE l7resourceid = 12323; ``` **Неправильно:** ```sql UPDATE apps_settings SET custom_input_https_ports = '443,8443' -- Строка, не массив WHERE l7resourceid = 12323; ``` --- ### 3. Многострочные кастомные директивы **Правильно:** ```sql UPDATE apps_settings SET server_directives_angie_custom = 'keepalive_timeout 80s; send_timeout 60s; proxy_buffers 4 32k;' WHERE l7resourceid = 12323; ``` **Также правильно (с экранированием):** ```sql UPDATE apps_settings SET server_directives_angie_custom = E'keepalive_timeout 80s;\nsend_timeout 60s;' WHERE l7resourceid = 12323; ``` --- ## Мониторинг БД ### Проверка соединения ```sql -- Проверить активные соединения SELECT datname, usename, client_addr, state, query_start FROM pg_stat_activity WHERE datname = 'waf_info'; ``` --- ### Проверка данных ```sql -- Количество клиентов на каждом instance SELECT waf_instance, COUNT(*) as clients_count FROM client_info GROUP BY waf_instance; -- Количество ресурсов на клиента SELECT client_title, COUNT(*) as resources_count FROM apps_settings GROUP BY client_title; -- Клиенты с кастомными upstream директивами SELECT client_title, l7resourceid FROM apps_settings WHERE upstream_angie_custom IS NOT NULL OR upstream_nginx_custom IS NOT NULL; ``` --- ## Резервное копирование ```bash # Дамп всей БД pg_dump -h 10.100.10.8 -U install -d waf_info > waf_info_backup.sql # Дамп только структуры pg_dump -h 10.100.10.8 -U install -d waf_info --schema-only > schema.sql # Дамп только данных pg_dump -h 10.100.10.8 -U install -d waf_info --data-only > data.sql # Восстановление psql -h 10.100.10.8 -U install -d waf_info < waf_info_backup.sql ``` --- ## Итого ### Основные таблицы | Таблица | Записей (примерно) | Частота изменений | |---------|-------------------|-------------------| | instances | 10-50 | Редко (при добавлении хостов) | | client_info | 50-500 | Редко (при добавлении клиентов) | | apps_settings | 500-5000 | Часто (изменение настроек) | | ips | 1 | Очень редко | ### Ключевые связи - `instances.instance` → `client_info.waf_instance` - `client_info.client_title` → `apps_settings.client_title` - `apps_settings.l7resourceid` → REST API (origins, aliases) ### Кастомизация Все кастомные поля в `apps_settings`: - 8 полей для кастомных директив - 4 режима: REPLACE (upstream, server_directives), SERVER_BLOCK, APPEND (location) - NULL = дефолты, NOT NULL = кастом