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>
201 lines
8.4 KiB
Go
201 lines
8.4 KiB
Go
// Package auth enthält Identität, Anmeldung, Sitzungen und rollenbasierte
|
||
// Zugriffssteuerung der Control Plane.
|
||
//
|
||
// Grundsätze (PROMPT.md §41–§45):
|
||
// - Berechtigungen werden immer serverseitig geprüft.
|
||
// - Fehlermeldungen der Anmeldung geben niemals preis, ob ein Konto existiert.
|
||
// - Ein zweiter Faktor ist für privilegierte Konten erzwingbar.
|
||
package auth
|
||
|
||
import (
|
||
"errors"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
// UserStatus beschreibt den Zustand eines Benutzerkontos.
|
||
type UserStatus string
|
||
|
||
const (
|
||
// UserStatusActive erlaubt die Anmeldung.
|
||
UserStatusActive UserStatus = "active"
|
||
// UserStatusDisabled verbietet die Anmeldung dauerhaft.
|
||
UserStatusDisabled UserStatus = "disabled"
|
||
// UserStatusLocked verbietet die Anmeldung bis zur Entsperrung.
|
||
UserStatusLocked UserStatus = "locked"
|
||
)
|
||
|
||
// User ist ein Benutzerkonto.
|
||
//
|
||
// Das Passwort erscheint hier bewusst nicht: der Hash verlässt die
|
||
// Datenzugriffsschicht nur dort, wo er zur Prüfung gebraucht wird.
|
||
type User struct {
|
||
// ID ist der öffentliche Bezeichner.
|
||
ID uuid.UUID `json:"id"`
|
||
// Username ist der Anmeldename.
|
||
Username string `json:"username"`
|
||
// Email ist die optionale Mailadresse.
|
||
Email string `json:"email,omitempty"`
|
||
// Status ist der Zustand des Kontos.
|
||
Status UserStatus `json:"status"`
|
||
// MFAEnabled meldet, ob ein bestätigter zweiter Faktor vorliegt.
|
||
MFAEnabled bool `json:"mfa_enabled"`
|
||
// Roles sind die zugewiesenen Rollennamen.
|
||
Roles []string `json:"roles"`
|
||
// Permissions sind die aus den Rollen abgeleiteten Berechtigungen.
|
||
Permissions []string `json:"permissions"`
|
||
// CreatedAt ist der Anlagezeitpunkt in UTC.
|
||
CreatedAt time.Time `json:"created_at"`
|
||
// UpdatedAt ist der Zeitpunkt der letzten Änderung in UTC.
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
// LastLoginAt ist der Zeitpunkt der letzten erfolgreichen Anmeldung.
|
||
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
||
}
|
||
|
||
// HasPermission meldet, ob der Benutzer eine Berechtigung besitzt.
|
||
func (user User) HasPermission(requiredPermission string) bool {
|
||
for _, grantedPermission := range user.Permissions {
|
||
if grantedPermission == requiredPermission {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// IsPrivileged meldet, ob das Konto weitreichende Rechte besitzt.
|
||
//
|
||
// Für solche Konten kann ein zweiter Faktor erzwungen werden (PROMPT.md §43).
|
||
func (user User) IsPrivileged() bool {
|
||
// Schreibrechte an Benutzern, Rollen oder Sicherheitseinstellungen erlauben
|
||
// es, sich selbst weitere Rechte zu verschaffen.
|
||
for _, privilegedPermission := range []string{"users.write", "roles.write", "security.write"} {
|
||
if user.HasPermission(privilegedPermission) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// Role ist eine Rolle der Zugriffssteuerung.
|
||
type Role struct {
|
||
// ID ist der Bezeichner der Rolle.
|
||
ID uuid.UUID `json:"id"`
|
||
// Name ist der eindeutige technische Name.
|
||
Name string `json:"name"`
|
||
// Description erklärt den Zweck der Rolle.
|
||
Description string `json:"description,omitempty"`
|
||
// IsSystem kennzeichnet mitgelieferte Rollen, die nicht gelöscht werden dürfen.
|
||
IsSystem bool `json:"is_system"`
|
||
// Permissions sind die zugeordneten Berechtigungen.
|
||
Permissions []string `json:"permissions"`
|
||
}
|
||
|
||
// Permission ist eine Einzelberechtigung.
|
||
type Permission struct {
|
||
// Name ist der technische Name im Format bereich.aktion.
|
||
Name string `json:"name"`
|
||
// Description erklärt, was die Berechtigung erlaubt.
|
||
Description string `json:"description"`
|
||
}
|
||
|
||
// Session ist eine aktive Anmeldesitzung.
|
||
type Session struct {
|
||
// ID ist der Bezeichner der Sitzung.
|
||
ID uuid.UUID
|
||
// UserID ist der angemeldete Benutzer.
|
||
UserID uuid.UUID
|
||
// AccessExpiresAt ist die Ablaufzeit des Zugriffstokens in UTC.
|
||
AccessExpiresAt time.Time
|
||
// RefreshExpiresAt ist die Ablaufzeit des Erneuerungstokens in UTC.
|
||
RefreshExpiresAt time.Time
|
||
}
|
||
|
||
// TokenPair sind die bei einer Anmeldung ausgegebenen Tokens.
|
||
//
|
||
// Die Klartextwerte existieren ausschließlich in dieser Struktur und in der
|
||
// Antwort an den Aufrufer; gespeichert wird nur ihr Hash.
|
||
type TokenPair struct {
|
||
// AccessToken ist das kurzlebige Zugriffstoken.
|
||
AccessToken string `json:"access_token"`
|
||
// RefreshToken ist das langlebigere Erneuerungstoken.
|
||
RefreshToken string `json:"refresh_token"`
|
||
// AccessExpiresAt ist die Ablaufzeit des Zugriffstokens in UTC.
|
||
AccessExpiresAt time.Time `json:"access_expires_at"`
|
||
// RefreshExpiresAt ist die Ablaufzeit des Erneuerungstokens in UTC.
|
||
RefreshExpiresAt time.Time `json:"refresh_expires_at"`
|
||
}
|
||
|
||
// LoginResult ist das Ergebnis eines Anmeldeversuchs.
|
||
//
|
||
// Entweder ist die Anmeldung abgeschlossen (Tokens gesetzt) oder es fehlt noch
|
||
// der zweite Faktor (MFARequired gesetzt). Beides gleichzeitig gibt es nicht.
|
||
type LoginResult struct {
|
||
// MFARequired meldet, dass noch ein zweiter Faktor nötig ist.
|
||
MFARequired bool `json:"mfa_required"`
|
||
// ChallengeID benennt die offene MFA-Herausforderung.
|
||
ChallengeID *uuid.UUID `json:"challenge_id,omitempty"`
|
||
// Tokens sind die ausgegebenen Tokens, sofern die Anmeldung abgeschlossen ist.
|
||
Tokens *TokenPair `json:"tokens,omitempty"`
|
||
// User ist der angemeldete Benutzer, sofern die Anmeldung abgeschlossen ist.
|
||
User *User `json:"user,omitempty"`
|
||
}
|
||
|
||
// MFAEnrollment sind die Daten zur Einrichtung eines zweiten Faktors.
|
||
//
|
||
// Secret und Wiederherstellungscodes werden genau einmal ausgeliefert und
|
||
// danach nie wieder abrufbar (PROMPT.md §43).
|
||
type MFAEnrollment struct {
|
||
// Secret ist das TOTP-Secret in Base32.
|
||
Secret string `json:"secret"`
|
||
// ProvisioningURI ist die otpauth-URI für Authenticator-Apps.
|
||
ProvisioningURI string `json:"provisioning_uri"`
|
||
// RecoveryCodes sind die einmalig verwendbaren Wiederherstellungscodes.
|
||
RecoveryCodes []string `json:"recovery_codes"`
|
||
}
|
||
|
||
// Fehler der Anmeldung.
|
||
//
|
||
// ErrInvalidCredentials wird bewusst für unbekannte Benutzer und falsche
|
||
// Passwörter gleichermaßen verwendet: eine Unterscheidung erlaubte es, gültige
|
||
// Anmeldenamen zu ermitteln (Benutzer-Enumeration).
|
||
var (
|
||
// ErrInvalidCredentials meldet falsche Anmeldedaten.
|
||
ErrInvalidCredentials = errors.New("benutzername oder passwort ist falsch")
|
||
// ErrAccountLocked meldet ein wegen zu vieler Fehlversuche gesperrtes Konto.
|
||
ErrAccountLocked = errors.New("das konto ist vorübergehend gesperrt")
|
||
// ErrAccountDisabled meldet ein deaktiviertes Konto.
|
||
ErrAccountDisabled = errors.New("das konto ist deaktiviert")
|
||
// ErrInvalidMFACode meldet einen falschen Code des zweiten Faktors.
|
||
ErrInvalidMFACode = errors.New("der code ist falsch oder bereits verwendet")
|
||
// ErrChallengeNotFound meldet eine unbekannte, abgelaufene oder bereits
|
||
// eingelöste MFA-Herausforderung.
|
||
ErrChallengeNotFound = errors.New("die anmeldung ist abgelaufen, bitte erneut anmelden")
|
||
// ErrChallengeAttemptsExceeded meldet zu viele Fehlversuche beim zweiten Faktor.
|
||
ErrChallengeAttemptsExceeded = errors.New("zu viele fehlversuche, bitte erneut anmelden")
|
||
// ErrSessionInvalid meldet ein unbekanntes, abgelaufenes oder widerrufenes Token.
|
||
ErrSessionInvalid = errors.New("die sitzung ist ungültig oder abgelaufen")
|
||
// ErrUserNotFound meldet einen nicht vorhandenen Benutzer.
|
||
ErrUserNotFound = errors.New("der benutzer existiert nicht")
|
||
// ErrUsernameTaken meldet einen bereits vergebenen Anmeldenamen.
|
||
ErrUsernameTaken = errors.New("der benutzername ist bereits vergeben")
|
||
// ErrEmailTaken meldet eine bereits vergebene Mailadresse.
|
||
ErrEmailTaken = errors.New("die mailadresse ist bereits vergeben")
|
||
// ErrRoleNotFound meldet eine nicht vorhandene Rolle.
|
||
ErrRoleNotFound = errors.New("die rolle existiert nicht")
|
||
// ErrSystemRoleImmutable meldet den Versuch, eine mitgelieferte Rolle zu ändern.
|
||
ErrSystemRoleImmutable = errors.New("mitgelieferte rollen können nicht geändert oder gelöscht werden")
|
||
// ErrRoleInUse meldet den Versuch, eine zugewiesene Rolle zu löschen.
|
||
ErrRoleInUse = errors.New("die rolle ist noch Benutzern zugewiesen")
|
||
// ErrMFAAlreadyEnabled meldet einen bereits eingerichteten zweiten Faktor.
|
||
ErrMFAAlreadyEnabled = errors.New("für dieses konto ist bereits ein zweiter faktor eingerichtet")
|
||
// ErrMFANotEnrolled meldet einen fehlenden zweiten Faktor.
|
||
ErrMFANotEnrolled = errors.New("für dieses konto ist kein zweiter faktor eingerichtet")
|
||
// ErrLastAdministrator verhindert das Entfernen des letzten Administrators.
|
||
ErrLastAdministrator = errors.New("der letzte administrator kann nicht entfernt oder entrechtet werden")
|
||
// ErrWeakPassword meldet ein zu schwaches Passwort.
|
||
ErrWeakPassword = errors.New("das passwort erfüllt die mindestanforderungen nicht")
|
||
)
|