- 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)
235 lines
4.5 KiB
Go
Executable File
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"
|
|
}
|