// 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 }