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>
80 lines
3.0 KiB
Go
80 lines
3.0 KiB
Go
package crypto
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/subtle"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
)
|
|
|
|
// tokenByteLength ist die Länge eines erzeugten Tokens in Byte.
|
|
//
|
|
// 32 Byte entsprechen 256 Bit Entropie. Ein Erraten ist damit auch bei
|
|
// unbegrenzten Versuchen praktisch ausgeschlossen.
|
|
const tokenByteLength = 32
|
|
|
|
// GenerateToken erzeugt ein kryptografisch sicheres Token.
|
|
//
|
|
// Die Ausgabe ist URL-sicher kodiert und kann damit unverändert in Headern
|
|
// übertragen werden.
|
|
func GenerateToken() (string, error) {
|
|
tokenBytes := make([]byte, tokenByteLength)
|
|
if _, randomError := rand.Read(tokenBytes); randomError != nil {
|
|
return "", fmt.Errorf("es konnte kein sicheres token erzeugt werden: %w", randomError)
|
|
}
|
|
|
|
return base64.RawURLEncoding.EncodeToString(tokenBytes), nil
|
|
}
|
|
|
|
// HashToken bildet den Speicherwert eines Tokens.
|
|
//
|
|
// Gespeichert wird ausschließlich dieser Hash: ein Datenbankleck erlaubt damit
|
|
// keine Übernahme laufender Sitzungen. Anders als bei Passwörtern genügt hier
|
|
// SHA-256, weil ein Token bereits 256 Bit Zufall enthält und deshalb nicht
|
|
// erraten werden kann — die Rechenkosten von Argon2id hätten keinen Nutzen.
|
|
func HashToken(plainToken string) string {
|
|
tokenDigest := sha256.Sum256([]byte(plainToken))
|
|
return hex.EncodeToString(tokenDigest[:])
|
|
}
|
|
|
|
// TokensEqual vergleicht zwei Token-Hashes in konstanter Zeit.
|
|
//
|
|
// Ein zeichenweiser Vergleich würde über die Laufzeit verraten, wie viele
|
|
// Zeichen bereits übereinstimmen.
|
|
func TokensEqual(firstHash string, secondHash string) bool {
|
|
return subtle.ConstantTimeCompare([]byte(firstHash), []byte(secondHash)) == 1
|
|
}
|
|
|
|
// recoveryCodeByteLength ist die Länge eines Wiederherstellungscodes in Byte.
|
|
//
|
|
// 10 Byte ergeben 16 Zeichen in Base32 — kurz genug zum Abschreiben und mit
|
|
// 80 Bit Entropie nicht zu erraten.
|
|
const recoveryCodeByteLength = 10
|
|
|
|
// GenerateRecoveryCode erzeugt einen gut abschreibbaren Wiederherstellungscode.
|
|
//
|
|
// Verwendet wird ein Alphabet ohne leicht verwechselbare Zeichen (kein 0/O,
|
|
// kein 1/I), da diese Codes typischerweise ausgedruckt und abgetippt werden.
|
|
func GenerateRecoveryCode() (string, error) {
|
|
// Das Alphabet enthält 32 Zeichen, sodass 5 Bit je Zeichen kodiert werden.
|
|
const codeAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
|
|
|
randomBytes := make([]byte, recoveryCodeByteLength)
|
|
if _, randomError := rand.Read(randomBytes); randomError != nil {
|
|
return "", fmt.Errorf("es konnte kein sicherer wiederherstellungscode erzeugt werden: %w", randomError)
|
|
}
|
|
|
|
// Die Umwandlung nutzt jedes Byte modulo der Alphabetgröße. Da 256 kein
|
|
// Vielfaches von 32 ist, entstünde eine minimale Ungleichverteilung -
|
|
// bei 32 als Teiler von 256 ist sie jedoch exakt null.
|
|
codeCharacters := make([]byte, len(randomBytes))
|
|
for byteIndex, randomByte := range randomBytes {
|
|
codeCharacters[byteIndex] = codeAlphabet[int(randomByte)%len(codeAlphabet)]
|
|
}
|
|
|
|
// Die Gruppierung erleichtert das fehlerfreie Abtippen.
|
|
return fmt.Sprintf("%s-%s", codeCharacters[:5], codeCharacters[5:]), nil
|
|
}
|