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>
104 lines
3.9 KiB
Go
104 lines
3.9 KiB
Go
package agent
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestRestoreTargetGuardBlocksSystemDirectories ist der Kerntest der
|
|
// Agenten-Wiederherstellung.
|
|
//
|
|
// Der Agent schreibt auf **sein eigenes** System. Ein Auftrag, der nach /etc
|
|
// zurueckschreibt, ueberschriebe das laufende Betriebssystem des gesicherten
|
|
// Rechners — und der Server kann das nicht verhindern: Er kennt die
|
|
// Verzeichnisstruktur eines fremden Systems nicht, und ein Windows-Agent hat
|
|
// andere Systempfade als ein Linux-Server.
|
|
//
|
|
// Die Pruefung gehoert deshalb auf das empfangende System, nicht nur auf den
|
|
// Server (wo sie seit Phase 19 ebenfalls steht).
|
|
func TestRestoreTargetGuardBlocksSystemDirectories(testInstance *testing.T) {
|
|
forbiddenTargets := []string{
|
|
"/etc", "/etc/cron.d", "/usr/local/bin", "/bin", "/sbin",
|
|
"/boot/grub", "/var/lib/postgresql", "/System/Library",
|
|
"/private/etc/hosts", "/root/.ssh", "/",
|
|
}
|
|
|
|
for _, target := range forbiddenTargets {
|
|
if guardError := validateRestoreTarget(target); guardError == nil {
|
|
testInstance.Errorf("das Systemverzeichnis %q wurde als Wiederherstellungsziel "+
|
|
"zugelassen", target)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestRestoreTargetGuardAllowsOrdinaryTargets prueft die Gegenprobe.
|
|
//
|
|
// Ein Schutz, der auch die legitimen Ziele sperrt, macht die Wiederherstellung
|
|
// unbrauchbar — und wer ihn deswegen abschaltet, hat gar keinen mehr.
|
|
func TestRestoreTargetGuardAllowsOrdinaryTargets(testInstance *testing.T) {
|
|
allowedTargets := []string{
|
|
"/srv/wiederherstellung", "/home/benutzer/daten", "/mnt/ziel",
|
|
"/data/restore-2026", testInstance.TempDir(),
|
|
}
|
|
|
|
for _, target := range allowedTargets {
|
|
if guardError := validateRestoreTarget(target); guardError != nil {
|
|
testInstance.Errorf("das gewöhnliche Ziel %q wurde gesperrt: %v", target, guardError)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestRestoreTargetGuardIsNotFooledByPrefixes prueft den Pfadvergleich.
|
|
//
|
|
// Ein Vergleich ueber strings.HasPrefix haelt "/etchen" fuer einen Teil von
|
|
// "/etc" — derselbe Fund wie beim TargetGuard des Servers (Phase 19).
|
|
func TestRestoreTargetGuardIsNotFooledByPrefixes(testInstance *testing.T) {
|
|
lookalikeTargets := []string{"/etchen", "/var/library", "/usrdaten", "/bindungen"}
|
|
|
|
for _, target := range lookalikeTargets {
|
|
if guardError := validateRestoreTarget(target); guardError != nil {
|
|
testInstance.Errorf("das Ziel %q wurde fälschlich gesperrt: %v", target, guardError)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestRestoreTargetGuardResolvesEscapes prueft Rueckwaertsschritte.
|
|
//
|
|
// "/srv/../etc" ist "/etc". Ohne Bereinigung liefe der Vergleich gegen die
|
|
// Zeichenkette statt gegen den tatsaechlichen Pfad.
|
|
func TestRestoreTargetGuardResolvesEscapes(testInstance *testing.T) {
|
|
escapeAttempts := []string{"/srv/../etc", "/tmp/../etc/cron.d", "/home/x/../../etc"}
|
|
|
|
for _, target := range escapeAttempts {
|
|
if guardError := validateRestoreTarget(target); guardError == nil {
|
|
testInstance.Errorf("der Ausbruchsversuch %q wurde zugelassen", target)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestRestoreTargetErrorNamesTheReason prueft die Fehlermeldung.
|
|
func TestRestoreTargetErrorNamesTheReason(testInstance *testing.T) {
|
|
guardError := validateRestoreTarget("/etc/beliebig")
|
|
if guardError == nil {
|
|
testInstance.Fatal("das Systemverzeichnis wurde zugelassen")
|
|
}
|
|
|
|
if !strings.Contains(guardError.Error(), "systemverzeichnis") {
|
|
testInstance.Errorf("die Meldung nennt den Grund nicht: %v", guardError)
|
|
}
|
|
}
|
|
|
|
// TestCompressionLevelFallsBackSafely prueft die Auftragsdeutung.
|
|
//
|
|
// Ein neuerer Server koennte eine Stufe kennen, die dieser Agent nicht hat.
|
|
// Die Sicherung deswegen ausfallen zu lassen waere unverhaeltnismaessig.
|
|
func TestCompressionLevelFallsBackSafely(testInstance *testing.T) {
|
|
if resolveCompressionLevel("eine-kuenftige-stufe") != resolveCompressionLevel("balanced") {
|
|
testInstance.Error("eine unbekannte Kompressionsstufe ergibt nicht die ausgewogene")
|
|
}
|
|
|
|
if resolveCompressionLevel("off") == resolveCompressionLevel("maximum") {
|
|
testInstance.Error("bekannte Stufen werden nicht unterschieden")
|
|
}
|
|
}
|