package main import ( "database/sql" "encoding/json" "fmt" "log" "strings" "time" ) /* DATABASE */ func dbConnect() (*sql.DB, error) { dsn := fmt.Sprintf( "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", dbHost, dbPort, dbUser, dbPassword, dbName, ) return sql.Open("postgres", dsn) } func loadClients(db *sql.DB) ([]string, error) { rows, err := db.Query(`SELECT client_title FROM client_info`) if err != nil { return nil, err } defer rows.Close() var result []string for rows.Next() { var c string if err := rows.Scan(&c); err != nil { return nil, err } result = append(result, c) } return result, nil } func loadL7IDs(db *sql.DB, client string) ([]int64, error) { rows, err := db.Query( `SELECT l7resourceid FROM apps_settings WHERE client_title = $1 AND mode = 'auto'`, client, ) if err != nil { return nil, err } defer rows.Close() var ids []int64 for rows.Next() { var id int64 if err := rows.Scan(&id); err != nil { return nil, err } ids = append(ids, id) } return ids, nil } func processSID(db *sql.DB, sid int64) error { // WHOIS var whois whoisResponse if err := apiGet(whoisURL+fmt.Sprint(sid)+"/global", &whois); err != nil { return err } // ORIGINS var originsResp apiList[originItem] if err := apiGet(originURL+fmt.Sprint(sid), &originsResp); err != nil { return err } // ALIASES var aliasResp apiList[aliasItem] if err := apiGet(aliasURL+fmt.Sprint(sid), &aliasResp); err != nil { return err } originsJSON, _ := json.Marshal(originsResp.Data.Result.Items) var aliases []string var latestModifiedAt int64 = 0 for _, a := range aliasResp.Data.Result.Items { aliases = append(aliases, a.Domain) if a.ModifiedAt > latestModifiedAt { latestModifiedAt = a.ModifiedAt } } aliasesJSON, _ := json.Marshal(aliases) // Проверяем AntiDDOS antiddosEnabled := false protectedIP := whois.Data.Result.ProtectedIp var resolvedIP string if protectedIP != "" && whois.Data.Result.L7ResourceName != "" { // Резолвим домен resolvedIPAddr, err := resolveDomain(whois.Data.Result.L7ResourceName) if err != nil { log.Printf("[AntiDDOS Check] Failed to resolve %s: %v", whois.Data.Result.L7ResourceName, err) } else { resolvedIP = resolvedIPAddr log.Printf("[AntiDDOS Check] Domain: %s, Resolved: %s, Protected: %s", whois.Data.Result.L7ResourceName, resolvedIP, protectedIP) if resolvedIP == protectedIP { antiddosEnabled = true log.Printf("[AntiDDOS Check] ✅ AntiDDOS is ENABLED for %s", whois.Data.Result.L7ResourceName) } else { log.Printf("[AntiDDOS Check] ❌ AntiDDOS is DISABLED for %s", whois.Data.Result.L7ResourceName) } } } // Извлекаем WAF данные из API wafEnabledSP := whois.Data.Result.WafEnabled wafVendor := whois.Data.Result.WafProvider instanceSP := whois.Data.Result.WafInstance log.Printf("[WAF Info] SID: %d, WAF Enabled: %d, Vendor: %s, Instance: %s", sid, wafEnabledSP, wafVendor, instanceSP) return upsertSPInfo( db, sid, whois.Data.Result.L7ResourceName, originsResp.Data.Result.Items, aliases, originsJSON, aliasesJSON, latestModifiedAt, protectedIP, resolvedIP, antiddosEnabled, wafEnabledSP, wafVendor, instanceSP, ) } func upsertSPInfo( db *sql.DB, sid int64, domain string, newOrigins []originItem, newAliases []string, originsJSON []byte, aliasesJSON []byte, aliasesModifiedAt int64, protectedIP string, resolvedIP string, antiddosEnabled bool, wafEnabledSP int, wafVendor string, instanceSP string, ) error { // Проверяем существующие данные var existingDomain string var existingOriginsJSON, existingAliasesJSON []byte var existingWafEnabled sql.NullInt64 var existingWafVendor, existingInstanceSP sql.NullString err := db.QueryRow(` SELECT domain_name, origins, aliases, waf_enabled_sp, waf_vendor, instance_sp FROM sp_info WHERE sid = $1 `, sid).Scan(&existingDomain, &existingOriginsJSON, &existingAliasesJSON, &existingWafEnabled, &existingWafVendor, &existingInstanceSP) isNewRecord := err == sql.ErrNoRows if err != nil && !isNewRecord { return err } hasChanges := false var changeDetails []string needsPTAFUpdate := false hasWAFChanges := false var wafChangeDetails []string if !isNewRecord { var existingOrigins []originItem var existingAliases []string json.Unmarshal(existingOriginsJSON, &existingOrigins) json.Unmarshal(existingAliasesJSON, &existingAliases) if existingDomain != domain { hasChanges = true needsPTAFUpdate = true changeDetails = append(changeDetails, fmt.Sprintf("🔄 Домен изменён:\n Было: %s\n Стало: %s", existingDomain, domain)) } originChanges := compareOrigins(existingOrigins, newOrigins) if originChanges != "" { hasChanges = true changeDetails = append(changeDetails, originChanges) } aliasChanges, aliasesChanged := compareAliases(existingAliases, newAliases) if aliasesChanged { hasChanges = true needsPTAFUpdate = true changeDetails = append(changeDetails, aliasChanges) } // Проверяем изменения WAF настроек oldWafEnabled := 0 if existingWafEnabled.Valid { oldWafEnabled = int(existingWafEnabled.Int64) } oldWafVendor := "" if existingWafVendor.Valid { oldWafVendor = existingWafVendor.String } oldInstanceSP := "" if existingInstanceSP.Valid { oldInstanceSP = existingInstanceSP.String } if oldWafEnabled != wafEnabledSP { hasWAFChanges = true wafChangeDetails = append(wafChangeDetails, fmt.Sprintf("WAF Enabled:\n Было: %d\n Стало: %d", oldWafEnabled, wafEnabledSP)) } if oldWafVendor != wafVendor { hasWAFChanges = true oldDisplay := oldWafVendor if oldDisplay == "" { oldDisplay = "(не указан)" } newDisplay := wafVendor if newDisplay == "" { newDisplay = "(не указан)" } wafChangeDetails = append(wafChangeDetails, fmt.Sprintf("WAF Provider:\n Было: %s\n Стало: %s", oldDisplay, newDisplay)) } if oldInstanceSP != instanceSP { hasWAFChanges = true oldDisplay := oldInstanceSP if oldDisplay == "" { oldDisplay = "(не указан)" } newDisplay := instanceSP if newDisplay == "" { newDisplay = "(не указан)" } wafChangeDetails = append(wafChangeDetails, fmt.Sprintf("WAF Instance:\n Было: %s\n Стало: %s", oldDisplay, newDisplay)) } } var aliasesModifiedTime *time.Time if aliasesModifiedAt > 0 { t := time.Unix(aliasesModifiedAt, 0) aliasesModifiedTime = &t } _, err = db.Exec(` INSERT INTO sp_info (sid, domain_name, origins, aliases, aliaces_in_sp_modified, protected_ip, resolved_ip, antiddos_enable, waf_enabled_sp, waf_vendor, instance_sp) VALUES ($1, $2, $3::jsonb, $4::jsonb, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (sid) DO UPDATE SET domain_name = EXCLUDED.domain_name, origins = EXCLUDED.origins, aliases = EXCLUDED.aliases, aliaces_in_sp_modified = EXCLUDED.aliaces_in_sp_modified, protected_ip = EXCLUDED.protected_ip, resolved_ip = EXCLUDED.resolved_ip, antiddos_enable = EXCLUDED.antiddos_enable, waf_enabled_sp = EXCLUDED.waf_enabled_sp, waf_vendor = EXCLUDED.waf_vendor, instance_sp = EXCLUDED.instance_sp, updated_at = now() `, sid, domain, originsJSON, aliasesJSON, aliasesModifiedTime, protectedIP, resolvedIP, antiddosEnabled, wafEnabledSP, wafVendor, instanceSP, ) if err != nil { return err } // Отправляем алерт для обычных изменений if hasChanges { ptafWarning := "" if needsPTAFUpdate { ptafWarning = "\n\n⚠️ ВНИМАНИЕ: Необходимо внести изменения в кабинете PT AF!" } message := fmt.Sprintf( "🔔 Обновление WAF Info\n\n"+ "SID: %d\n"+ "Домен: %s\n\n"+ "Изменения:\n\n%s%s", sid, domain, strings.Join(changeDetails, "\n\n"), ptafWarning, ) if err := sendAlert(message); err != nil { log.Printf("Failed to send Telegram alert for SID %d: %v", sid, err) } else { log.Printf("Alert sent for SID %d", sid) } } // Отправляем отдельный алерт для изменений WAF настроек if hasWAFChanges { wafMessage := fmt.Sprintf( "⚙️ Изменения настроек инстанса на стороне SP\n\n"+ "SID: %d\n"+ "Домен: %s\n\n"+ "Изменения:\n\n%s", sid, domain, strings.Join(wafChangeDetails, "\n\n"), ) if err := sendAlert(wafMessage); err != nil { log.Printf("Failed to send WAF settings alert for SID %d: %v", sid, err) } else { log.Printf("WAF settings alert sent for SID %d", sid) } } return nil } func wafVendorWarning(vendor string) string { switch strings.ToLower(vendor) { case "ptaf": return "⚠️ Проверьте, не нужно ли внести изменения в PT AF!" case "sw": return "⚠️ Проверьте, не нужно ли внести изменения в SW!" case "wmx": return "⚠️ Проверьте, не нужно ли внести изменения в WMX!" default: return "⚠️ Проверьте, не нужно ли внести изменения в WAF!" } } func cleanupRemovedSIDs(db *sql.DB, activeSIDs []int64) error { if len(activeSIDs) == 0 { log.Println("[Cleanup] No active SIDs provided, skipping cleanup to avoid accidental data loss") return nil } rows, err := db.Query(` SELECT s.sid, s.domain_name, a.waf_vendor FROM sp_info s LEFT JOIN apps_settings a ON a.l7resourceid = s.sid `) if err != nil { return fmt.Errorf("failed to query sp_info: %w", err) } defer rows.Close() activeSet := make(map[int64]bool, len(activeSIDs)) for _, sid := range activeSIDs { activeSet[sid] = true } type staleRecord struct { sid int64 domain string wafVendor string } var stale []staleRecord for rows.Next() { var sid int64 var domain string var wafVendor sql.NullString if err := rows.Scan(&sid, &domain, &wafVendor); err != nil { return err } if !activeSet[sid] { vendor := "" if wafVendor.Valid { vendor = wafVendor.String } stale = append(stale, staleRecord{sid, domain, vendor}) } } if len(stale) == 0 { log.Println("[Cleanup] No stale SIDs found in sp_info") return nil } log.Printf("[Cleanup] Found %d stale SID(s) to remove", len(stale)) for _, rec := range stale { log.Printf("[Cleanup] Removing SID %d (%s) from sp_info", rec.sid, rec.domain) _, err := db.Exec(`DELETE FROM sp_info WHERE sid = $1`, rec.sid) if err != nil { log.Printf("[Cleanup] Failed to delete SID %d: %v", rec.sid, err) continue } wafWarning := wafVendorWarning(rec.wafVendor) message := fmt.Sprintf( "🗑 Ресурс удалён из мониторинга\n\n"+ "SID: %d\n"+ "Домен: %s\n\n"+ "Ресурс отсутствует в apps_settings и был удалён из sp_info.\n"+ "%s", rec.sid, rec.domain, wafWarning, ) if err := sendAlert(message); err != nil { log.Printf("[Cleanup] Failed to send Telegram alert for SID %d: %v", rec.sid, err) } else { log.Printf("[Cleanup] Alert sent for removed SID %d", rec.sid) } } return nil }