WIP: Save current state - security subsystems, migrations, logging
This commit is contained in:
@@ -7,7 +7,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/aggregator/pkg/common"
|
||||
"github.com/Fimeg/RedFlag/aggregator-agent/internal/common"
|
||||
"github.com/Fimeg/RedFlag/aggregator-agent/internal/event"
|
||||
"github.com/Fimeg/RedFlag/aggregator-agent/internal/models"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// MigrationPlan represents a complete migration plan
|
||||
@@ -36,15 +39,60 @@ type MigrationResult struct {
|
||||
|
||||
// MigrationExecutor handles the execution of migration plans
|
||||
type MigrationExecutor struct {
|
||||
plan *MigrationPlan
|
||||
result *MigrationResult
|
||||
plan *MigrationPlan
|
||||
result *MigrationResult
|
||||
eventBuffer *event.Buffer
|
||||
agentID uuid.UUID
|
||||
stateManager *StateManager
|
||||
}
|
||||
|
||||
// NewMigrationExecutor creates a new migration executor
|
||||
func NewMigrationExecutor(plan *MigrationPlan) *MigrationExecutor {
|
||||
func NewMigrationExecutor(plan *MigrationPlan, configPath string) *MigrationExecutor {
|
||||
return &MigrationExecutor{
|
||||
plan: plan,
|
||||
result: &MigrationResult{},
|
||||
plan: plan,
|
||||
result: &MigrationResult{},
|
||||
stateManager: NewStateManager(configPath),
|
||||
}
|
||||
}
|
||||
|
||||
// NewMigrationExecutorWithEvents creates a new migration executor with event buffering
|
||||
func NewMigrationExecutorWithEvents(plan *MigrationPlan, eventBuffer *event.Buffer, agentID uuid.UUID, configPath string) *MigrationExecutor {
|
||||
return &MigrationExecutor{
|
||||
plan: plan,
|
||||
result: &MigrationResult{},
|
||||
eventBuffer: eventBuffer,
|
||||
agentID: agentID,
|
||||
stateManager: NewStateManager(configPath),
|
||||
}
|
||||
}
|
||||
|
||||
// bufferEvent buffers a migration failure event
|
||||
func (e *MigrationExecutor) bufferEvent(eventSubtype, severity, component, message string, metadata map[string]interface{}) {
|
||||
if e.eventBuffer == nil {
|
||||
return // Event buffering not enabled
|
||||
}
|
||||
|
||||
// Use agent ID if available
|
||||
var agentIDPtr *uuid.UUID
|
||||
if e.agentID != uuid.Nil {
|
||||
agentIDPtr = &e.agentID
|
||||
}
|
||||
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.New(),
|
||||
AgentID: agentIDPtr,
|
||||
EventType: "migration_failure",
|
||||
EventSubtype: eventSubtype,
|
||||
Severity: severity,
|
||||
Component: component,
|
||||
Message: message,
|
||||
Metadata: metadata,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Buffer the event (best effort)
|
||||
if err := e.eventBuffer.BufferEvent(event); err != nil {
|
||||
fmt.Printf("Warning: Failed to buffer migration event: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +106,13 @@ func (e *MigrationExecutor) ExecuteMigration() (*MigrationResult, error) {
|
||||
|
||||
// Phase 1: Create backups
|
||||
if err := e.createBackups(); err != nil {
|
||||
e.bufferEvent("backup_creation_failure", "error", "migration_executor",
|
||||
fmt.Sprintf("Backup creation failed: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"backup_path": e.plan.BackupPath,
|
||||
"phase": "backup_creation",
|
||||
})
|
||||
return e.completeMigration(false, fmt.Errorf("backup creation failed: %w", err))
|
||||
}
|
||||
e.result.AppliedChanges = append(e.result.AppliedChanges, "Created backups at "+e.plan.BackupPath)
|
||||
@@ -65,30 +120,69 @@ func (e *MigrationExecutor) ExecuteMigration() (*MigrationResult, error) {
|
||||
// Phase 2: Directory migration
|
||||
if contains(e.plan.Detection.RequiredMigrations, "directory_migration") {
|
||||
if err := e.migrateDirectories(); err != nil {
|
||||
e.bufferEvent("directory_migration_failure", "error", "migration_executor",
|
||||
fmt.Sprintf("Directory migration failed: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"phase": "directory_migration",
|
||||
})
|
||||
return e.completeMigration(false, fmt.Errorf("directory migration failed: %w", err))
|
||||
}
|
||||
e.result.AppliedChanges = append(e.result.AppliedChanges, "Migrated directories")
|
||||
|
||||
// Mark directory migration as completed
|
||||
if err := e.stateManager.MarkMigrationCompleted("directory_migration", e.plan.BackupPath, e.plan.TargetVersion); err != nil {
|
||||
fmt.Printf("[MIGRATION] Warning: Failed to mark directory migration as completed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Configuration migration
|
||||
if contains(e.plan.Detection.RequiredMigrations, "config_migration") {
|
||||
if err := e.migrateConfiguration(); err != nil {
|
||||
e.bufferEvent("configuration_migration_failure", "error", "migration_executor",
|
||||
fmt.Sprintf("Configuration migration failed: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"phase": "configuration_migration",
|
||||
})
|
||||
return e.completeMigration(false, fmt.Errorf("configuration migration failed: %w", err))
|
||||
}
|
||||
e.result.AppliedChanges = append(e.result.AppliedChanges, "Migrated configuration")
|
||||
|
||||
// Mark configuration migration as completed
|
||||
if err := e.stateManager.MarkMigrationCompleted("config_migration", e.plan.BackupPath, e.plan.TargetVersion); err != nil {
|
||||
fmt.Printf("[MIGRATION] Warning: Failed to mark configuration migration as completed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: Docker secrets migration (if available)
|
||||
if contains(e.plan.Detection.RequiredMigrations, "docker_secrets_migration") {
|
||||
if e.plan.Detection.DockerDetection == nil {
|
||||
e.bufferEvent("docker_migration_failure", "error", "migration_executor",
|
||||
"Docker secrets migration requested but detection data missing",
|
||||
map[string]interface{}{
|
||||
"error": "missing detection data",
|
||||
"phase": "docker_secrets_migration",
|
||||
})
|
||||
return e.completeMigration(false, fmt.Errorf("docker secrets migration requested but detection data missing"))
|
||||
}
|
||||
|
||||
dockerExecutor := NewDockerSecretsExecutor(e.plan.Detection.DockerDetection, e.plan.Config)
|
||||
if err := dockerExecutor.ExecuteDockerSecretsMigration(); err != nil {
|
||||
e.bufferEvent("docker_migration_failure", "error", "migration_executor",
|
||||
fmt.Sprintf("Docker secrets migration failed: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"phase": "docker_secrets_migration",
|
||||
})
|
||||
return e.completeMigration(false, fmt.Errorf("docker secrets migration failed: %w", err))
|
||||
}
|
||||
e.result.AppliedChanges = append(e.result.AppliedChanges, "Migrated to Docker secrets")
|
||||
|
||||
// Mark docker secrets migration as completed
|
||||
if err := e.stateManager.MarkMigrationCompleted("docker_secrets_migration", e.plan.BackupPath, e.plan.TargetVersion); err != nil {
|
||||
fmt.Printf("[MIGRATION] Warning: Failed to mark docker secrets migration as completed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 5: Security hardening
|
||||
@@ -98,11 +192,22 @@ func (e *MigrationExecutor) ExecuteMigration() (*MigrationResult, error) {
|
||||
fmt.Sprintf("Security hardening incomplete: %v", err))
|
||||
} else {
|
||||
e.result.AppliedChanges = append(e.result.AppliedChanges, "Applied security hardening")
|
||||
|
||||
// Mark security hardening as completed
|
||||
if err := e.stateManager.MarkMigrationCompleted("security_hardening", e.plan.BackupPath, e.plan.TargetVersion); err != nil {
|
||||
fmt.Printf("[MIGRATION] Warning: Failed to mark security hardening as completed: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 6: Validation
|
||||
if err := e.validateMigration(); err != nil {
|
||||
e.bufferEvent("migration_validation_failure", "error", "migration_executor",
|
||||
fmt.Sprintf("Migration validation failed: %v", err),
|
||||
map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"phase": "validation",
|
||||
})
|
||||
return e.completeMigration(false, fmt.Errorf("migration validation failed: %w", err))
|
||||
}
|
||||
|
||||
@@ -252,27 +357,78 @@ func (e *MigrationExecutor) collectAllFiles() []common.AgentFile {
|
||||
}
|
||||
|
||||
func (e *MigrationExecutor) backupFile(file common.AgentFile, backupPath string) error {
|
||||
relPath, err := filepath.Rel(e.plan.Config.OldConfigPath, file.Path)
|
||||
if err != nil {
|
||||
// Try relative to old state path
|
||||
relPath, err = filepath.Rel(e.plan.Config.OldStatePath, file.Path)
|
||||
if err != nil {
|
||||
relPath = filepath.Base(file.Path)
|
||||
// Check if file exists before attempting backup
|
||||
if _, err := os.Stat(file.Path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// File doesn't exist, log and skip
|
||||
fmt.Printf("[MIGRATION] [agent] [migration_executor] File does not exist, skipping backup: %s\n", file.Path)
|
||||
e.bufferEvent("backup_file_missing", "warning", "migration_executor",
|
||||
fmt.Sprintf("File does not exist, skipping backup: %s", file.Path),
|
||||
map[string]interface{}{
|
||||
"file_path": file.Path,
|
||||
"phase": "backup",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("migration: failed to stat file %s: %w", file.Path, err)
|
||||
}
|
||||
|
||||
// Clean paths to fix trailing slash issues
|
||||
cleanOldConfig := filepath.Clean(e.plan.Config.OldConfigPath)
|
||||
cleanOldState := filepath.Clean(e.plan.Config.OldStatePath)
|
||||
cleanPath := filepath.Clean(file.Path)
|
||||
var relPath string
|
||||
var err error
|
||||
|
||||
// Try to get relative path based on expected file location
|
||||
// If file is under old config path, use that as base
|
||||
if strings.HasPrefix(cleanPath, cleanOldConfig) {
|
||||
relPath, err = filepath.Rel(cleanOldConfig, cleanPath)
|
||||
if err != nil || strings.Contains(relPath, "..") {
|
||||
// Fallback to filename if path traversal or error
|
||||
relPath = filepath.Base(cleanPath)
|
||||
}
|
||||
} else if strings.HasPrefix(cleanPath, cleanOldState) {
|
||||
relPath, err = filepath.Rel(cleanOldState, cleanPath)
|
||||
if err != nil || strings.Contains(relPath, "..") {
|
||||
// Fallback to filename if path traversal or error
|
||||
relPath = filepath.Base(cleanPath)
|
||||
}
|
||||
} else {
|
||||
// File is not in expected old locations - use just the filename
|
||||
// This happens for files already in the new location
|
||||
relPath = filepath.Base(cleanPath)
|
||||
// Add subdirectory based on file type to avoid collisions
|
||||
switch {
|
||||
case ContainsAny(cleanPath, []string{"config.json", "agent.key", "server.key", "ca.crt"}):
|
||||
relPath = filepath.Join("config", relPath)
|
||||
case ContainsAny(cleanPath, []string{
|
||||
"pending_acks.json", "public_key.cache", "last_scan.json", "metrics.json"}):
|
||||
relPath = filepath.Join("state", relPath)
|
||||
}
|
||||
}
|
||||
|
||||
backupFilePath := filepath.Join(backupPath, relPath)
|
||||
// Ensure backup path is clean
|
||||
cleanBackupPath := filepath.Clean(backupPath)
|
||||
backupFilePath := filepath.Join(cleanBackupPath, relPath)
|
||||
backupFilePath = filepath.Clean(backupFilePath)
|
||||
backupDir := filepath.Dir(backupFilePath)
|
||||
|
||||
// Final safety check
|
||||
if strings.Contains(backupFilePath, "..") {
|
||||
return fmt.Errorf("migration: backup path contains parent directory reference: %s", backupFilePath)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(backupDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create backup directory: %w", err)
|
||||
return fmt.Errorf("migration: failed to create backup directory %s: %w", backupDir, err)
|
||||
}
|
||||
|
||||
// Copy file to backup location
|
||||
if err := copyFile(file.Path, backupFilePath); err != nil {
|
||||
return fmt.Errorf("failed to copy file to backup: %w", err)
|
||||
if err := copyFile(cleanPath, backupFilePath); err != nil {
|
||||
return fmt.Errorf("migration: failed to copy file to backup: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("[MIGRATION] [agent] [migration_executor] Successfully backed up: %s\n", cleanPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -349,6 +505,11 @@ func (e *MigrationExecutor) completeMigration(success bool, err error) (*Migrati
|
||||
if e.result.RollbackAvailable {
|
||||
fmt.Printf("[MIGRATION] 📦 Rollback available at: %s\n", e.result.BackupPath)
|
||||
}
|
||||
|
||||
// Clean up old directories after successful migration
|
||||
if err := e.stateManager.CleanupOldDirectories(); err != nil {
|
||||
fmt.Printf("[MIGRATION] Warning: Failed to cleanup old directories: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("[MIGRATION] ❌ Migration failed after %v\n", e.result.Duration)
|
||||
if len(e.result.Errors) > 0 {
|
||||
|
||||
Reference in New Issue
Block a user