Files
dbbackup/internal/security/audit.go
Renz 0cf21cd893 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)
2025-11-25 15:25:56 +00:00

235 lines
4.5 KiB
Go
Executable File

package security
import (
"os"
"time"
"dbbackup/internal/logger"
)
// AuditEvent represents an auditable event
type AuditEvent struct {
Timestamp time.Time
User string
Action string
Resource string
Result string
Details map[string]interface{}
}
// AuditLogger provides audit logging functionality
type AuditLogger struct {
log logger.Logger
enabled bool
}
// NewAuditLogger creates a new audit logger
func NewAuditLogger(log logger.Logger, enabled bool) *AuditLogger {
return &AuditLogger{
log: log,
enabled: enabled,
}
}
// LogBackupStart logs backup operation start
func (a *AuditLogger) LogBackupStart(user, database, backupType string) {
if !a.enabled {
return
}
event := AuditEvent{
Timestamp: time.Now(),
User: user,
Action: "BACKUP_START",
Resource: database,
Result: "INITIATED",
Details: map[string]interface{}{
"backup_type": backupType,
},
}
a.logEvent(event)
}
// LogBackupComplete logs successful backup completion
func (a *AuditLogger) LogBackupComplete(user, database, archivePath string, sizeBytes int64) {
if !a.enabled {
return
}
event := AuditEvent{
Timestamp: time.Now(),
User: user,
Action: "BACKUP_COMPLETE",
Resource: database,
Result: "SUCCESS",
Details: map[string]interface{}{
"archive_path": archivePath,
"size_bytes": sizeBytes,
},
}
a.logEvent(event)
}
// LogBackupFailed logs backup failure
func (a *AuditLogger) LogBackupFailed(user, database string, err error) {
if !a.enabled {
return
}
event := AuditEvent{
Timestamp: time.Now(),
User: user,
Action: "BACKUP_FAILED",
Resource: database,
Result: "FAILURE",
Details: map[string]interface{}{
"error": err.Error(),
},
}
a.logEvent(event)
}
// LogRestoreStart logs restore operation start
func (a *AuditLogger) LogRestoreStart(user, database, archivePath string) {
if !a.enabled {
return
}
event := AuditEvent{
Timestamp: time.Now(),
User: user,
Action: "RESTORE_START",
Resource: database,
Result: "INITIATED",
Details: map[string]interface{}{
"archive_path": archivePath,
},
}
a.logEvent(event)
}
// LogRestoreComplete logs successful restore completion
func (a *AuditLogger) LogRestoreComplete(user, database string, duration time.Duration) {
if !a.enabled {
return
}
event := AuditEvent{
Timestamp: time.Now(),
User: user,
Action: "RESTORE_COMPLETE",
Resource: database,
Result: "SUCCESS",
Details: map[string]interface{}{
"duration_seconds": duration.Seconds(),
},
}
a.logEvent(event)
}
// LogRestoreFailed logs restore failure
func (a *AuditLogger) LogRestoreFailed(user, database string, err error) {
if !a.enabled {
return
}
event := AuditEvent{
Timestamp: time.Now(),
User: user,
Action: "RESTORE_FAILED",
Resource: database,
Result: "FAILURE",
Details: map[string]interface{}{
"error": err.Error(),
},
}
a.logEvent(event)
}
// LogConfigChange logs configuration changes
func (a *AuditLogger) LogConfigChange(user, setting, oldValue, newValue string) {
if !a.enabled {
return
}
event := AuditEvent{
Timestamp: time.Now(),
User: user,
Action: "CONFIG_CHANGE",
Resource: setting,
Result: "SUCCESS",
Details: map[string]interface{}{
"old_value": oldValue,
"new_value": newValue,
},
}
a.logEvent(event)
}
// LogConnectionAttempt logs database connection attempts
func (a *AuditLogger) LogConnectionAttempt(user, host string, success bool, err error) {
if !a.enabled {
return
}
result := "SUCCESS"
details := map[string]interface{}{
"host": host,
}
if !success {
result = "FAILURE"
if err != nil {
details["error"] = err.Error()
}
}
event := AuditEvent{
Timestamp: time.Now(),
User: user,
Action: "DB_CONNECTION",
Resource: host,
Result: result,
Details: details,
}
a.logEvent(event)
}
// logEvent writes the audit event to log
func (a *AuditLogger) logEvent(event AuditEvent) {
fields := map[string]interface{}{
"audit": true,
"timestamp": event.Timestamp.Format(time.RFC3339),
"user": event.User,
"action": event.Action,
"resource": event.Resource,
"result": event.Result,
}
// Merge event details
for k, v := range event.Details {
fields[k] = v
}
a.log.WithFields(fields).Info("AUDIT")
}
// GetCurrentUser returns the current system user
func GetCurrentUser() string {
if user := os.Getenv("USER"); user != "" {
return user
}
if user := os.Getenv("USERNAME"); user != "" {
return user
}
return "unknown"
}