syncova-backup/packages/agent/task_execution_test.go
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
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>
2026-08-17 09:10:54 +02:00

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")
}
}