HIGH Priority Security Features: - Path sanitization with filepath.Clean() for all user paths - Path traversal attack prevention in backup/restore operations - Secure config file permissions (0600 instead of 0644) - SHA-256 checksum generation for all backup archives - Checksum verification during restore operations - Comprehensive audit logging for compliance New Security Module (internal/security/): - paths.go: ValidateBackupPath() and ValidateArchivePath() - checksum.go: ChecksumFile(), VerifyChecksum(), LoadAndVerifyChecksum() - audit.go: AuditLogger with structured event tracking Integration Points: - Backup engine: Path validation, checksum generation - Restore engine: Path validation, checksum verification - All backup/restore operations: Audit logging - Configuration saves: Audit logging Security Enhancements: - .dbbackup.conf now created with 0600 permissions (owner-only) - All archive files get .sha256 checksum files - Restore warns if checksum verification fails but continues - Audit events logged for all administrative operations - User tracking via $USER/$USERNAME environment variables Compliance Features: - Audit trail for backups, restores, config changes - Structured logging with timestamps, users, actions, results - Event details include paths, sizes, durations, errors Testing: - All code compiles successfully - Cross-platform build verified - Ready for integration testing
118 lines
2.6 KiB
Go
118 lines
2.6 KiB
Go
package tui
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
|
||
tea "github.com/charmbracelet/bubbletea"
|
||
|
||
"dbbackup/internal/config"
|
||
"dbbackup/internal/logger"
|
||
)
|
||
|
||
// ConfirmationModel for yes/no confirmations
|
||
type ConfirmationModel struct {
|
||
config *config.Config
|
||
logger logger.Logger
|
||
parent tea.Model
|
||
ctx context.Context
|
||
title string
|
||
message string
|
||
cursor int
|
||
choices []string
|
||
confirmed bool
|
||
onConfirm func() (tea.Model, tea.Cmd) // Callback when confirmed
|
||
}
|
||
|
||
func NewConfirmationModel(cfg *config.Config, log logger.Logger, parent tea.Model, title, message string) ConfirmationModel {
|
||
return ConfirmationModel{
|
||
config: cfg,
|
||
logger: log,
|
||
parent: parent,
|
||
title: title,
|
||
message: message,
|
||
choices: []string{"Yes", "No"},
|
||
}
|
||
}
|
||
|
||
func NewConfirmationModelWithAction(cfg *config.Config, log logger.Logger, parent tea.Model, title, message string, onConfirm func() (tea.Model, tea.Cmd)) ConfirmationModel {
|
||
return ConfirmationModel{
|
||
config: cfg,
|
||
logger: log,
|
||
parent: parent,
|
||
title: title,
|
||
message: message,
|
||
choices: []string{"Yes", "No"},
|
||
onConfirm: onConfirm,
|
||
}
|
||
}
|
||
|
||
func (m ConfirmationModel) Init() tea.Cmd {
|
||
return nil
|
||
}
|
||
|
||
func (m ConfirmationModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||
switch msg := msg.(type) {
|
||
case tea.KeyMsg:
|
||
switch msg.String() {
|
||
case "ctrl+c", "q", "esc", "n":
|
||
return m.parent, nil
|
||
|
||
case "left", "h":
|
||
if m.cursor > 0 {
|
||
m.cursor--
|
||
}
|
||
|
||
case "right", "l":
|
||
if m.cursor < len(m.choices)-1 {
|
||
m.cursor++
|
||
}
|
||
|
||
case "enter", "y":
|
||
if msg.String() == "y" || m.cursor == 0 {
|
||
m.confirmed = true
|
||
// Execute the onConfirm callback if provided
|
||
if m.onConfirm != nil {
|
||
return m.onConfirm()
|
||
}
|
||
// Default: execute cluster backup for backward compatibility
|
||
executor := NewBackupExecution(m.config, m.logger, m.parent, m.ctx, "cluster", "", 0)
|
||
return executor, executor.Init()
|
||
}
|
||
return m.parent, nil
|
||
}
|
||
}
|
||
|
||
return m, nil
|
||
}
|
||
|
||
func (m ConfirmationModel) View() string {
|
||
var s strings.Builder
|
||
|
||
header := titleStyle.Render(m.title)
|
||
s.WriteString(fmt.Sprintf("\n%s\n\n", header))
|
||
|
||
s.WriteString(fmt.Sprintf("%s\n\n", m.message))
|
||
|
||
// Show choices
|
||
for i, choice := range m.choices {
|
||
cursor := " "
|
||
if m.cursor == i {
|
||
cursor = ">"
|
||
s.WriteString(selectedStyle.Render(fmt.Sprintf("%s [%s]", cursor, choice)))
|
||
} else {
|
||
s.WriteString(fmt.Sprintf("%s [%s]", cursor, choice))
|
||
}
|
||
s.WriteString(" ")
|
||
}
|
||
|
||
s.WriteString("\n\n⌨️ ←/→: Select • Enter/y: Confirm • n/ESC: Cancel\n")
|
||
|
||
return s.String()
|
||
}
|
||
|
||
func (m ConfirmationModel) IsConfirmed() bool {
|
||
return m.confirmed
|
||
}
|