implemented LE and ACME and fixed some bugs

This commit is contained in:
2025-11-27 04:20:09 +01:00
parent ec1e0da9d5
commit 145dfd3d7c
36 changed files with 10583 additions and 1107 deletions

View File

@@ -0,0 +1,267 @@
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(&currentRenewalEnabled)
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 alle Queue-Einträge für diesen FQDN
if !req.RenewalEnabled {
_, err = tx.ExecContext(ctx, "DELETE FROM renewal_queue WHERE fqdn_id = ?", 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("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,
})
}