feat: add config sync endpoint and security UI updates
- Add GET /api/v1/agents/:id/config endpoint for server configuration - Agent fetches config during check-in and applies updates - Add version tracking to prevent unnecessary config applications - Clean separation: config sync independent of commands - Fix agent UI subsystem settings to actually control agent behavior - Update Security Health UI with frosted glass styling and tooltips
This commit is contained in:
@@ -31,6 +31,10 @@ const (
|
||||
AgentVersion = "0.1.22" // v0.1.22: Machine binding and version enforcement security release
|
||||
)
|
||||
|
||||
var (
|
||||
lastConfigVersion int64 = 0 // Track last applied config version
|
||||
)
|
||||
|
||||
// getConfigPath returns the platform-specific config path
|
||||
func getConfigPath() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -452,6 +456,79 @@ func renewTokenIfNeeded(apiClient *client.Client, cfg *config.Config, err error)
|
||||
return apiClient, nil
|
||||
}
|
||||
|
||||
// syncServerConfig checks for and applies server configuration updates
|
||||
func syncServerConfig(apiClient *client.Client, cfg *config.Config) error {
|
||||
// Get current config from server
|
||||
serverConfig, err := apiClient.GetConfig(cfg.AgentID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get server config: %w", err)
|
||||
}
|
||||
|
||||
// Check if config version is newer
|
||||
if serverConfig.Version <= lastConfigVersion {
|
||||
return nil // No update needed
|
||||
}
|
||||
|
||||
log.Printf("📡 Server config update detected (version: %d)", serverConfig.Version)
|
||||
changes := false
|
||||
|
||||
// Apply subsystem configuration from server
|
||||
for subsystemName, subsystemConfig := range serverConfig.Subsystems {
|
||||
if configMap, ok := subsystemConfig.(map[string]interface{}); ok {
|
||||
enabled := false
|
||||
intervalMinutes := 0
|
||||
autoRun := false
|
||||
|
||||
if e, exists := configMap["enabled"]; exists {
|
||||
if eVal, ok := e.(bool); ok {
|
||||
enabled = eVal
|
||||
}
|
||||
}
|
||||
|
||||
if i, exists := configMap["interval_minutes"]; exists {
|
||||
if iVal, ok := i.(float64); ok {
|
||||
intervalMinutes = int(iVal)
|
||||
}
|
||||
}
|
||||
|
||||
if a, exists := configMap["auto_run"]; exists {
|
||||
if aVal, ok := a.(bool); ok {
|
||||
autoRun = aVal
|
||||
}
|
||||
}
|
||||
|
||||
// Only log changes if different from current state
|
||||
currentEnabled := cfg.Subsystems.APT.Enabled // We'd need to check actual current state here
|
||||
if enabled != currentEnabled {
|
||||
log.Printf(" → %s: enabled=%v (changed)", subsystemName, enabled)
|
||||
changes = true
|
||||
}
|
||||
|
||||
if intervalMinutes > 0 && intervalMinutes != cfg.CheckInInterval {
|
||||
log.Printf(" → %s: interval=%d minutes (changed)", subsystemName, intervalMinutes)
|
||||
changes = true
|
||||
// Note: For now, we use the check-in interval from server config
|
||||
cfg.CheckInInterval = intervalMinutes
|
||||
}
|
||||
|
||||
if autoRun {
|
||||
log.Printf(" → %s: auto_run=%v (server-side scheduling)", subsystemName, autoRun)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changes {
|
||||
log.Printf("✅ Server configuration applied successfully")
|
||||
} else {
|
||||
log.Printf("ℹ️ Server config received but no changes detected")
|
||||
}
|
||||
|
||||
// Update last config version
|
||||
lastConfigVersion = serverConfig.Version
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAgent(cfg *config.Config) error {
|
||||
log.Printf("🚩 RedFlag Agent v%s starting...\n", AgentVersion)
|
||||
log.Printf("==================================================================")
|
||||
@@ -670,6 +747,13 @@ func runAgent(cfg *config.Config) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Sync configuration from server (non-blocking)
|
||||
go func() {
|
||||
if err := syncServerConfig(apiClient, cfg); err != nil {
|
||||
log.Printf("Warning: Failed to sync server config: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
commands := response.Commands
|
||||
if len(commands) == 0 {
|
||||
log.Printf("Check-in successful - no new commands")
|
||||
|
||||
@@ -285,21 +285,59 @@ func handleScanDocker(apiClient *client.Client, cfg *config.Config, ackTracker *
|
||||
log.Printf("Failed to report scan log: %v\n", err)
|
||||
}
|
||||
|
||||
// Report updates to server if any were found
|
||||
if len(result.Updates) > 0 {
|
||||
report := client.UpdateReport{
|
||||
CommandID: commandID,
|
||||
Timestamp: time.Now(),
|
||||
Updates: result.Updates,
|
||||
// Report Docker images to server using dedicated endpoint
|
||||
// Get Docker scanner and use proper interface
|
||||
dockerScanner, err := orchestrator.NewDockerScanner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Docker scanner: %w", err)
|
||||
}
|
||||
defer dockerScanner.Close()
|
||||
|
||||
if dockerScanner.IsAvailable() {
|
||||
images, err := dockerScanner.ScanDocker()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan Docker images: %w", err)
|
||||
}
|
||||
|
||||
if err := apiClient.ReportUpdates(cfg.AgentID, report); err != nil {
|
||||
return fmt.Errorf("failed to report Docker updates: %w", err)
|
||||
}
|
||||
// Always report all Docker images (not just those with updates)
|
||||
if len(images) > 0 {
|
||||
// Convert DockerImage to DockerReportItem for API call
|
||||
imageItems := make([]client.DockerReportItem, 0, len(images))
|
||||
for _, image := range images {
|
||||
item := client.DockerReportItem{
|
||||
PackageType: "docker_image",
|
||||
PackageName: image.ImageName,
|
||||
CurrentVersion: image.ImageID,
|
||||
AvailableVersion: image.LatestImageID,
|
||||
Severity: image.Severity,
|
||||
RepositorySource: image.RepositorySource,
|
||||
Metadata: image.Metadata,
|
||||
}
|
||||
imageItems = append(imageItems, item)
|
||||
}
|
||||
|
||||
log.Printf("✓ Reported %d Docker image updates to server\n", len(result.Updates))
|
||||
report := client.DockerReport{
|
||||
CommandID: commandID,
|
||||
Timestamp: time.Now(),
|
||||
Images: imageItems,
|
||||
}
|
||||
|
||||
if err := apiClient.ReportDockerImages(cfg.AgentID, report); err != nil {
|
||||
return fmt.Errorf("failed to report Docker images: %w", err)
|
||||
}
|
||||
|
||||
updateCount := 0
|
||||
for _, image := range images {
|
||||
if image.HasUpdate {
|
||||
updateCount++
|
||||
}
|
||||
}
|
||||
log.Printf("✓ Reported %d Docker images (%d with updates) to server\n", len(images), updateCount)
|
||||
} else {
|
||||
log.Println("No Docker images found")
|
||||
}
|
||||
} else {
|
||||
log.Println("No Docker image updates found")
|
||||
log.Println("Docker not available on this system")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user