added last fixes for dev branch prepartion

This commit is contained in:
2025-11-27 23:50:59 +01:00
parent 145dfd3d7c
commit 688b277b5d
13 changed files with 1051 additions and 158 deletions

View File

@@ -21,12 +21,50 @@ import (
"time"
)
const (
// Let's Encrypt Staging für Tests
acmeStagingDirectory = "https://acme-staging-v02.api.letsencrypt.org/directory"
acmeStagingNewAccount = "https://acme-staging-v02.api.letsencrypt.org/acme/new-acct"
acmeStagingNewOrder = "https://acme-staging-v02.api.letsencrypt.org/acme/new-order"
)
// ACMEDirectory enthält die Endpunkte eines ACME-Servers
type ACMEDirectory struct {
NewNonce string `json:"newNonce"`
NewAccount string `json:"newAccount"`
NewOrder string `json:"newOrder"`
RevokeCert string `json:"revokeCert"`
KeyChange string `json:"keyChange"`
Meta struct {
TermsOfService string `json:"termsOfService"`
Website string `json:"website"`
CaaIdentities []string `json:"caaIdentities"`
ExternalAccountRequired bool `json:"externalAccountRequired"`
} `json:"meta"`
}
// getACMEDirectory ruft die Directory-Endpunkte von einem ACME-Server ab
func getACMEDirectory(directoryURL string) (*ACMEDirectory, error) {
client := &http.Client{
Timeout: 30 * time.Second,
}
resp, err := client.Get(directoryURL)
if err != nil {
return nil, fmt.Errorf("fehler beim Abrufen der ACME Directory: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("fehler beim Abrufen der ACME Directory (Status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("fehler beim Lesen der Directory-Response: %v", err)
}
var directory ACMEDirectory
if err := json.Unmarshal(body, &directory); err != nil {
return nil, fmt.Errorf("fehler beim Parsen der Directory-Response: %v", err)
}
return &directory, nil
}
// ACMEKeyPair enthält Private und Public Key
type ACMEKeyPair struct {
@@ -114,10 +152,9 @@ func loadOrCreateKeyPair(fqdnID string, keyDir string) (*ACMEKeyPair, error) {
}
// getNonce ruft einen neuen Nonce vom ACME-Server ab
func getNonce() (string, error) {
func getNonce(directoryURL string) (string, error) {
// Rufe Directory-Endpoint auf, um einen Nonce zu bekommen
req, err := http.NewRequest("HEAD", acmeStagingDirectory, nil)
req, err := http.NewRequest("HEAD", directoryURL, nil)
if err != nil {
return "", fmt.Errorf("fehler beim Erstellen des HEAD-Requests: %v", err)
}
@@ -338,12 +375,12 @@ func calculateKeyAuthHash(token string, pubKey *rsa.PublicKey) (string, error) {
return txtValue, nil
}
// createAccount erstellt einen neuen Account bei Let's Encrypt
func createAccount(keyPair *ACMEKeyPair, email string, traceID, fqdnID string, statusCallback func(status string)) (string, error) {
statusCallback("Erstelle Account bei Let's Encrypt...")
// createAccount erstellt einen neuen Account bei einem ACME-Server
func createAccount(keyPair *ACMEKeyPair, directoryURL, newAccountURL string, email string, traceID, fqdnID string, statusCallback func(status string)) (string, error) {
statusCallback("Erstelle Account bei ACME-Server...")
// Hole Nonce vom Server
nonce, err := getNonce()
nonce, err := getNonce(directoryURL)
if err != nil {
return "", fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}
@@ -355,13 +392,13 @@ func createAccount(keyPair *ACMEKeyPair, email string, traceID, fqdnID string, s
}
// Erstelle JWS (ohne KeyID, da es ein neuer Account ist)
jws, err := createJWS(keyPair, payload, acmeStagingNewAccount, "", nonce)
jws, err := createJWS(keyPair, payload, newAccountURL, "", nonce)
if err != nil {
return "", fmt.Errorf("fehler beim Erstellen des JWS: %v", err)
}
// Sende Request
req, err := http.NewRequest("POST", acmeStagingNewAccount, bytes.NewBufferString(jws))
req, err := http.NewRequest("POST", newAccountURL, bytes.NewBufferString(jws))
if err != nil {
return "", fmt.Errorf("fehler beim Erstellen des HTTP-Requests: %v", err)
}
@@ -398,12 +435,12 @@ func createAccount(keyPair *ACMEKeyPair, email string, traceID, fqdnID string, s
return keyID, nil
}
// createOrder erstellt eine neue Order bei Let's Encrypt
func createOrder(keyPair *ACMEKeyPair, keyID string, domains []string, traceID, fqdnID string, statusCallback func(status string)) (string, map[string]interface{}, error) {
statusCallback("Erstelle Order bei Let's Encrypt...")
// createOrder erstellt eine neue Order bei einem ACME-Server
func createOrder(keyPair *ACMEKeyPair, directoryURL, newOrderURL string, keyID string, domains []string, traceID, fqdnID string, statusCallback func(status string)) (string, map[string]interface{}, error) {
statusCallback("Erstelle Order bei ACME-Server...")
// Hole Nonce vom Server
nonce, err := getNonce()
nonce, err := getNonce(directoryURL)
if err != nil {
return "", nil, fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}
@@ -420,13 +457,13 @@ func createOrder(keyPair *ACMEKeyPair, keyID string, domains []string, traceID,
}
// Erstelle JWS (mit KeyID)
jws, err := createJWS(keyPair, payload, acmeStagingNewOrder, keyID, nonce)
jws, err := createJWS(keyPair, payload, newOrderURL, keyID, nonce)
if err != nil {
return "", nil, fmt.Errorf("fehler beim Erstellen des JWS: %v", err)
}
// Sende Request
req, err := http.NewRequest("POST", acmeStagingNewOrder, bytes.NewBufferString(jws))
req, err := http.NewRequest("POST", newOrderURL, bytes.NewBufferString(jws))
if err != nil {
return "", nil, fmt.Errorf("fehler beim Erstellen des HTTP-Requests: %v", err)
}
@@ -468,10 +505,10 @@ func createOrder(keyPair *ACMEKeyPair, keyID string, domains []string, traceID,
return orderURL, orderResponse, nil
}
// RequestCertificate beantragt ein Zertifikat von Let's Encrypt (manuell ohne LEGO)
// RequestCertificate beantragt ein Zertifikat von einem ACME-Server
// cleanupTokenFunc wird aufgerufen, wenn die Challenge invalid ist, um den Token zu bereinigen
// traceID wird vom Aufrufer übergeben, um die gleiche TRACE_ID über den gesamten Prozess zu verwenden
func RequestCertificate(fqdn string, email string, fqdnID string, existingKeyID string, traceID string, updateTokenFunc func(token string) error, cleanupTokenFunc func() error, statusCallback func(status string)) (*CertificateRequestResult, error) {
func RequestCertificate(ctx *ACMEClientContext, fqdn string, email string, fqdnID string, existingKeyID string, traceID string, updateTokenFunc func(token string) error, cleanupTokenFunc func() error, statusCallback func(status string)) (*CertificateRequestResult, error) {
log.Printf("[ACME] ===== REQUEST CERTIFICATE START =====")
log.Printf("[ACME] FQDN: %s", fqdn)
@@ -503,14 +540,14 @@ func RequestCertificate(fqdn string, email string, fqdnID string, existingKeyID
result.Status = append(result.Status, "Key-Paar erfolgreich geladen/erstellt")
statusCallback("Key-Paar erfolgreich geladen/erstellt")
// Schritt 2: Erstelle Account bei Let's Encrypt (falls noch nicht vorhanden)
// Schritt 2: Erstelle Account beim ACME-Server (falls noch nicht vorhanden)
log.Printf("[ACME] Schritt 2: Erstelle/Verwende Account...")
var keyID string
if existingKeyID == "" {
log.Printf("[ACME] Keine KeyID vorhanden, erstelle neuen Account...")
statusCallback("Erstelle Account bei Let's Encrypt...")
statusCallback(fmt.Sprintf("Erstelle Account bei %s...", ctx.Provider.GetDisplayName()))
result.StepStatus["ACCOUNT_ERSTELLUNG"] = "loading"
keyID, err = createAccount(keyPair, email, traceID, fqdnID, statusCallback)
keyID, err = createAccount(keyPair, ctx.DirectoryURL, ctx.NewAccountURL, email, traceID, fqdnID, statusCallback)
if err != nil {
log.Printf("[ACME] FEHLER bei Schritt 2 (Account-Erstellung): %v", err)
logCertStatus(traceID, fqdnID, "ACCOUNT_ERSTELLUNG", "FAILED", err.Error())
@@ -531,16 +568,16 @@ func RequestCertificate(fqdn string, email string, fqdnID string, existingKeyID
statusCallback(fmt.Sprintf("Verwende existierenden Account (KeyID: %s)", keyID))
}
// Schritt 3: Erstelle Order bei Let's Encrypt
// Schritt 3: Erstelle Order beim ACME-Server
baseFqdn := fqdn
if strings.HasPrefix(baseFqdn, "*.") {
baseFqdn = baseFqdn[2:]
}
log.Printf("[ACME] Schritt 3: Erstelle Order für Domain: %s", baseFqdn)
statusCallback("Erstelle Order bei Let's Encrypt...")
statusCallback(fmt.Sprintf("Erstelle Order bei %s...", ctx.Provider.GetDisplayName()))
result.StepStatus["ORDER_ERSTELLUNG"] = "loading"
orderURL, orderResponse, err := createOrder(keyPair, keyID, []string{baseFqdn}, traceID, fqdnID, statusCallback)
orderURL, orderResponse, err := createOrder(keyPair, ctx.DirectoryURL, ctx.NewOrderURL, keyID, []string{baseFqdn}, traceID, fqdnID, statusCallback)
if err != nil {
log.Printf("[ACME] FEHLER bei Schritt 3 (Order-Erstellung): %v", err)
logCertStatus(traceID, fqdnID, "ORDER_ERSTELLUNG", "FAILED", err.Error())
@@ -557,7 +594,7 @@ func RequestCertificate(fqdn string, email string, fqdnID string, existingKeyID
if orderResponse != nil {
statusCallback("Extrahiere Challenge-Token...")
token, err := extractTokenFromOrder(keyPair, keyID, orderResponse, baseFqdn)
token, err := extractTokenFromOrder(ctx, keyPair, keyID, orderResponse, baseFqdn)
if err != nil {
logCertStatus(traceID, fqdnID, "TOKEN_EXTRAKTION", "FAILED", err.Error())
return nil, fmt.Errorf("fehler beim Extrahieren des Tokens: %v", err)
@@ -588,14 +625,14 @@ func RequestCertificate(fqdn string, email string, fqdnID string, existingKeyID
// Schritt 5: Aktiviere Challenge bei Let's Encrypt
statusCallback("Aktiviere Challenge bei Let's Encrypt...")
challengeURL, err := extractChallengeURLFromOrder(keyPair, keyID, orderResponse, baseFqdn)
challengeURL, err := extractChallengeURLFromOrder(ctx, keyPair, keyID, orderResponse, baseFqdn)
if err != nil {
logCertStatus(traceID, fqdnID, "CHALLENGE_URL_EXTRAKTION", "FAILED", err.Error())
return nil, fmt.Errorf("fehler beim Extrahieren der Challenge-URL: %v", err)
}
result.StepStatus["CHALLENGE_AKTIVIERUNG"] = "loading"
if err := activateChallenge(keyPair, keyID, challengeURL, traceID, fqdnID); err != nil {
if err := activateChallenge(ctx, keyPair, keyID, challengeURL, traceID, fqdnID); err != nil {
logCertStatus(traceID, fqdnID, "CHALLENGE_AKTIVIERUNG", "FAILED", err.Error())
result.StepStatus["CHALLENGE_AKTIVIERUNG"] = "error"
return nil, fmt.Errorf("fehler beim Aktivieren der Challenge: %v", err)
@@ -608,7 +645,7 @@ func RequestCertificate(fqdn string, email string, fqdnID string, existingKeyID
// Schritt 6: Warte auf Challenge-Validierung (Polling)
statusCallback("Warte auf Challenge-Validierung...")
if err := waitForChallengeValidation(keyPair, keyID, challengeURL, traceID, fqdnID, statusCallback, cleanupTokenFunc); err != nil {
if err := waitForChallengeValidation(ctx, keyPair, keyID, challengeURL, traceID, fqdnID, statusCallback, cleanupTokenFunc); err != nil {
// Cleanup wurde bereits in waitForChallengeValidation durchgeführt
// Bereinige auch den Token aus der Datenbank
if cleanupTokenFunc != nil {
@@ -627,7 +664,7 @@ func RequestCertificate(fqdn string, email string, fqdnID string, existingKeyID
statusCallback("Finalisiere Order und hole Zertifikat...")
result.StepStatus["ZERTIFIKAT_ERSTELLUNG"] = "loading"
certPEM, keyPEM, err := finalizeOrderAndGetCertificate(keyPair, keyID, orderURL, orderResponse, traceID, fqdnID, statusCallback)
certPEM, keyPEM, err := finalizeOrderAndGetCertificate(ctx, keyPair, keyID, orderURL, orderResponse, traceID, fqdnID, statusCallback)
if err != nil {
logCertStatus(traceID, fqdnID, "ZERTIFIKAT_ERSTELLUNG", "FAILED", err.Error())
result.StepStatus["ZERTIFIKAT_ERSTELLUNG"] = "error"
@@ -648,7 +685,7 @@ func RequestCertificate(fqdn string, email string, fqdnID string, existingKeyID
}
// extractTokenFromOrder extrahiert den Challenge-Token aus der Order-Response
func extractTokenFromOrder(keyPair *ACMEKeyPair, keyID string, orderResponse map[string]interface{}, domain string) (string, error) {
func extractTokenFromOrder(ctx *ACMEClientContext, keyPair *ACMEKeyPair, keyID string, orderResponse map[string]interface{}, domain string) (string, error) {
// Extrahiere authorizations Array
authorizations, ok := orderResponse["authorizations"].([]interface{})
@@ -661,7 +698,7 @@ func extractTokenFromOrder(keyPair *ACMEKeyPair, keyID string, orderResponse map
}
// Hole Nonce für Authorization-Request
nonce, err := getNonce()
nonce, err := getNonce(ctx.DirectoryURL)
if err != nil {
return "", fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}
@@ -753,7 +790,7 @@ func extractTokenFromOrder(keyPair *ACMEKeyPair, keyID string, orderResponse map
}
// extractChallengeURLFromOrder extrahiert die Challenge-URL aus der Order-Response
func extractChallengeURLFromOrder(keyPair *ACMEKeyPair, keyID string, orderResponse map[string]interface{}, domain string) (string, error) {
func extractChallengeURLFromOrder(ctx *ACMEClientContext, keyPair *ACMEKeyPair, keyID string, orderResponse map[string]interface{}, domain string) (string, error) {
// Extrahiere authorizations Array
authorizations, ok := orderResponse["authorizations"].([]interface{})
@@ -766,7 +803,7 @@ func extractChallengeURLFromOrder(keyPair *ACMEKeyPair, keyID string, orderRespo
}
// Hole Nonce für Authorization-Request
nonce, err := getNonce()
nonce, err := getNonce(ctx.DirectoryURL)
if err != nil {
return "", fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}
@@ -855,10 +892,10 @@ func extractChallengeURLFromOrder(keyPair *ACMEKeyPair, keyID string, orderRespo
return "", fmt.Errorf("keine DNS-01 Challenge URL in Authorizations gefunden")
}
// activateChallenge aktiviert eine Challenge bei Let's Encrypt
func activateChallenge(keyPair *ACMEKeyPair, keyID string, challengeURL string, traceID, fqdnID string) error {
// activateChallenge aktiviert eine Challenge beim ACME-Server
func activateChallenge(ctx *ACMEClientContext, keyPair *ACMEKeyPair, keyID string, challengeURL string, traceID, fqdnID string) error {
// Hole Nonce
nonce, err := getNonce()
nonce, err := getNonce(ctx.DirectoryURL)
if err != nil {
return fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}
@@ -901,10 +938,10 @@ func activateChallenge(keyPair *ACMEKeyPair, keyID string, challengeURL string,
}
// cleanupChallenge führt einen Cleanup-Prozess für eine Challenge durch
func cleanupChallenge(keyPair *ACMEKeyPair, keyID string, challengeURL string) error {
func cleanupChallenge(ctx *ACMEClientContext, keyPair *ACMEKeyPair, keyID string, challengeURL string) error {
// Hole Nonce
nonce, err := getNonce()
nonce, err := getNonce(ctx.DirectoryURL)
if err != nil {
return fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}
@@ -944,7 +981,7 @@ func cleanupChallenge(keyPair *ACMEKeyPair, keyID string, challengeURL string) e
}
// waitForChallengeValidation wartet auf die Validierung der Challenge (Polling)
func waitForChallengeValidation(keyPair *ACMEKeyPair, keyID string, challengeURL string, traceID, fqdnID string, statusCallback func(status string), cleanupTokenFunc func() error) error {
func waitForChallengeValidation(ctx *ACMEClientContext, keyPair *ACMEKeyPair, keyID string, challengeURL string, traceID, fqdnID string, statusCallback func(status string), cleanupTokenFunc func() error) error {
maxAttempts := 30 // Maximal 30 Versuche
pollInterval := 2 * time.Second // Alle 2 Sekunden prüfen
@@ -952,7 +989,7 @@ func waitForChallengeValidation(keyPair *ACMEKeyPair, keyID string, challengeURL
statusCallback(fmt.Sprintf("Prüfe Challenge-Status (%d/%d)...", attempt, maxAttempts))
// Hole Nonce
nonce, err := getNonce()
nonce, err := getNonce(ctx.DirectoryURL)
if err != nil {
return fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}
@@ -1047,7 +1084,7 @@ func waitForChallengeValidation(keyPair *ACMEKeyPair, keyID string, challengeURL
}
// waitForOrderReady wartet, bis die Order den Status "ready" hat (nach Challenge-Validierung)
func waitForOrderReady(keyPair *ACMEKeyPair, keyID string, orderURL string, traceID, fqdnID string, statusCallback func(status string)) error {
func waitForOrderReady(ctx *ACMEClientContext, keyPair *ACMEKeyPair, keyID string, orderURL string, traceID, fqdnID string, statusCallback func(status string)) error {
maxAttempts := 30 // Maximal 30 Versuche
pollInterval := 2 * time.Second // Alle 2 Sekunden prüfen
maxConsecutiveErrors := 3 // Maximal 3 aufeinanderfolgende Fehler
@@ -1057,7 +1094,7 @@ func waitForOrderReady(keyPair *ACMEKeyPair, keyID string, orderURL string, trac
statusCallback(fmt.Sprintf("Prüfe Order-Status (%d/%d)...", attempt, maxAttempts))
// Hole Nonce
nonce, err := getNonce()
nonce, err := getNonce(ctx.DirectoryURL)
if err != nil {
log.Printf("[ACME] Fehler beim Abrufen des Nonce: %v", err)
consecutiveErrors++
@@ -1184,11 +1221,11 @@ func waitForOrderReady(keyPair *ACMEKeyPair, keyID string, orderURL string, trac
}
// finalizeOrderAndGetCertificate finalisiert die Order und holt das Zertifikat
func finalizeOrderAndGetCertificate(keyPair *ACMEKeyPair, keyID string, orderURL string, orderResponse map[string]interface{}, traceID, fqdnID string, statusCallback func(status string)) (string, string, error) {
func finalizeOrderAndGetCertificate(ctx *ACMEClientContext, keyPair *ACMEKeyPair, keyID string, orderURL string, orderResponse map[string]interface{}, traceID, fqdnID string, statusCallback func(status string)) (string, string, error) {
// Warte, bis die Order bereit ist (Status "ready" oder "valid")
statusCallback("Warte auf Order-Bereitschaft...")
if err := waitForOrderReady(keyPair, keyID, orderURL, traceID, fqdnID, statusCallback); err != nil {
if err := waitForOrderReady(ctx, keyPair, keyID, orderURL, traceID, fqdnID, statusCallback); err != nil {
return "", "", fmt.Errorf("fehler beim Warten auf Order-Bereitschaft: %v", err)
}
@@ -1235,7 +1272,7 @@ func finalizeOrderAndGetCertificate(keyPair *ACMEKeyPair, keyID string, orderURL
}
// Hole Nonce
nonce, err := getNonce()
nonce, err := getNonce(ctx.DirectoryURL)
if err != nil {
return "", "", fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}
@@ -1291,7 +1328,7 @@ func finalizeOrderAndGetCertificate(keyPair *ACMEKeyPair, keyID string, orderURL
for attempt := 1; attempt <= maxAttempts; attempt++ {
statusCallback(fmt.Sprintf("Prüfe Order-Status (%d/%d)...", attempt, maxAttempts))
nonce, err := getNonce()
nonce, err := getNonce(ctx.DirectoryURL)
if err != nil {
return "", "", fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}
@@ -1349,7 +1386,7 @@ func finalizeOrderAndGetCertificate(keyPair *ACMEKeyPair, keyID string, orderURL
statusCallback("Hole Zertifikat...")
// Hole Zertifikat
nonce, err = getNonce()
nonce, err = getNonce(ctx.DirectoryURL)
if err != nil {
return "", "", fmt.Errorf("fehler beim Abrufen des Nonce: %v", err)
}