package main import ( "context" "database/sql" "encoding/json" "fmt" "log" "net/http" "time" "github.com/gorilla/mux" ) // updateFqdnRenewalEnabledHandler aktualisiert das renewal_enabled Flag für einen FQDN func updateFqdnRenewalEnabledHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "PUT, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } vars := mux.Vars(r) spaceID := vars["spaceId"] fqdnID := vars["fqdnId"] // Prüfe Berechtigung userID, _ := getUserFromRequest(r) if userID == "" { http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) return } hasAccess, err := hasSpaceAccess(userID, spaceID) if err != nil { http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) return } if !hasAccess { http.Error(w, "Keine Berechtigung für diesen Space", http.StatusForbidden) return } // Prüfe ob FQDN existiert und hole aktuellen renewal_enabled Status var currentRenewalEnabled sql.NullInt64 err = db.QueryRow("SELECT renewal_enabled FROM fqdns WHERE id = ? AND space_id = ?", fqdnID, spaceID).Scan(¤tRenewalEnabled) if err == sql.ErrNoRows { http.Error(w, "FQDN nicht gefunden", http.StatusNotFound) return } if err != nil { http.Error(w, "Fehler beim Laden des FQDN", http.StatusInternalServerError) log.Printf("Fehler beim Laden des FQDN: %v", err) return } // Parse Request Body var req struct { RenewalEnabled bool `json:"renewalEnabled"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Ungültige Anfrage", http.StatusBadRequest) return } // Prüfe ob renewal_enabled von false auf true geändert wird wasDisabled := !currentRenewalEnabled.Valid || currentRenewalEnabled.Int64 == 0 willBeEnabled := req.RenewalEnabled shouldProcessRenewalInfo := wasDisabled && willBeEnabled // Beginne Transaktion ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() tx, err := db.BeginTx(ctx, nil) if err != nil { http.Error(w, "Fehler beim Starten der Transaktion", http.StatusInternalServerError) log.Printf("Fehler beim Starten der Transaktion: %v", err) return } defer tx.Rollback() // Update renewal_enabled (explizit als 0 oder 1 speichern, nicht NULL) var renewalEnabledInt int if req.RenewalEnabled { renewalEnabledInt = 1 } else { renewalEnabledInt = 0 } _, err = tx.ExecContext(ctx, "UPDATE fqdns SET renewal_enabled = ? WHERE id = ? AND space_id = ?", renewalEnabledInt, fqdnID, spaceID) if err != nil { http.Error(w, "Fehler beim Aktualisieren des renewal_enabled Flags", http.StatusInternalServerError) log.Printf("Fehler beim Aktualisieren des renewal_enabled Flags: %v", err) return } // Wenn renewal_enabled deaktiviert wird, lösche nur pending/processing Queue-Einträge für diesen FQDN // Completed und failed Einträge bleiben als Historie erhalten if !req.RenewalEnabled { _, err = tx.ExecContext(ctx, "DELETE FROM renewal_queue WHERE fqdn_id = ? AND status IN ('pending', 'processing')", fqdnID) if err != nil { http.Error(w, "Fehler beim Löschen der Queue-Einträge", http.StatusInternalServerError) log.Printf("Fehler beim Löschen der Queue-Einträge: %v", err) return } log.Printf("Pending/Processing Queue-Einträge für FQDN %s gelöscht (renewal_enabled deaktiviert)", fqdnID) } // Committe die Transaktion if err = tx.Commit(); err != nil { http.Error(w, "Fehler beim Speichern der Änderungen", http.StatusInternalServerError) log.Printf("Fehler beim Committen der Transaktion: %v", err) return } // Wenn renewal_enabled von false auf true geändert wurde, verarbeite RenewalInfo für das aktuelle Zertifikat if shouldProcessRenewalInfo { go func() { // Hole das aktuellste (nicht-intermediate) Zertifikat für diesen FQDN var certID, certPEM string err := db.QueryRow(` SELECT id, certificate_pem FROM certificates WHERE fqdn_id = ? AND (is_intermediate = 0 OR is_intermediate IS NULL) ORDER BY expires_at DESC, created_at DESC LIMIT 1 `, fqdnID).Scan(&certID, &certPEM) if err == sql.ErrNoRows { log.Printf("Kein Zertifikat für FQDN %s gefunden - RenewalInfo wird übersprungen", fqdnID) return } if err != nil { log.Printf("Fehler beim Laden des Zertifikats für FQDN %s: %v", fqdnID, err) return } if certPEM == "" { log.Printf("Zertifikat %s hat kein PEM - RenewalInfo wird übersprungen", certID) return } // Verarbeite RenewalInfo im Hintergrund log.Printf("Verarbeite RenewalInfo für Zertifikat %s (FQDN %s wurde aktiviert)", certID, fqdnID) if err := ProcessRenewalInfoForCertificate(certPEM, certID, fqdnID, spaceID, true); err != nil { log.Printf("Fehler beim Verarbeiten der RenewalInfo für FQDN %s (wird ignoriert): %v", fqdnID, err) } else { log.Printf("RenewalInfo erfolgreich verarbeitet für FQDN %s", fqdnID) } }() } // Hole Username für Audit-Log userID, username := getUserFromRequest(r) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "renewalEnabled": req.RenewalEnabled, }) // Audit-Log if auditService != nil { ipAddress, userAgent := getRequestInfo(r) auditService.Track(r.Context(), "UPDATE", "fqdn", fqdnID, userID, username, map[string]interface{}{ "spaceId": spaceID, "renewalEnabled": req.RenewalEnabled, "message": fmt.Sprintf("Renewal-Status für FQDN aktualisiert: %v", req.RenewalEnabled), }, ipAddress, userAgent) } } // getRenewalQueueHandler gibt alle Einträge aus der renewal_queue zurück func getRenewalQueueHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } // Prüfe Berechtigung - jeder authentifizierte User kann die Queue sehen userID, _ := getUserFromRequest(r) if userID == "" { http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) return } // Hole alle Queue-Einträge mit FQDN und Space-Informationen ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() rows, err := db.QueryContext(ctx, ` SELECT rq.id, rq.certificate_id, rq.fqdn_id, rq.space_id, rq.scheduled_at, rq.status, rq.created_at, rq.processed_at, rq.error_message, f.fqdn, s.name as space_name FROM renewal_queue rq LEFT JOIN fqdns f ON rq.fqdn_id = f.id LEFT JOIN spaces s ON rq.space_id = s.id ORDER BY rq.scheduled_at ASC `) if err != nil { http.Error(w, "Fehler beim Laden der Renewal Queue", http.StatusInternalServerError) log.Printf("Fehler beim Laden der Renewal Queue: %v", err) return } defer rows.Close() var queueItems []map[string]interface{} for rows.Next() { var id, certID, fqdnID, spaceID, scheduledAt, status, createdAt, fqdn, spaceName string var processedAt, errorMessage sql.NullString err := rows.Scan(&id, &certID, &fqdnID, &spaceID, &scheduledAt, &status, &createdAt, &processedAt, &errorMessage, &fqdn, &spaceName) if err != nil { log.Printf("Fehler beim Scannen der Queue-Zeile: %v", err) continue } item := map[string]interface{}{ "id": id, "certificateId": certID, "fqdnId": fqdnID, "spaceId": spaceID, "scheduledAt": scheduledAt, "status": status, "createdAt": createdAt, "fqdn": fqdn, "spaceName": spaceName, } if processedAt.Valid { item["processedAt"] = processedAt.String } if errorMessage.Valid { item["errorMessage"] = errorMessage.String } queueItems = append(queueItems, item) } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "queue": queueItems, }) } // deleteAllRenewalQueueEntriesHandler löscht alle Einträge aus der Renewal Queue func deleteAllRenewalQueueEntriesHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } // Prüfe Berechtigung - nur authentifizierte User userID, username := getUserFromRequest(r) if userID == "" { http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) return } // Prüfe Bestätigung (optional, aber empfohlen) confirm := r.URL.Query().Get("confirm") if confirm != "true" { http.Error(w, "Bestätigung erforderlich. Verwende ?confirm=true", http.StatusBadRequest) return } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Lösche alle Queue-Einträge result, err := db.ExecContext(ctx, "DELETE FROM renewal_queue") if err != nil { http.Error(w, "Fehler beim Löschen der Queue-Einträge", http.StatusInternalServerError) log.Printf("Fehler beim Löschen der Queue-Einträge: %v", err) return } rowsAffected, err := result.RowsAffected() if err != nil { http.Error(w, "Fehler beim Prüfen der gelöschten Zeilen", http.StatusInternalServerError) log.Printf("Fehler beim Prüfen der gelöschten Zeilen: %v", err) return } log.Printf("Alle Renewal Queue-Einträge gelöscht: %d Einträge", rowsAffected) response := map[string]interface{}{ "success": true, "message": "Alle Renewal Queue-Einträge erfolgreich gelöscht", "deletedCount": rowsAffected, } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(response) // Audit-Log if auditService != nil { ipAddress, userAgent := getRequestInfo(r) auditService.Track(r.Context(), "DELETE", "renewal_queue", "", userID, username, map[string]interface{}{ "deletedCount": rowsAffected, "message": fmt.Sprintf("Alle Renewal Queue-Einträge gelöscht (%d Einträge)", rowsAffected), }, ipAddress, userAgent) } }