From b70db4ccf0a26eeef35750ed8a05205c85532802 Mon Sep 17 00:00:00 2001 From: Debian Date: Fri, 7 Aug 2026 08:35:03 +0200 Subject: [PATCH] =?UTF-8?q?fix(scheduler):=20ACME-Renewal=20nur=20auf=20VI?= =?UTF-8?q?P-Master=20=E2=80=94=20Standby-403=20verursachte=20Cert-Drift?= =?UTF-8?q?=20=E2=80=94=20v1.3.20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der Scheduler fuhr runRenewer (ACME) ungated auf beiden Nodes. ACME-HTTP-01- Challenges laufen aber auf :80 der VIP → nur der VIP-Master kann sie bestehen. Der BACKUP-Node scheiterte immer mit 403 (invalid authorization) und setzte tls_certs.status lokal auf "error" → Divergenz zur replizierten Row (Primary=active) → Config-Drift-Banner + Log-Noise. runRenewer jetzt hinter nodeHoldsVIP() gegated (Start + 6h-Tick); runCertExpiryCheck bleibt ungated (read-only). Die Cert-Row/PEM repliziert ohnehin vom Master auf den Standby. Co-Authored-By: Claude Opus 4.8 --- VERSION | 2 +- cmd/edgeguard-scheduler/main.go | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 32b1dc7..14d56a2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.19 \ No newline at end of file +1.3.20 \ No newline at end of file diff --git a/cmd/edgeguard-scheduler/main.go b/cmd/edgeguard-scheduler/main.go index 9a0fb16..a552533 100644 --- a/cmd/edgeguard-scheduler/main.go +++ b/cmd/edgeguard-scheduler/main.go @@ -191,7 +191,8 @@ func main() { auditRepo := audit.New(pool) alertDedupe := newDedupe(12 * time.Hour) - if renewer != nil { + // ACME nur auf dem VIP-Master (siehe Tick-Kommentar unten). + if renewer != nil && nodeHoldsVIP(ctx, pool) { runRenewer(ctx, renewer, alertSvc, alertDedupe) } runLicenseVerify(ctx, licClient, licKeyStore, licRepo, nodeID, alertSvc, alertDedupe) @@ -258,7 +259,13 @@ func main() { for { select { case <-renewTick.C: - if renewer != nil { + // ACME-HTTP-01-Challenges laufen auf :80 der VIP → nur der + // VIP-Master kann sie bestehen. Ein BACKUP-Node scheitert IMMER + // mit 403 (invalid authorization) und setzt tls_certs.status lokal + // auf "error" → Divergenz zur replizierten Row (Primary=active) → + // Config-Drift-Banner + Log-Noise. Renewal daher nur am VIP-Master; + // die Cert-Row/PEM repliziert von dort ohnehin auf den Standby. + if renewer != nil && nodeHoldsVIP(ctx, pool) { runRenewer(ctx, renewer, alertSvc, alertDedupe) } runCertExpiryCheck(ctx, tlsRepo, alertSvc, alertDedupe)