feat: Complete MEDIUM priority security features with testing

- Implemented TUI auto-select for automated testing
- Fixed TUI automation: autoSelectMsg handling in Update()
- Auto-database selection in DatabaseSelector
- Created focused test suite (test_as_postgres.sh)
- Created retention policy test (test_retention.sh)
- All 10 security tests passing

Features validated:
 Backup retention policy (30 days, min backups)
 Rate limiting (exponential backoff)
 Privilege checks (root detection)
 Resource limit validation
 Path sanitization
 Checksum verification (SHA-256)
 Audit logging
 Secure permissions
 Configuration persistence
 TUI automation framework

Test results: 10/10 passed
Backup files created with .dump, .sha256, .info
Retention cleanup verified (old files removed)
This commit is contained in:
2025-11-25 15:25:56 +00:00
parent 86eee44d14
commit 0cf21cd893
77 changed files with 1319 additions and 4 deletions

0
internal/tui/archive_browser.go Normal file → Executable file
View File

0
internal/tui/backup_exec.go Normal file → Executable file
View File

0
internal/tui/backup_manager.go Normal file → Executable file
View File

0
internal/tui/confirmation.go Normal file → Executable file
View File

31
internal/tui/dbselector.go Normal file → Executable file
View File

@@ -84,6 +84,37 @@ func (m DatabaseSelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.databases = []string{"Error loading databases"}
} else {
m.databases = msg.databases
// Auto-select database if specified
if m.config.TUIAutoDatabase != "" {
for i, db := range m.databases {
if db == m.config.TUIAutoDatabase {
m.cursor = i
m.selected = db
m.logger.Info("Auto-selected database", "database", db)
// If sample backup, ask for ratio (or auto-use default)
if m.backupType == "sample" {
if m.config.TUIDryRun {
// In dry-run, use default ratio
executor := NewBackupExecution(m.config, m.logger, m.parent, m.ctx, m.backupType, m.selected, 10)
return executor, executor.Init()
}
inputModel := NewInputModel(m.config, m.logger, m,
"📊 Sample Ratio",
"Enter sample ratio (1-100):",
"10",
ValidateInt(1, 100))
return inputModel, nil
}
// For single backup, go directly to execution
executor := NewBackupExecution(m.config, m.logger, m.parent, m.ctx, m.backupType, m.selected, 0)
return executor, executor.Init()
}
}
m.logger.Warn("Auto-database not found in list", "requested", m.config.TUIAutoDatabase)
}
}
return m, nil

0
internal/tui/dirbrowser.go Normal file → Executable file
View File

0
internal/tui/dirpicker.go Normal file → Executable file
View File

0
internal/tui/history.go Normal file → Executable file
View File

0
internal/tui/input.go Normal file → Executable file
View File

52
internal/tui/menu.go Normal file → Executable file
View File

@@ -125,14 +125,66 @@ func (m *MenuModel) Close() error {
// Ensure MenuModel implements io.Closer
var _ io.Closer = (*MenuModel)(nil)
// autoSelectMsg is sent when auto-select should trigger
type autoSelectMsg struct{}
// Init initializes the model
func (m MenuModel) Init() tea.Cmd {
// Auto-select menu option if specified
if m.config.TUIAutoSelect >= 0 && m.config.TUIAutoSelect < len(m.choices) {
m.logger.Info("TUI Auto-select enabled", "option", m.config.TUIAutoSelect, "label", m.choices[m.config.TUIAutoSelect])
// Return command to trigger auto-selection
return func() tea.Msg {
return autoSelectMsg{}
}
}
return nil
}
// Update handles messages
func (m MenuModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case autoSelectMsg:
// Handle auto-selection
if m.config.TUIAutoSelect >= 0 && m.config.TUIAutoSelect < len(m.choices) {
m.cursor = m.config.TUIAutoSelect
m.logger.Info("Auto-selecting option", "cursor", m.cursor, "choice", m.choices[m.cursor])
// Trigger the selection based on cursor position
switch m.cursor {
case 0: // Single Database Backup
return m.handleSingleBackup()
case 1: // Sample Database Backup
return m.handleSampleBackup()
case 2: // Cluster Backup
return m.handleClusterBackup()
case 4: // Restore Single Database
return m.handleRestoreSingle()
case 5: // Restore Cluster Backup
return m.handleRestoreCluster()
case 6: // List & Manage Backups
return m.handleBackupManager()
case 8: // View Active Operations
return m.handleViewOperations()
case 9: // Show Operation History
return m.handleOperationHistory()
case 10: // Database Status
return m.handleStatus()
case 11: // Settings
return m.handleSettings()
case 12: // Clear History
m.message = "🗑️ History cleared"
case 13: // Quit
if m.cancel != nil {
m.cancel()
}
m.quitting = true
return m, tea.Quit
}
}
return m, nil
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":

0
internal/tui/operations.go Normal file → Executable file
View File

0
internal/tui/progress.go Normal file → Executable file
View File

0
internal/tui/restore_exec.go Normal file → Executable file
View File

0
internal/tui/restore_preview.go Normal file → Executable file
View File

0
internal/tui/settings.go Normal file → Executable file
View File

0
internal/tui/status.go Normal file → Executable file
View File