334 lines
10 KiB
Go
334 lines
10 KiB
Go
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)
|
|
}
|
|
}
|
|
|