562 lines
18 KiB
Markdown
562 lines
18 KiB
Markdown
# 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:
|
|
|
|
```go
|
|
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
|
|
1. **scan_updates** - Main focus (lines 503-506)
|
|
2. collect_specs (not implemented)
|
|
3. dry_run_update (lines 511-514)
|
|
4. install_updates (lines 516-519)
|
|
5. confirm_dependencies (lines 521-524)
|
|
6. enable_heartbeat (lines 526-529)
|
|
7. disable_heartbeat (lines 531-534)
|
|
8. 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
|
|
|
|
1. **No Abstraction Layer**: Each scanner is called directly with repeated `if available -> scan -> handle error` blocks
|
|
2. **Sequential Execution**: All scanners run one-by-one (lines 559-646) - no parallelization
|
|
3. **Tight Coupling**: Error handling logic is mixed with business logic
|
|
4. **No Subsystem State Management**: Cannot track individual subsystem health or readiness
|
|
5. **Repeated Code**: Same pattern repeated 5 times for different scanners
|
|
|
|
**Code Pattern Repetition** (Example - APT):
|
|
```go
|
|
// 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 if `apt` command 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**:
|
|
```go
|
|
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 if `dnf` command 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**:
|
|
|
|
1. **Method 1**: `scanWithJSON()` - Primary, JSON output (lines 87-122)
|
|
2. **Method 2**: `scanWithBasicOutput()` - Fallback, text parsing (lines 125-134)
|
|
3. **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):
|
|
```go
|
|
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):
|
|
```go
|
|
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 disk
|
|
- `UpdateScanResults()` - Store latest scan results
|
|
- `SetAgentInfo()` - Store agent metadata
|
|
- `SetAgentStatus()` - Update status
|
|
- `Save()` - 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**:
|
|
```go
|
|
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):
|
|
1. **Scanner Implementations**: Each scanner is a separate file with its own logic
|
|
2. **Registry Client**: Docker registry communication is separated
|
|
3. **System Info**: Platform-specific implementations split (windows.go, windows_stub.go, windows_wua.go, etc.)
|
|
4. **Installers**: Separate installer implementations per package type
|
|
5. **Local Cache**: Separate subsystem for caching
|
|
|
|
### Monolithic (Bad):
|
|
1. **handleScanUpdates()**: Tight coupling of all scanners in one function
|
|
2. **Command Processing**: All command types in a single switch statement
|
|
3. **Error Aggregation**: No formal error handling subsystem; just accumulates strings
|
|
4. **No Subsystem Health Tracking**: Can't individually monitor scanner status
|
|
5. **No Parallelization**: Scanners run sequentially, wasting time
|
|
6. **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
|
|
|
|
1. **APT Scanner** - Linux Debian/Ubuntu package updates
|
|
2. **DNF Scanner** - Linux Fedora/RHEL package updates
|
|
3. **Docker Scanner** - Container image updates (with Registry subsystem)
|
|
4. **Windows Update Scanner** - Windows OS updates (WUA API)
|
|
5. **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:
|
|
|
|
1. **Create ScannerRegistry/Factory** - Manage scanner lifecycle
|
|
2. **Extract orchestration logic** - Create ScanOrchestrator interface
|
|
3. **Implement health tracking** - Track subsystem readiness
|
|
4. **Enable parallelization** - Run scanners concurrently
|
|
5. **Formal error handling** - Per-subsystem error types
|
|
6. **Dependency injection** - Inject scanners into handlers
|
|
7. **Configuration per subsystem** - Enable/disable individual scanners
|
|
8. **Metrics/observability** - Track scan duration, success rate per subsystem
|
|
|