magos/DATABASE_SCHEMA.md
2026-02-24 12:08:59 +03:00

24 KiB
Raw Blame History

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 сетей

Подключение к БД

Параметры подключения

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

SELECT instance 
FROM instances 
WHERE hostname = $1;

Функция: getInstanceByHostname(hostname string)

Использование: При старте программы для определения какой это инстанс.

Пример:

hostname := "host01.example.com"
instance, err := getInstanceByHostname(db, hostname)
// instance = "waf-prod-01"

2. Проверка принадлежности хоста к инстансу

SELECT COUNT(*) 
FROM instances 
WHERE hostname = $1 AND instance = $2;

Функция: checkHostBelongsToInstance(hostname, instance string)

Использование: Валидация что хост принадлежит данному инстансу.

Пример:

hostname := "host01.example.com"
instance := "waf-prod-01"
err := checkHostBelongsToInstance(db, hostname, instance)
// err == nil если хост принадлежит инстансу

Примеры данных

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

SELECT 
    containers_count, 
    ptaf_config, 
    client_title, 
    fluent_bit_port, 
    waf_instance
FROM client_info
WHERE waf_instance = $1;

Функция: getClientInfoByInstance(instance string)

Использование: Получение всех клиентов для данного инстанса.

Пример:

instance := "waf-prod-01"
clients, err := getClientInfoByInstance(db, instance)
// clients = [{ContainersCount: 3, ClientTitle: "CLIENT001", ...}, ...]

2. Получение клиента по client_title

SELECT 
    containers_count, 
    ptaf_config, 
    client_title, 
    fluent_bit_port, 
    waf_instance
FROM client_info
WHERE client_title = $1;

Функция: getClientInfoByClientTitle(clientTitle string)

Использование: Получение информации о конкретном клиенте.


Примеры данных

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)

Пример изменения:

-- Увеличить до 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

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)

Использование: Получение всех настроек приложений для клиента.


Примеры данных

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 значение:

upstream backend {
    server 127.0.0.1:21000;
    
    # Default upstream directives
    keepalive 60;
    keepalive_timeout 70s;
}

НЕ NULL значение:

upstream backend {
    server 127.0.0.1:21000;
    
    # Custom upstream directives (replace defaults)
    keepalive 200;
    keepalive_timeout 90s;
    least_conn;
}

Пример 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 значение:

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 значение:

server {
    listen 443 ssl;
    
    # Custom directives (replace defaults)
    keepalive_timeout 120s;
    client_max_body_size 100m;
    # Нужно указать ВСЕ директивы, включая буферы!
}

Пример 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:

-- Создать полностью кастомный 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:

-- Добавить 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:

-- Отключить 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)

Пример:

-- Приложение слушает на портах 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;

Результат:

# 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

Используемые запросы

SELECT antibot_networks, waf_networks
FROM ips;

Функция: loadIPsFromDB()

Использование: При старте программы для загрузки IP сетей.

Пример данных:

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}'
);

Использование в конфигах:

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()

Пример ответа:

{
  "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"
        }
      ]
    }
  }
}

Использование в конфигах:

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

Пример ответа:

{
  "data": {
    "result": {
      "items": [
        {
          "id": 1,
          "alias": "www.example.com"
        },
        {
          "id": 2,
          "alias": "app.example.com"
        }
      ]
    }
  }
}

Использование в конфигах:

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. Генерируем конфиги на основе всех данных

Примеры типичных запросов

Получить все ресурсы клиента

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;

Изменить количество контейнеров

-- Увеличить до 5
UPDATE client_info
SET containers_count = 5
WHERE client_title = 'CLIENT001';

-- Проверка
SELECT client_title, containers_count 
FROM client_info 
WHERE client_title = 'CLIENT001';

Добавить кастомную балансировку

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

UPDATE apps_settings
SET sni = 1
WHERE l7resourceid = 12323;

-- Проверка
SELECT l7resourceid, sni 
FROM apps_settings 
WHERE sni = 1;

Добавить дополнительные порты

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;

Индексы (рекомендуемые)

-- Для быстрого поиска по 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);

Миграции и изменения схемы

Добавление нового кастомного поля

-- Добавить новое поле для header манипуляций
ALTER TABLE apps_settings 
ADD COLUMN headers_custom TEXT DEFAULT NULL;

-- Обновить код для чтения этого поля

Изменение типа поля

-- Изменить тип sni на boolean (если сейчас integer)
ALTER TABLE apps_settings 
ALTER COLUMN sni TYPE BOOLEAN 
USING (sni::integer = 0);

Best Practices

1. NULL vs пустая строка

Правильно:

UPDATE apps_settings
SET upstream_angie_custom = NULL  -- NULL для дефолтов
WHERE l7resourceid = 12323;

Неправильно:

UPDATE apps_settings
SET upstream_angie_custom = ''  -- Пустая строка может вызвать ошибки
WHERE l7resourceid = 12323;

2. Массивы портов

Правильно:

UPDATE apps_settings
SET custom_input_https_ports = '{443, 8443}'  -- PostgreSQL array
WHERE l7resourceid = 12323;

Неправильно:

UPDATE apps_settings
SET custom_input_https_ports = '443,8443'  -- Строка, не массив
WHERE l7resourceid = 12323;

3. Многострочные кастомные директивы

Правильно:

UPDATE apps_settings
SET server_directives_angie_custom = 'keepalive_timeout 80s;
send_timeout 60s;
proxy_buffers 4 32k;'
WHERE l7resourceid = 12323;

Также правильно (с экранированием):

UPDATE apps_settings
SET server_directives_angie_custom = E'keepalive_timeout 80s;\nsend_timeout 60s;'
WHERE l7resourceid = 12323;

Мониторинг БД

Проверка соединения

-- Проверить активные соединения
SELECT 
    datname, 
    usename, 
    client_addr, 
    state, 
    query_start 
FROM pg_stat_activity 
WHERE datname = 'waf_info';

Проверка данных

-- Количество клиентов на каждом 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;

Резервное копирование

# Дамп всей БД
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.instanceclient_info.waf_instance
  • client_info.client_titleapps_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 = кастом