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:
234
internal/security/audit.go
Normal file
234
internal/security/audit.go
Normal file
@@ -0,0 +1,234 @@
|
||||
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"
|
||||
}
|
||||
Reference in New Issue
Block a user