security: Implement HIGH priority security improvements
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
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"dbbackup/internal/backup"
|
||||
"dbbackup/internal/config"
|
||||
"dbbackup/internal/database"
|
||||
"dbbackup/internal/security"
|
||||
)
|
||||
|
||||
// runClusterBackup performs a full cluster backup
|
||||
@@ -28,15 +29,21 @@ func runClusterBackup(ctx context.Context) error {
|
||||
"port", cfg.Port,
|
||||
"backup_dir", cfg.BackupDir)
|
||||
|
||||
// Audit log: backup start
|
||||
user := security.GetCurrentUser()
|
||||
auditLogger.LogBackupStart(user, "all_databases", "cluster")
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
auditLogger.LogBackupFailed(user, "all_databases", err)
|
||||
return fmt.Errorf("failed to create database instance: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Connect to database
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
auditLogger.LogBackupFailed(user, "all_databases", err)
|
||||
return fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
@@ -45,9 +52,13 @@ func runClusterBackup(ctx context.Context) error {
|
||||
|
||||
// Perform cluster backup
|
||||
if err := engine.BackupCluster(ctx); err != nil {
|
||||
auditLogger.LogBackupFailed(user, "all_databases", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Audit log: backup success
|
||||
auditLogger.LogBackupComplete(user, "all_databases", cfg.BackupDir, 0)
|
||||
|
||||
// Save configuration for future use (unless disabled)
|
||||
if !cfg.NoSaveConfig {
|
||||
localCfg := config.ConfigFromConfig(cfg)
|
||||
@@ -55,6 +66,7 @@ func runClusterBackup(ctx context.Context) error {
|
||||
log.Warn("Failed to save configuration", "error", err)
|
||||
} else {
|
||||
log.Info("Configuration saved to .dbbackup.conf")
|
||||
auditLogger.LogConfigChange(user, "config_file", "", ".dbbackup.conf")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,25 +90,34 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
"port", cfg.Port,
|
||||
"backup_dir", cfg.BackupDir)
|
||||
|
||||
// Audit log: backup start
|
||||
user := security.GetCurrentUser()
|
||||
auditLogger.LogBackupStart(user, databaseName, "single")
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return fmt.Errorf("failed to create database instance: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Connect to database
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
// Verify database exists
|
||||
exists, err := db.DatabaseExists(ctx, databaseName)
|
||||
if err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return fmt.Errorf("failed to check if database exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("database '%s' does not exist", databaseName)
|
||||
err := fmt.Errorf("database '%s' does not exist", databaseName)
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create backup engine
|
||||
@@ -104,9 +125,13 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
|
||||
// Perform single database backup
|
||||
if err := engine.BackupSingle(ctx, databaseName); err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Audit log: backup success
|
||||
auditLogger.LogBackupComplete(user, databaseName, cfg.BackupDir, 0)
|
||||
|
||||
// Save configuration for future use (unless disabled)
|
||||
if !cfg.NoSaveConfig {
|
||||
localCfg := config.ConfigFromConfig(cfg)
|
||||
@@ -114,6 +139,7 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
log.Warn("Failed to save configuration", "error", err)
|
||||
} else {
|
||||
log.Info("Configuration saved to .dbbackup.conf")
|
||||
auditLogger.LogConfigChange(user, "config_file", "", ".dbbackup.conf")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,25 +185,34 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
"port", cfg.Port,
|
||||
"backup_dir", cfg.BackupDir)
|
||||
|
||||
// Audit log: backup start
|
||||
user := security.GetCurrentUser()
|
||||
auditLogger.LogBackupStart(user, databaseName, "sample")
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return fmt.Errorf("failed to create database instance: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Connect to database
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
// Verify database exists
|
||||
exists, err := db.DatabaseExists(ctx, databaseName)
|
||||
if err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return fmt.Errorf("failed to check if database exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("database '%s' does not exist", databaseName)
|
||||
err := fmt.Errorf("database '%s' does not exist", databaseName)
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create backup engine
|
||||
@@ -185,9 +220,13 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
|
||||
// Perform sample backup
|
||||
if err := engine.BackupSample(ctx, databaseName); err != nil {
|
||||
auditLogger.LogBackupFailed(user, databaseName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Audit log: backup success
|
||||
auditLogger.LogBackupComplete(user, databaseName, cfg.BackupDir, 0)
|
||||
|
||||
// Save configuration for future use (unless disabled)
|
||||
if !cfg.NoSaveConfig {
|
||||
localCfg := config.ConfigFromConfig(cfg)
|
||||
@@ -195,6 +234,7 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
log.Warn("Failed to save configuration", "error", err)
|
||||
} else {
|
||||
log.Info("Configuration saved to .dbbackup.conf")
|
||||
auditLogger.LogConfigChange(user, "config_file", "", ".dbbackup.conf")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"dbbackup/internal/database"
|
||||
"dbbackup/internal/restore"
|
||||
"dbbackup/internal/security"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -272,10 +273,19 @@ func runRestoreSingle(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// Execute restore
|
||||
log.Info("Starting restore...", "database", targetDB)
|
||||
|
||||
// Audit log: restore start
|
||||
user := security.GetCurrentUser()
|
||||
startTime := time.Now()
|
||||
auditLogger.LogRestoreStart(user, targetDB, archivePath)
|
||||
|
||||
if err := engine.RestoreSingle(ctx, archivePath, targetDB, restoreClean, restoreCreate); err != nil {
|
||||
auditLogger.LogRestoreFailed(user, targetDB, err)
|
||||
return fmt.Errorf("restore failed: %w", err)
|
||||
}
|
||||
|
||||
// Audit log: restore success
|
||||
auditLogger.LogRestoreComplete(user, targetDB, time.Since(startTime))
|
||||
|
||||
log.Info("✅ Restore completed successfully", "database", targetDB)
|
||||
return nil
|
||||
@@ -368,10 +378,19 @@ func runRestoreCluster(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// Execute cluster restore
|
||||
log.Info("Starting cluster restore...")
|
||||
|
||||
// Audit log: restore start
|
||||
user := security.GetCurrentUser()
|
||||
startTime := time.Now()
|
||||
auditLogger.LogRestoreStart(user, "all_databases", archivePath)
|
||||
|
||||
if err := engine.RestoreCluster(ctx, archivePath); err != nil {
|
||||
auditLogger.LogRestoreFailed(user, "all_databases", err)
|
||||
return fmt.Errorf("cluster restore failed: %w", err)
|
||||
}
|
||||
|
||||
// Audit log: restore success
|
||||
auditLogger.LogRestoreComplete(user, "all_databases", time.Since(startTime))
|
||||
|
||||
log.Info("✅ Cluster restore completed successfully")
|
||||
return nil
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
|
||||
"dbbackup/internal/config"
|
||||
"dbbackup/internal/logger"
|
||||
"dbbackup/internal/security"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg *config.Config
|
||||
log logger.Logger
|
||||
cfg *config.Config
|
||||
log logger.Logger
|
||||
auditLogger *security.AuditLogger
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
@@ -57,6 +59,9 @@ For help with specific commands, use: dbbackup [command] --help`,
|
||||
func Execute(ctx context.Context, config *config.Config, logger logger.Logger) error {
|
||||
cfg = config
|
||||
log = logger
|
||||
|
||||
// Initialize audit logger
|
||||
auditLogger = security.NewAuditLogger(logger, true)
|
||||
|
||||
// Set version info
|
||||
rootCmd.Version = fmt.Sprintf("%s (built: %s, commit: %s)",
|
||||
|
||||
Reference in New Issue
Block a user