6.7 KiB
P1-001: Agent Install ID Parsing Issue
Priority: P1 (Major) Source Reference: From needsfixingbeforepush.md line 3 Date Identified: 2025-11-12
Problem Description
The generateInstallScript function in downloads.go is not properly extracting the agent_id query parameter, causing the install script to always generate new agent IDs instead of using existing registered agent IDs for upgrades.
Current Behavior
Install script downloads always generate new UUIDs instead of preserving existing agent IDs:
# BEFORE (broken)
curl -sfL "http://localhost:8080/api/v1/install/linux?agent_id=6fdba4c92c4d4d33a4010e98db0df72d8bbe3d62c6b7e0a33cef3325e29bdd6d"
# Result: AGENT_ID="cf865204-125a-491d-976f-5829b6c081e6" (NEW UUID generated)
Expected Behavior
For upgrade scenarios, the install script should preserve the existing agent ID passed via query parameter:
# AFTER (fixed)
curl -sfL "http://localhost:8080/api/v1/install/linux?agent_id=6fdba4c92c4d4d33a4010e98db0df72d8bbe3d62c6b7e0a33cef3325e29bdd6d"
# Result: AGENT_ID="6fdba4c92c4d4d33a4010e98db0df72d8bbe3d62c6b7e0a33cef3325e29bdd6d" (PASSED UUID)
Root Cause Analysis
The generateInstallScript function only looks at query parameters but doesn't properly validate/extract the UUID format from the agent_id parameter. The function likely ignores or fails to parse the existing agent ID, falling back to generating a new UUID each time.
Proposed Solution
Implement proper agent ID parsing with security validation following this priority order:
- Header:
X-Agent-ID(most secure, not exposed in URLs/logs) - Path:
/api/v1/install/:platform/:agent_id(legacy support) - Query:
?agent_id=uuid(fallback for current usage)
All paths must:
- Validate UUID format before using
- Enforce rate limiting on agent ID reuse
- Apply signature validation for security
Implementation Details
// Example fix in downloads.go
func generateInstallScript(c *gin.Context) (string, error) {
var agentID string
// Priority 1: Check header (most secure)
if agentID = c.GetHeader("X-Agent-ID"); agentID != "" {
if isValidUUID(agentID) {
// Use header agent ID
}
}
// Priority 2: Check path parameter
if agentID == "" {
if agentID = c.Param("agent_id"); agentID != "" {
if isValidUUID(agentID) {
// Use path agent ID
}
}
}
// Priority 3: Check query parameter (current broken behavior)
if agentID == "" {
if agentID = c.Query("agent_id"); agentID != "" {
if isValidUUID(agentID) {
// Use query agent ID
}
}
}
// Fallback: Generate new UUID if no valid agent ID provided
if agentID == "" {
agentID = generateNewUUID()
}
// Generate install script with the determined agent ID
return generateScriptTemplate(agentID), nil
}
Definition of Done
- Install script preserves existing agent ID when provided via query parameter
- Agent ID format validation (UUID v4) prevents malformed IDs
- New UUID generated only when no valid agent ID is provided
- Security validation prevents agent ID spoofing
- Rate limiting prevents abuse of agent ID reuse
- Backward compatibility maintained for existing install methods
Test Plan
-
Query Parameter Test:
# Test with valid UUID in query parameter TEST_UUID="6fdba4c92c4d4d33a4010e98db0df72d8bbe3d62c6b7e0a33cef3325e29bdd6d" curl -sfL "http://localhost:8080/api/v1/install/linux?agent_id=$TEST_UUID" | grep "AGENT_ID=" # Expected: AGENT_ID="$TEST_UUID" (same UUID) # Not: AGENT_ID="<new-generated-uuid>" -
Invalid UUID Test:
# Test with malformed UUID curl -sfL "http://localhost:8080/api/v1/install/linux?agent_id=invalid-uuid" | grep "AGENT_ID=" # Expected: AGENT_ID="<new-generated-uuid>" (rejects invalid, generates new) -
Empty Parameter Test:
# Test with empty agent_id parameter curl -sfL "http://localhost:8080/api/v1/install/linux?agent_id=" | grep "AGENT_ID=" # Expected: AGENT_ID="<new-generated-uuid>" (empty treated as not provided) -
No Parameter Test:
# Test without agent_id parameter (current behavior) curl -sfL "http://localhost:8080/api/v1/install/linux" | grep "AGENT_ID=" # Expected: AGENT_ID="<new-generated-uuid>" (maintain backward compatibility) -
Security Validation Test:
# Test with UUID validation edge cases curl -sfL "http://localhost:8080/api/v1/install/linux?agent_id=00000000-0000-0000-0000-000000000000" | grep "AGENT_ID=" # Should handle edge cases appropriately
Files to Modify
aggregator-server/internal/api/handlers/downloads.go(main fix location)- Add UUID validation utility functions
- Potentially update rate limiting logic for agent ID reuse
- Add tests for install script generation
Impact
- Agent Upgrades: Prevents agent identity loss during upgrades/reinstallation
- Agent Management: Maintains consistent agent identity across system lifecycle
- Audit Trail: Preserves agent history and command continuity
- User Experience: Allows seamless agent reinstallation without re-registration
Security Considerations
- Agent ID Spoofing: Must validate that agent ID belongs to legitimate agent
- Rate Limiting: Prevent abuse of agent ID reuse for malicious purposes
- Signature Validation: Ensure agent ID requests are authenticated
- Audit Logging: Log agent ID reuse attempts for security monitoring
Upgrade Scenario Use Case
# Agent needs upgrade/reinstallation on same machine
# Admin provides existing agent ID to preserve history
EXISTING_AGENT_ID="6fdba4c92c4d4d33a4010e98db0df72d8bbe3d62c6b7e0a33cef3325e29bdd6d"
# Install script preserves agent identity
curl -sfL "http://redflag-server:8080/api/v1/install/linux?agent_id=$EXISTING_AGENT_ID" | sudo bash
# Result: Agent reinstalls with same ID, preserving:
# - Command history
# - Configuration settings
# - Agent registration record
# - Audit trail continuity
Verification Commands
After fix implementation:
# Verify query parameter preservation
UUID="6fdba4c92c4d4d33a4010e98db0df72d8bbe3d62c6b7e0a33cef3325e29bdd6d"
SCRIPT=$(curl -sfL "http://localhost:8080/api/v1/install/linux?agent_id=$UUID")
echo "$SCRIPT" | grep "AGENT_ID="
# Should output: AGENT_ID="6fdba4c92c4d4d33a4010e98db0df72d8bbe3d62c6b7e0a33cef3325e29bdd6d"
# Test invalid UUID rejection
INVALID_SCRIPT=$(curl -sfL "http://localhost:8080/api/v1/install/linux?agent_id=invalid")
echo "$INVALID_SCRIPT" | grep "AGENT_ID="
# Should output different UUID (generated new)