226 lines
8.1 KiB
Go
Executable file
226 lines
8.1 KiB
Go
Executable file
package main
|
||
|
||
import (
|
||
"database/sql"
|
||
"fmt"
|
||
"log"
|
||
"net"
|
||
"os"
|
||
"strconv"
|
||
|
||
"github.com/joho/godotenv"
|
||
"github.com/lib/pq"
|
||
)
|
||
|
||
// Константы для graceful shutdown
|
||
const (
|
||
// Время ожидания перед удалением контейнера при масштабировании вниз (в секундах)
|
||
// Должно быть больше чем keepalive_timeout в nginx (обычно 60-75 секунд)
|
||
ContainerDrainTimeout = 90
|
||
|
||
// Время ожидания перед удалением контейнера при миграции между хостами (в секундах)
|
||
// Увеличенное время для безопасного переключения трафика на новый хост
|
||
MigrationDrainTimeout = 3600 // 1 час
|
||
)
|
||
|
||
// Константы диапазонов портов для Docker
|
||
const (
|
||
HTTPPortStart = 18000 // Начало диапазона HTTP портов
|
||
HTTPPortEnd = 20999 // Конец диапазона HTTP портов (3000 портов)
|
||
HTTPSPortStart = 21000 // Начало диапазона HTTPS портов
|
||
HTTPSPortEnd = 23999 // Конец диапазона HTTPS портов (3000 портов)
|
||
)
|
||
|
||
// Пути к файлам конфигурации
|
||
const (
|
||
EnvFilePath = "/etc/magos/magos.env"
|
||
)
|
||
|
||
// Константы конфигурации по умолчанию (используются как fallback если env не загружен)
|
||
const (
|
||
DefaultDBName = "waf_info"
|
||
DefaultDBPort = 5432
|
||
DefaultPrimaryDBHost = "10.100.10.8"
|
||
DefaultSecondaryDBHost = "10.100.13.5"
|
||
DefaultDockerImage = "ptaf-core-nginx-agent:release-4.1.6.409465"
|
||
DefaultDockerImageURL = ""
|
||
DefaultWorkerProcesses = 4
|
||
DefaultWorkerConnections = 64000
|
||
)
|
||
|
||
// Конфигурация приложения
|
||
type Config struct {
|
||
DBUser string
|
||
DBPassword string
|
||
DBName string
|
||
DBPort int
|
||
PrimaryDBHost string
|
||
SecondaryDBHost string
|
||
APIToken string
|
||
AntibotNetworks []string
|
||
WAFNetworks []string
|
||
WorkerProcesses int
|
||
WorkerConnections int
|
||
DockerImage string
|
||
DockerImageURL string // URL для скачивания tar-архива образа
|
||
}
|
||
|
||
// loadEnvFile загружает переменные окружения из файла /etc/magos/magos.env
|
||
func loadEnvFile() error {
|
||
if err := godotenv.Load(EnvFilePath); err != nil {
|
||
return fmt.Errorf("ошибка загрузки %s: %w", EnvFilePath, err)
|
||
}
|
||
log.Printf("✓ Загружен файл конфигурации: %s", EnvFilePath)
|
||
return nil
|
||
}
|
||
|
||
// getEnv возвращает значение переменной окружения или дефолтное значение
|
||
func getEnv(key, defaultValue string) string {
|
||
if value := os.Getenv(key); value != "" {
|
||
return value
|
||
}
|
||
return defaultValue
|
||
}
|
||
|
||
// getEnvInt возвращает int значение переменной окружения или дефолтное значение
|
||
func getEnvInt(key string, defaultValue int) int {
|
||
if value := os.Getenv(key); value != "" {
|
||
if intVal, err := strconv.Atoi(value); err == nil {
|
||
return intVal
|
||
}
|
||
log.Printf("⚠ Некорректное значение %s=%s, используется дефолт %d", key, value, defaultValue)
|
||
}
|
||
return defaultValue
|
||
}
|
||
|
||
// getDefaultConfig возвращает конфигурацию из env-файла или дефолтные значения
|
||
func getDefaultConfig() Config {
|
||
// Пытаемся загрузить env файл (не критично если не получится)
|
||
if err := loadEnvFile(); err != nil {
|
||
log.Printf("⚠ %v", err)
|
||
log.Printf(" Используются дефолтные значения для DB_USER, DB_PASSWORD, API_TOKEN")
|
||
}
|
||
|
||
return Config{
|
||
DBUser: getEnv("DB_USER", ""),
|
||
DBPassword: getEnv("DB_PASSWORD", ""),
|
||
DBName: getEnv("DB_NAME", DefaultDBName),
|
||
DBPort: getEnvInt("DB_PORT", DefaultDBPort),
|
||
PrimaryDBHost: getEnv("PRIMARY_DB_HOST", DefaultPrimaryDBHost),
|
||
SecondaryDBHost: getEnv("SECONDARY_DB_HOST", DefaultSecondaryDBHost),
|
||
APIToken: getEnv("API_TOKEN", ""),
|
||
AntibotNetworks: []string{
|
||
"185.66.85.0/24",
|
||
"185.66.86.0/24",
|
||
"185.35.5.0/24",
|
||
"185.35.6.0/24",
|
||
"212.67.26.0/24",
|
||
},
|
||
WAFNetworks: []string{
|
||
"109.238.89.0/24",
|
||
"89.20.63.0/24",
|
||
},
|
||
WorkerProcesses: getEnvInt("WORKER_PROCESSES", DefaultWorkerProcesses),
|
||
WorkerConnections: getEnvInt("WORKER_CONNECTIONS", DefaultWorkerConnections),
|
||
DockerImage: getEnv("DOCKER_IMAGE", DefaultDockerImage),
|
||
DockerImageURL: getEnv("DOCKER_IMAGE_URL", DefaultDockerImageURL),
|
||
}
|
||
}
|
||
|
||
// initConfig инициализирует конфигурацию, загружая IP списки из БД
|
||
func initConfig(db *sql.DB) (Config, error) {
|
||
// Читаем IP адреса и сети из таблицы ips
|
||
antibotNetworks, wafNetworks, err := loadIPsFromDB(db)
|
||
if err != nil {
|
||
log.Printf("⚠ Не удалось загрузить IP настройки из БД: %v", err)
|
||
log.Printf(" Используются значения по умолчанию")
|
||
return getDefaultConfig(), nil
|
||
}
|
||
|
||
// Загружаем env файл для получения credentials
|
||
if err := loadEnvFile(); err != nil {
|
||
log.Printf("⚠ %v", err)
|
||
log.Printf(" Используются дефолтные значения для DB_USER, DB_PASSWORD, API_TOKEN")
|
||
}
|
||
|
||
// Формируем Config с настройками из env + IP из БД
|
||
config := Config{
|
||
DBUser: getEnv("DB_USER", ""),
|
||
DBPassword: getEnv("DB_PASSWORD", ""),
|
||
DBName: getEnv("DB_NAME", DefaultDBName),
|
||
DBPort: getEnvInt("DB_PORT", DefaultDBPort),
|
||
PrimaryDBHost: getEnv("PRIMARY_DB_HOST", DefaultPrimaryDBHost),
|
||
SecondaryDBHost: getEnv("SECONDARY_DB_HOST", DefaultSecondaryDBHost),
|
||
APIToken: getEnv("API_TOKEN", ""),
|
||
DockerImage: getEnv("DOCKER_IMAGE", DefaultDockerImage),
|
||
DockerImageURL: getEnv("DOCKER_IMAGE_URL", DefaultDockerImageURL),
|
||
WorkerProcesses: getEnvInt("WORKER_PROCESSES", DefaultWorkerProcesses),
|
||
WorkerConnections: getEnvInt("WORKER_CONNECTIONS", DefaultWorkerConnections),
|
||
AntibotNetworks: antibotNetworks,
|
||
WAFNetworks: wafNetworks,
|
||
}
|
||
|
||
// Валидация критичных параметров
|
||
if config.DBUser == "" {
|
||
log.Printf("⚠ DB_USER не установлен в %s", EnvFilePath)
|
||
}
|
||
if config.DBPassword == "" {
|
||
log.Printf("⚠ DB_PASSWORD не установлен в %s", EnvFilePath)
|
||
}
|
||
if config.APIToken == "" {
|
||
log.Printf("⚠ API_TOKEN не установлен в %s", EnvFilePath)
|
||
}
|
||
|
||
log.Printf("✓ IP конфигурация загружена из таблицы ips")
|
||
log.Printf(" - Antibot Networks: %d сетей", len(config.AntibotNetworks))
|
||
log.Printf(" - WAF Networks: %d сетей", len(config.WAFNetworks))
|
||
|
||
return config, nil
|
||
}
|
||
|
||
// loadIPsFromDB загружает списки IP адресов и сетей из таблицы ips
|
||
func loadIPsFromDB(db *sql.DB) (antibotNetworks []string, wafNetworks []string, err error) {
|
||
query := `
|
||
SELECT antibot_networks, waf_networks
|
||
FROM ips
|
||
WHERE id = 1
|
||
`
|
||
|
||
// PostgreSQL массивы читаем через pq.Array
|
||
var antibotNetworksArray, wafNetworksArray []string
|
||
|
||
err = db.QueryRow(query).Scan(
|
||
pq.Array(&antibotNetworksArray),
|
||
pq.Array(&wafNetworksArray),
|
||
)
|
||
|
||
if err != nil {
|
||
if err == sql.ErrNoRows {
|
||
return nil, nil, fmt.Errorf("записи с id=1 не найдено в таблице ips")
|
||
}
|
||
return nil, nil, fmt.Errorf("ошибка чтения из таблицы ips: %w", err)
|
||
}
|
||
|
||
return antibotNetworksArray, wafNetworksArray, nil
|
||
}
|
||
|
||
// isIPInWAFNetworks проверяет, входит ли IP адрес в одну из WAF подсетей
|
||
func isIPInWAFNetworks(ipStr string, wafNetworks []string) bool {
|
||
ip := net.ParseIP(ipStr)
|
||
if ip == nil {
|
||
return false
|
||
}
|
||
|
||
for _, cidr := range wafNetworks {
|
||
_, ipNet, err := net.ParseCIDR(cidr)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
|
||
if ipNet.Contains(ip) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|