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 }