Enterprise-Backup-, Recovery-, Verification-, Security- und Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme. Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes geht in keine Bewertung als "gut" ein. Umfang (Phasen 0-23): - Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll, Katalogaufbau allein aus den Manifesten — ohne Datenbank - Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck - Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell) - Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive - Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit - Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center, Ransomware-Heuristik (meldet, handelt nie) - Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing - Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository - Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64 Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs. Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md und docs/release-candidate.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
235 lines
8.9 KiB
Go
235 lines
8.9 KiB
Go
// Package alerting erkennt Zustaende, die jemand ansehen sollte.
|
|
//
|
|
// Das Meldungswesen hat einen Feind, und es ist nicht der fehlende Alarm: Es ist
|
|
// der Alarm, den niemand mehr liest. Ein System, das jede Minute dieselbe
|
|
// Meldung erzeugt, wird nach drei Tagen weggeklickt — und dann fehlt die eine,
|
|
// auf die es ankam.
|
|
//
|
|
// Jede Regel dieses Pakets ist deshalb an drei Fragen gemessen: Ist der Zustand
|
|
// wirklich handlungsbeduerftig? Verschwindet die Meldung von selbst, wenn er
|
|
// vorbei ist? Und sagt sie, was zu tun ist?
|
|
package alerting
|
|
|
|
import "time"
|
|
|
|
// Severity ist der Schweregrad einer Meldung.
|
|
type Severity string
|
|
|
|
const (
|
|
// SeverityInformation ist eine reine Auskunft.
|
|
SeverityInformation Severity = "information"
|
|
// SeverityWarning verlangt einen Blick bei Gelegenheit.
|
|
SeverityWarning Severity = "warning"
|
|
// SeverityHigh verlangt einen Blick heute.
|
|
SeverityHigh Severity = "high"
|
|
// SeverityCritical verlangt sofortiges Handeln.
|
|
//
|
|
// Sparsam vergeben: Wer nachts geweckt wird, muss auch etwas tun koennen.
|
|
// Ein kritischer Alarm ohne moegliche Handlung ist Schlafentzug ohne Nutzen.
|
|
SeverityCritical Severity = "critical"
|
|
)
|
|
|
|
// Rank liefert die Ordnungszahl eines Schweregrads.
|
|
func (severity Severity) Rank() int {
|
|
switch severity {
|
|
case SeverityCritical:
|
|
return 4
|
|
case SeverityHigh:
|
|
return 3
|
|
case SeverityWarning:
|
|
return 2
|
|
case SeverityInformation:
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// AtLeast meldet, ob dieser Schweregrad den geforderten erreicht.
|
|
func (severity Severity) AtLeast(minimumSeverity Severity) bool {
|
|
return severity.Rank() >= minimumSeverity.Rank()
|
|
}
|
|
|
|
// Bezeichner der Regeln (SYNCOVA_IMPLEMENTATION_PLAN.md §16).
|
|
const (
|
|
// RuleBackupFailure meldet einen gescheiterten Sicherungslauf.
|
|
RuleBackupFailure = "backup_failure"
|
|
// RuleRepeatedBackupFailure meldet mehrere Fehlschlaege in Folge.
|
|
RuleRepeatedBackupFailure = "repeated_backup_failure"
|
|
// RuleRepositoryUnavailable meldet ein nicht erreichbares Repository.
|
|
RuleRepositoryUnavailable = "repository_unavailable"
|
|
// RuleRepositoryFilling meldet ein zu 80 Prozent belegtes Repository.
|
|
RuleRepositoryFilling = "repository_filling"
|
|
// RuleRepositoryFull meldet ein zu 90 Prozent belegtes Repository.
|
|
RuleRepositoryFull = "repository_full"
|
|
// RuleAgentOffline meldet einen Agenten ohne Lebendmeldung.
|
|
RuleAgentOffline = "agent_offline"
|
|
// RuleRecoveryPointViolation meldet eine verletzte RPO-Vorgabe.
|
|
RuleRecoveryPointViolation = "rpo_violation"
|
|
// RuleVerificationFailure meldet eine Pruefung mit Befund.
|
|
RuleVerificationFailure = "verification_failure"
|
|
// RuleUnusualChangeRate meldet auffaellig viele neue Daten.
|
|
RuleUnusualChangeRate = "unusual_change_rate"
|
|
// RuleImmutableCopyMissing meldet ein Backup ohne Aufbewahrungsschutz.
|
|
RuleImmutableCopyMissing = "immutable_copy_missing"
|
|
// RuleCertificateExpiry meldet ein ablaufendes Agentenzertifikat.
|
|
RuleCertificateExpiry = "certificate_expiry"
|
|
)
|
|
|
|
// RuleDefinition beschreibt eine Regel.
|
|
type RuleDefinition struct {
|
|
// Name ist der maschinenlesbare Bezeichner.
|
|
Name string `json:"name"`
|
|
// Title ist die Bezeichnung.
|
|
Title string `json:"title"`
|
|
// Description erklaert, worauf die Regel achtet.
|
|
Description string `json:"description"`
|
|
// Severity ist der Schweregrad ausgeloester Meldungen.
|
|
Severity Severity `json:"severity"`
|
|
// Available meldet, ob die Regel ueberhaupt ausloesen kann.
|
|
Available bool `json:"available"`
|
|
// UnavailableReason erklaert eine Regel ohne Datengrundlage.
|
|
UnavailableReason string `json:"unavailable_reason,omitempty"`
|
|
}
|
|
|
|
// Schwellen der Regeln.
|
|
//
|
|
// Sie stehen hier beisammen, weil ihre Wahl den Wert des ganzen Meldungswesens
|
|
// bestimmt. Zu empfindlich bedeutet Alarmmuedigkeit, zu traege bedeutet, dass
|
|
// die Meldung zu spaet kommt.
|
|
const (
|
|
// repeatedFailureThreshold ist die Zahl der Fehlschlaege in Folge fuer die Eskalation.
|
|
//
|
|
// Zwei, nicht drei: Ein einzelner Fehlschlag kann ein Netzwerkzucken sein,
|
|
// zwei hintereinander sind ein Zustand.
|
|
repeatedFailureThreshold = 2
|
|
// fillingThresholdPercent ist die Belegung, ab der gewarnt wird.
|
|
fillingThresholdPercent = 80.0
|
|
// fullThresholdPercent ist die Belegung, ab der es kritisch wird.
|
|
fullThresholdPercent = 90.0
|
|
// agentOfflineAfter ist die Zeit ohne Lebendmeldung bis zur Meldung.
|
|
//
|
|
// Fuenfzehn Minuten bei einem Meldeabstand von einer Minute: Ein
|
|
// Netzwerkausfall von zwei Minuten weckt niemanden, eine Viertelstunde
|
|
// Schweigen ist ein Ausfall.
|
|
agentOfflineAfter = 15 * time.Minute
|
|
// Die Schwellen der Ransomware-Erkennung stehen seit Phase 16 im Paket
|
|
// ransomware: Sie werden dort gegen einen Basiswert je Kette gemessen, nicht
|
|
// gegen eine feste Zahl.
|
|
)
|
|
|
|
// Rules liefert alle Regeln in Anzeigereihenfolge.
|
|
func Rules() []RuleDefinition {
|
|
return []RuleDefinition{
|
|
{
|
|
Name: RuleBackupFailure,
|
|
Title: "Sicherung gescheitert",
|
|
Description: "Ein Sicherungslauf ist gescheitert oder meldete einen Teilfehler. Ein Teilfehler wird nicht wiederholt — er verlangt einen Blick.",
|
|
Severity: SeverityHigh,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleRepeatedBackupFailure,
|
|
Title: "Sicherung mehrfach gescheitert",
|
|
Description: "Ein Auftrag ist zweimal in Folge gescheitert. Ein einzelner Fehlschlag kann ein Netzwerkzucken sein, zwei hintereinander sind ein Zustand.",
|
|
Severity: SeverityCritical,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleRepositoryUnavailable,
|
|
Title: "Repository nicht erreichbar",
|
|
Description: "Ein Repository ist als nicht erreichbar geführt. Solange das gilt, kann weder gesichert noch wiederhergestellt werden.",
|
|
Severity: SeverityCritical,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleRepositoryFilling,
|
|
Title: "Repository zu 80 Prozent belegt",
|
|
Description: "Der Speicher wird knapp. Jetzt gibt es noch Zeit, aufzuräumen oder zu erweitern.",
|
|
Severity: SeverityWarning,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleRepositoryFull,
|
|
Title: "Repository zu 90 Prozent belegt",
|
|
Description: "Der Speicher läuft voll. Eine Sicherung, die keinen Platz mehr findet, scheitert — und zwar genau dann, wenn niemand hinsieht.",
|
|
Severity: SeverityCritical,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleAgentOffline,
|
|
Title: "Agent meldet sich nicht",
|
|
Description: "Ein Agent hat seit einer Viertelstunde keine Lebendmeldung geschickt. Seine Quellen werden derzeit nicht gesichert.",
|
|
Severity: SeverityHigh,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleRecoveryPointViolation,
|
|
Title: "Wiederherstellungspunkt überfällig",
|
|
Description: "Die letzte erfolgreiche Sicherung eines Auftrags liegt länger zurück als seine RPO-Vorgabe. Im Ernstfall gingen mehr Daten verloren als zugesagt.",
|
|
Severity: SeverityHigh,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleVerificationFailure,
|
|
Title: "Prüfung mit Befund",
|
|
Description: "Eine Prüfung fand beschädigte oder fehlende Blöcke. Das betroffene Backup ist nicht wiederherstellbar.",
|
|
Severity: SeverityCritical,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleUnusualChangeRate,
|
|
Title: "Auffällig viele neue Daten",
|
|
Description: "Ein Sicherungslauf weicht statistisch vom üblichen Verlauf seiner Kette ab — gemessen an " +
|
|
"sechs Signalen gegen einen Basiswert aus den letzten Läufen, nicht gegen eine feste Schwelle. " +
|
|
"Das ist ein **Verdacht**, keine Feststellung: Massenhafte Verschlüsselung sieht so aus — " +
|
|
"ein großes Update, eine Datenbankreorganisation oder ein Kettenwechsel aber auch. " +
|
|
"Die Anlage unternimmt von sich aus nichts.",
|
|
Severity: SeverityHigh,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleImmutableCopyMissing,
|
|
Title: "Backup ohne Aufbewahrungsschutz",
|
|
Description: "Ein Wiederherstellungspunkt liegt in einem Repository ohne Löschschutz. Wer Zugriff auf die Dateien hat, kann ihn entfernen.",
|
|
Severity: SeverityWarning,
|
|
Available: true,
|
|
},
|
|
{
|
|
Name: RuleCertificateExpiry,
|
|
Title: "Agentenzertifikat läuft ab",
|
|
Description: "Ein Zertifikat eines Agenten läuft in Kürze ab.",
|
|
Severity: SeverityWarning,
|
|
Available: false,
|
|
UnavailableReason: "Die Agenten weisen sich über Betriebstokens aus, nicht über Zertifikate. " +
|
|
"Die Tabelle agent_certificates wird von keiner Stelle beschrieben; die Regel könnte " +
|
|
"deshalb nie auslösen. Eine Regel, die dauerhaft schweigt, ist gefährlicher als keine — " +
|
|
"sie erweckt den Eindruck, es werde geprüft.",
|
|
},
|
|
}
|
|
}
|
|
|
|
// FindRule sucht eine Regel anhand ihres Namens.
|
|
func FindRule(ruleName string) (RuleDefinition, bool) {
|
|
for _, ruleDefinition := range Rules() {
|
|
if ruleDefinition.Name == ruleName {
|
|
return ruleDefinition, true
|
|
}
|
|
}
|
|
|
|
return RuleDefinition{}, false
|
|
}
|
|
|
|
// AvailableRuleCount zaehlt die ausloesbaren Regeln.
|
|
func AvailableRuleCount() int {
|
|
availableCount := 0
|
|
|
|
for _, ruleDefinition := range Rules() {
|
|
if ruleDefinition.Available {
|
|
availableCount++
|
|
}
|
|
}
|
|
|
|
return availableCount
|
|
}
|