18 KiB
RedFlag Agent Command Handling System - Architecture Analysis
Executive Summary
The agent implements a modular but primarily monolithic scanning architecture. While scanner implementations are isolated into separate files, the orchestration of scanning (the handleScanUpdates function) is a large, tightly-coupled function that combines all subsystems in a single control flow. Storage and system info gathering are separate, but not formally separated as distinct subsystems that can be independently managed.
1. Command Processing Pipeline
Entry Point: Main Agent Loop
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/cmd/agent/main.go
Lines: 410-549 (main check-in loop)
The agent continuously loops, checking in with the server and processing commands:
for {
// Lines 412-414: Add jitter
jitter := time.Duration(rand.Intn(30)) * time.Second
time.Sleep(jitter)
// Lines 417-425: System info update every hour
if time.Since(lastSystemInfoUpdate) >= systemInfoUpdateInterval {
// Call reportSystemInfo()
}
// Lines 465-490: GetCommands from server with optional metrics
commands, err := apiClient.GetCommands(cfg.AgentID, metrics)
// Lines 499-544: Switch on command type
for _, cmd := range commands {
switch cmd.Type {
case "scan_updates":
handleScanUpdates(...)
case "collect_specs":
case "dry_run_update":
case "install_updates":
case "confirm_dependencies":
case "enable_heartbeat":
case "disable_heartbeat":
case "reboot":
}
}
// Line 547: Wait for next check-in
time.Sleep(...)
}
Command Types Supported
- scan_updates - Main focus (lines 503-506)
- collect_specs (not implemented)
- dry_run_update (lines 511-514)
- install_updates (lines 516-519)
- confirm_dependencies (lines 521-524)
- enable_heartbeat (lines 526-529)
- disable_heartbeat (lines 531-534)
- reboot (lines 537-540)
2. MONOLITHIC scan_updates Implementation
Location and Size
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/cmd/agent/main.go
Function: handleScanUpdates()
Lines: 551-709 (159 lines)
The Monolith Problem
The function is a single, large, sequential orchestrator that tightly couples all scanning subsystems:
handleScanUpdates()
├─ APT Scanner (lines 559-574)
│ ├─ IsAvailable() check
│ ├─ Scan()
│ └─ Error handling + accumulation
│
├─ DNF Scanner (lines 576-592)
│ ├─ IsAvailable() check
│ ├─ Scan()
│ └─ Error handling + accumulation
│
├─ Docker Scanner (lines 594-610)
│ ├─ IsAvailable() check
│ ├─ Scan()
│ └─ Error handling + accumulation
│
├─ Windows Update Scanner (lines 612-628)
│ ├─ IsAvailable() check
│ ├─ Scan()
│ └─ Error handling + accumulation
│
├─ Winget Scanner (lines 630-646)
│ ├─ IsAvailable() check
│ ├─ Scan()
│ └─ Error handling + accumulation
│
├─ Report Building (lines 648-677)
│ ├─ Combine all errors
│ ├─ Build scan log report
│ └─ Report to server
│
└─ Update Reporting (lines 686-708)
├─ Report updates if found
└─ Return errors
Key Issues with Current Architecture
- No Abstraction Layer: Each scanner is called directly with repeated
if available -> scan -> handle errorblocks - Sequential Execution: All scanners run one-by-one (lines 559-646) - no parallelization
- Tight Coupling: Error handling logic is mixed with business logic
- No Subsystem State Management: Cannot track individual subsystem health or readiness
- Repeated Code: Same pattern repeated 5 times for different scanners
Code Pattern Repetition (Example - APT):
// Lines 559-574: APT pattern
if aptScanner.IsAvailable() {
log.Println(" - Scanning APT packages...")
updates, err := aptScanner.Scan()
if err != nil {
errorMsg := fmt.Sprintf("APT scan failed: %v", err)
log.Printf(" %s\n", errorMsg)
scanErrors = append(scanErrors, errorMsg)
} else {
resultMsg := fmt.Sprintf("Found %d APT updates", len(updates))
log.Printf(" %s\n", resultMsg)
scanResults = append(scanResults, resultMsg)
allUpdates = append(allUpdates, updates...)
}
} else {
scanResults = append(scanResults, "APT scanner not available")
}
This exact pattern repeats for DNF, Docker, Windows, and Winget scanners.
3. Scanner Implementations (Modular)
3.1 APT Scanner
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/internal/scanner/apt.go
Lines: 1-91
Interface Implementation:
IsAvailable()- Checks ifaptcommand exists (line 23-26)Scan()- Returns[]client.UpdateReportItem(lines 29-42)parseAPTOutput()- Helper function (lines 44-90)
Key Behavior:
- Runs
apt-get update(optional, line 31) - Runs
apt list --upgradable(line 35) - Parses output with regex (line 50)
- Determines severity based on repository name (lines 69-71)
Severity Logic:
severity := "moderate"
if strings.Contains(repository, "security") {
severity = "important"
}
3.2 DNF Scanner
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/internal/scanner/dnf.go
Lines: 1-157
Interface Implementation:
IsAvailable()- Checks ifdnfcommand exists (lines 23-26)Scan()- Returns[]client.UpdateReportItem(lines 29-43)parseDNFOutput()- Parses output (lines 45-108)getInstalledVersion()- Queries RPM (lines 111-118)determineSeverity()- Complex logic (lines 121-157)
Severity Determination (lines 121-157):
- Security keywords: critical
- Kernel updates: important
- Core system packages (glibc, systemd, bash): important
- Development tools: moderate
- Default: low
3.3 Docker Scanner
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/internal/scanner/docker.go
Lines: 1-163
Interface Implementation:
IsAvailable()- Checks docker command + daemon ping (lines 34-47)Scan()- Returns[]client.UpdateReportItem(lines 50-123)checkForUpdate()- Compare local vs remote digests (lines 137-154)Close()- Close Docker client (lines 157-162)
Key Behavior:
- Lists all containers (line 54)
- Gets image inspect details (line 72)
- Calls registry client for remote digest (line 86)
- Compares digest hashes to detect updates (line 151)
RegistryClient Subsystem (registry.go, lines 1-260):
- Handles Docker Registry HTTP API v2
- Caches manifest responses (5 min TTL)
- Parses image names into registry/repository
- Gets authentication tokens for Docker Hub
- Supports manifest digest extraction
3.4 Windows Update Scanner (WUA API)
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/internal/scanner/windows_wua.go
Lines: 1-553
Interface Implementation:
IsAvailable()- Returns true only on Windows (lines 27-30)Scan()- Returns[]client.UpdateReportItem(lines 33-67)- Windows-specific COM integration (lines 38-43)
- Conversion methods (lines 70-211)
Key Behavior:
- Initializes COM for Windows Update Agent API (lines 38-43)
- Creates update session and searcher (lines 46-55)
- Searches with criteria:
"IsInstalled=0 AND IsHidden=0"(line 58) - Converts WUA results with rich metadata (lines 90-211)
Metadata Extraction (lines 112-186):
- KB articles
- Update identity
- Security bulletins (includes CVEs)
- MSRC severity
- Download size
- Deployment dates
- More info URLs
- Release notes
- Categories
Severity Mapping (lines 463-479):
- MSRC critical/important → critical
- MSRC moderate → moderate
- MSRC low → low
3.5 Winget Scanner
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/internal/scanner/winget.go
Lines: 1-662
Interface Implementation:
IsAvailable()- Windows-only, checks winget command (lines 34-43)Scan()- Multi-method with fallbacks (lines 46-84)- Multiple scan methods for resilience (lines 87-178)
- Package parsing (lines 279-508)
Key Behavior - Multiple Scan Methods:
- Method 1:
scanWithJSON()- Primary, JSON output (lines 87-122) - Method 2:
scanWithBasicOutput()- Fallback, text parsing (lines 125-134) - Method 3:
attemptWingetRecovery()- Recovery procedures (lines 533-576)
Recovery Procedures (lines 533-576):
- Reset winget sources
- Update winget itself
- Repair Windows App Installer
- Scan with admin privileges
Severity Determination (lines 324-371):
- Security tools: critical
- Browsers/communication: high
- Development tools: moderate
- Microsoft Store apps: low
- Default: moderate
Package Categorization (lines 374-484):
- Development, Security, Browser, Communication, Media, Productivity, Utility, Gaming, Application
4. System Info and Storage Integration
4.1 System Info Collection
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/internal/system/info.go
Lines: 1-100+ (first 100 shown)
SystemInfo Structure (lines 13-28):
type SystemInfo struct {
Hostname string
OSType string
OSVersion string
OSArchitecture string
AgentVersion string
IPAddress string
CPUInfo CPUInfo
MemoryInfo MemoryInfo
DiskInfo []DiskInfo // MODULAR: Multiple disks!
RunningProcesses int
Uptime string
RebootRequired bool
RebootReason string
Metadata map[string]string
}
DiskInfo Structure (lines 45-57):
type DiskInfo struct {
Mountpoint string
Total uint64
Available uint64
Used uint64
UsedPercent float64
Filesystem string
IsRoot bool // Primary system disk
IsLargest bool // Largest storage disk
DiskType string // SSD, HDD, NVMe, etc.
Device string // Block device name
}
4.2 System Info Reporting
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/cmd/agent/main.go
Function: reportSystemInfo()
Lines: 1357-1407
Reporting Frequency:
- Lines 407-408:
const systemInfoUpdateInterval = 1 * time.Hour - Lines 417-425: Updates hourly during main loop
What Gets Reported:
- CPU model, cores, threads
- Memory total/used/percent
- Disk total/used/percent (primary disk)
- IP address
- Process count
- Uptime
- OS type/version/architecture
- All metadata from SystemInfo
4.3 Local Cache Subsystem
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/internal/cache/local.go
Key Functions:
Load()- Load cache from diskUpdateScanResults()- Store latest scan resultsSetAgentInfo()- Store agent metadataSetAgentStatus()- Update statusSave()- Persist cache to disk
5. Lightweight Metrics vs Full System Info
5.1 Lightweight Metrics (Every Check-in)
File: /home/memory/Desktop/Projects/RedFlag/aggregator-agent/cmd/agent/main.go
Lines: 429-444
What Gets Collected Every Check-in:
sysMetrics, err := system.GetLightweightMetrics()
if err == nil {
metrics = &client.SystemMetrics{
CPUPercent: sysMetrics.CPUPercent,
MemoryPercent: sysMetrics.MemoryPercent,
MemoryUsedGB: sysMetrics.MemoryUsedGB,
MemoryTotalGB: sysMetrics.MemoryTotalGB,
DiskUsedGB: sysMetrics.DiskUsedGB,
DiskTotalGB: sysMetrics.DiskTotalGB,
DiskPercent: sysMetrics.DiskPercent,
Uptime: sysMetrics.Uptime,
Version: AgentVersion,
}
}
5.2 Full System Info (Hourly)
Lines: 417-425 + reportSystemInfo function
Difference: Full info includes CPU model, detailed disk info, process count, IP address, and more detailed metadata
6. Current Modularity Assessment
Modular (Good):
- Scanner Implementations: Each scanner is a separate file with its own logic
- Registry Client: Docker registry communication is separated
- System Info: Platform-specific implementations split (windows.go, windows_stub.go, windows_wua.go, etc.)
- Installers: Separate installer implementations per package type
- Local Cache: Separate subsystem for caching
Monolithic (Bad):
- handleScanUpdates(): Tight coupling of all scanners in one function
- Command Processing: All command types in a single switch statement
- Error Aggregation: No formal error handling subsystem; just accumulates strings
- No Subsystem Health Tracking: Can't individually monitor scanner status
- No Parallelization: Scanners run sequentially, wasting time
- Logging Mixed with Logic: Log statements interleaved with business logic
7. Key Data Flow Paths
Path 1: scan_updates Command
GetCommands()
↓
switch cmd.Type == "scan_updates"
↓
handleScanUpdates()
├─ aptScanner.Scan() → UpdateReportItem[]
├─ dnfScanner.Scan() → UpdateReportItem[]
├─ dockerScanner.Scan() → UpdateReportItem[] (includes registryClient)
├─ windowsUpdateScanner.Scan() → UpdateReportItem[]
├─ wingetScanner.Scan() → UpdateReportItem[] (with recovery procedures)
├─ Combine all updates
├─ ReportLog() [scan summary]
└─ ReportUpdates() [actual updates]
Path 2: Local Scan via CLI
Lines: 712-805, handleScanCommand()
- Same scanner initialization and execution
- Save results to cache
- Display via display.PrintScanResults()
Path 3: System Metrics Reporting
Main Loop (every check-in)
├─ GetLightweightMetrics() [every 5-300 sec]
└─ Every hour:
├─ GetSystemInfo() [detailed]
├─ ReportSystemInfo() [to server]
8. File Structure Summary
Core Agent
aggregator-agent/
├── cmd/agent/
│ └── main.go [ENTRY POINT - 1510 lines]
│ ├─ registerAgent() [266-348]
│ ├─ runAgent() [387-549] [MAIN LOOP]
│ ├─ handleScanUpdates() [551-709] [MONOLITHIC]
│ ├─ handleScanCommand() [712-805]
│ ├─ handleStatusCommand() [808-846]
│ ├─ handleListUpdatesCommand() [849-871]
│ ├─ handleInstallUpdates() [873-989]
│ ├─ handleDryRunUpdate() [992-1105]
│ ├─ handleConfirmDependencies() [1108-1216]
│ ├─ handleEnableHeartbeat() [1219-1291]
│ ├─ handleDisableHeartbeat() [1294-1355]
│ ├─ reportSystemInfo() [1357-1407]
│ └─ handleReboot() [1410-1495]
Scanners
internal/scanner/
├── apt.go [91 lines] - APT package manager
├── dnf.go [157 lines] - DNF/RPM package manager
├── docker.go [163 lines] - Docker image scanning
├── registry.go [260 lines] - Docker Registry API client
├── windows.go [Stub for non-Windows]
├── windows_wua.go [553 lines] - Windows Update Agent API
├── winget.go [662 lines] - Windows package manager
└── windows_override.go [Overrides for Windows builds]
System & Supporting
internal/
├── system/
│ ├── info.go [100+ lines] - System information gathering
│ └── windows.go [Windows-specific system info]
├── cache/
│ └── local.go [Local caching of scan results]
├── client/
│ └── client.go [API communication]
├── config/
│ └── config.go [Configuration management]
├── installer/
│ ├── installer.go [Factory pattern]
│ ├── apt.go
│ ├── dnf.go
│ ├── docker.go
│ ├── windows.go
│ └── winget.go
├── service/
│ ├── service_stub.go
│ └── windows.go [Windows service management]
└── display/
└── terminal.go [Terminal display utilities]
9. Summary of Architecture Findings
Subsystems Included in scan_updates
- APT Scanner - Linux Debian/Ubuntu package updates
- DNF Scanner - Linux Fedora/RHEL package updates
- Docker Scanner - Container image updates (with Registry subsystem)
- Windows Update Scanner - Windows OS updates (WUA API)
- Winget Scanner - Windows application updates
Integration Model
Not a subsystem architecture, but rather:
- Sequential execution of isolated scanner modules
- Error accumulation without formal subsystem health tracking
- Sequential reporting - all errors reported together at end
- No dependency management between subsystems
- No resource pooling (each scanner creates its own connections)
Monolithic Aspects
The handleScanUpdates() function exhibits monolithic characteristics:
- Single responsibility is violated (orchestrates 5+ distinct scanning systems)
- Tight coupling between orchestrator and scanners
- Repeated code patterns suggest missing abstraction
- No separation of concerns between:
- Scanner availability checking
- Actual scanning
- Error handling
- Result aggregation
- Reporting
Modular Aspects
The individual scanner implementations ARE modular:
- Each scanner has own file
- Each implements common interface (IsAvailable, Scan)
- Each scanner logic is isolated
- Registry client is separated from Docker scanner
- Platform-specific code is separated (windows_wua.go vs windows.go stub)
Recommendations for Refactoring
If modularity/subsystem architecture is desired:
- Create ScannerRegistry/Factory - Manage scanner lifecycle
- Extract orchestration logic - Create ScanOrchestrator interface
- Implement health tracking - Track subsystem readiness
- Enable parallelization - Run scanners concurrently
- Formal error handling - Per-subsystem error types
- Dependency injection - Inject scanners into handlers
- Configuration per subsystem - Enable/disable individual scanners
- Metrics/observability - Track scan duration, success rate per subsystem