diff --git a/aggregator-agent/cmd/agent/main.go b/aggregator-agent/cmd/agent/main.go index 8075c4a..6792c4c 100644 --- a/aggregator-agent/cmd/agent/main.go +++ b/aggregator-agent/cmd/agent/main.go @@ -636,14 +636,17 @@ func runAgent(cfg *config.Config) error { apiClient := client.NewClient(cfg.ServerURL, cfg.Token) - // Initialize scanners + // Initialize scanners for package updates (used by update orchestrator) aptScanner := scanner.NewAPTScanner() dnfScanner := scanner.NewDNFScanner() - dockerScanner, _ := scanner.NewDockerScanner() windowsUpdateScanner := scanner.NewWindowsUpdateScanner() wingetScanner := scanner.NewWingetScanner() - // Initialize circuit breakers for each subsystem + // Docker, Storage, and System scanners are created by individual subsystem handlers + // dockerScanner is created in handleScanDocker + // storageScanner and systemScanner are created in main for individual handlers + + // Initialize circuit breakers for update scanners only aptCB := circuitbreaker.New("APT", circuitbreaker.Config{ FailureThreshold: cfg.Subsystems.APT.CircuitBreaker.FailureThreshold, FailureWindow: cfg.Subsystems.APT.CircuitBreaker.FailureWindow, @@ -656,12 +659,6 @@ func runAgent(cfg *config.Config) error { OpenDuration: cfg.Subsystems.DNF.CircuitBreaker.OpenDuration, HalfOpenAttempts: cfg.Subsystems.DNF.CircuitBreaker.HalfOpenAttempts, }) - dockerCB := circuitbreaker.New("Docker", circuitbreaker.Config{ - FailureThreshold: cfg.Subsystems.Docker.CircuitBreaker.FailureThreshold, - FailureWindow: cfg.Subsystems.Docker.CircuitBreaker.FailureWindow, - OpenDuration: cfg.Subsystems.Docker.CircuitBreaker.OpenDuration, - HalfOpenAttempts: cfg.Subsystems.Docker.CircuitBreaker.HalfOpenAttempts, - }) windowsCB := circuitbreaker.New("Windows Update", circuitbreaker.Config{ FailureThreshold: cfg.Subsystems.Windows.CircuitBreaker.FailureThreshold, FailureWindow: cfg.Subsystems.Windows.CircuitBreaker.FailureWindow, @@ -678,33 +675,17 @@ func runAgent(cfg *config.Config) error { // Initialize scanner orchestrator for parallel execution and granular subsystem management scanOrchestrator := orchestrator.NewOrchestrator() - // Register update scanners + // Register update scanners ONLY - package management systems scanOrchestrator.RegisterScanner("apt", orchestrator.NewAPTScannerWrapper(aptScanner), aptCB, cfg.Subsystems.APT.Timeout, cfg.Subsystems.APT.Enabled) scanOrchestrator.RegisterScanner("dnf", orchestrator.NewDNFScannerWrapper(dnfScanner), dnfCB, cfg.Subsystems.DNF.Timeout, cfg.Subsystems.DNF.Enabled) - scanOrchestrator.RegisterScanner("docker", orchestrator.NewDockerScannerWrapper(dockerScanner), dockerCB, cfg.Subsystems.Docker.Timeout, cfg.Subsystems.Docker.Enabled) scanOrchestrator.RegisterScanner("windows", orchestrator.NewWindowsUpdateScannerWrapper(windowsUpdateScanner), windowsCB, cfg.Subsystems.Windows.Timeout, cfg.Subsystems.Windows.Enabled) scanOrchestrator.RegisterScanner("winget", orchestrator.NewWingetScannerWrapper(wingetScanner), wingetCB, cfg.Subsystems.Winget.Timeout, cfg.Subsystems.Winget.Enabled) - // Register storage and system scanners - storageScanner := orchestrator.NewStorageScanner(AgentVersion) - systemScanner := orchestrator.NewSystemScanner(AgentVersion) - - // Storage and system scanners don't need circuit breakers (always available, fast operations) - storageCB := circuitbreaker.New("Storage", circuitbreaker.Config{ - FailureThreshold: 5, - FailureWindow: 10 * time.Minute, - OpenDuration: 5 * time.Minute, - HalfOpenAttempts: 1, - }) - systemCB := circuitbreaker.New("System", circuitbreaker.Config{ - FailureThreshold: 5, - FailureWindow: 10 * time.Minute, - OpenDuration: 5 * time.Minute, - HalfOpenAttempts: 1, - }) - - scanOrchestrator.RegisterScanner("storage", storageScanner, storageCB, 30*time.Second, cfg.Subsystems.Storage.Enabled) - scanOrchestrator.RegisterScanner("system", systemScanner, systemCB, 30*time.Second, true) // System scanner always enabled + // NOTE: Docker, Storage, and System scanners are NOT registered with the update orchestrator + // They have their own dedicated handlers and endpoints: + // - Docker: handleScanDocker → ReportDockerImages() + // - Storage: handleScanStorage → ReportMetrics() + // - System: handleScanSystem → ReportMetrics() // Initialize acknowledgment tracker for command result reliability ackTracker := acknowledgment.NewTracker(getStatePath()) diff --git a/aggregator-server/internal/api/handlers/downloads.go b/aggregator-server/internal/api/handlers/downloads.go index 94db1d4..39694c5 100644 --- a/aggregator-server/internal/api/handlers/downloads.go +++ b/aggregator-server/internal/api/handlers/downloads.go @@ -8,19 +8,22 @@ import ( "strings" "github.com/Fimeg/RedFlag/aggregator-server/internal/config" + "github.com/Fimeg/RedFlag/aggregator-server/internal/services" "github.com/gin-gonic/gin" ) // DownloadHandler handles agent binary downloads type DownloadHandler struct { - agentDir string - config *config.Config + agentDir string + config *config.Config + installTemplateService *services.InstallTemplateService } func NewDownloadHandler(agentDir string, cfg *config.Config) *DownloadHandler { return &DownloadHandler{ - agentDir: agentDir, - config: cfg, + agentDir: agentDir, + config: cfg, + installTemplateService: services.NewInstallTemplateService(), } } @@ -154,918 +157,18 @@ func (h *DownloadHandler) InstallScript(c *gin.Context) { } func (h *DownloadHandler) generateInstallScript(platform, baseURL string) string { - switch platform { - case "linux": - return h.generateLinuxScript(baseURL) - case "windows": - return h.generateWindowsScript(baseURL) - default: - return "# Unsupported platform: " + platform + // Use template service to generate install scripts + // For generic downloads, use placeholder values + script, err := h.installTemplateService.RenderInstallScriptFromBuild( + "", // Will be generated during install + platform, // Platform (linux/windows) + "latest", // Version + fmt.Sprintf("%s/downloads/%s", baseURL, platform), // Binary URL + fmt.Sprintf("%s/api/v1/config/", baseURL), // Config URL (placeholder) + ) + if err != nil { + return fmt.Sprintf("# Error generating install script: %v", err) } + return script } -func (h *DownloadHandler) generateLinuxScript(baseURL string) string { - return fmt.Sprintf(`#!/bin/bash -set -e - -# RedFlag Agent Smart Installer -# Uses the sophisticated build orchestrator and migration system - -REDFLAG_SERVER="%s" -AGENT_USER="redflag-agent" -AGENT_HOME="/var/lib/redflag-agent" -AGENT_BINARY="/usr/local/bin/redflag-agent" -SUDOERS_FILE="/etc/sudoers.d/redflag-agent" -SERVICE_FILE="/etc/systemd/system/redflag-agent.service" -CONFIG_DIR="/etc/redflag" -STATE_DIR="/var/lib/redflag" -OLD_CONFIG_DIR="/etc/aggregator" -OLD_STATE_DIR="/var/lib/aggregator" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}=== RedFlag Agent Smart Installer ===${NC}" -echo "" - -# Check if running as root -if [ "$EUID" -ne 0 ]; then - echo -e "${RED}ERROR: This script must be run as root (use sudo)${NC}" - exit 1 -fi - -# Get registration token from first argument -REGISTRATION_TOKEN="$1" -if [ -z "$REGISTRATION_TOKEN" ]; then - echo -e "${RED}ERROR: Registration token is required${NC}" - echo -e "${YELLOW}Usage: curl -sL ${REDFLAG_SERVER}/api/v1/install/linux | sudo bash -s -- YOUR_REGISTRATION_TOKEN${NC}" - exit 1 -fi - -echo -e "${BLUE}Registration token: ${GREEN}${REGISTRATION_TOKEN:0:8}...${NC}" -echo "" - -# Detect architecture -ARCH=$(uname -m) -case "$ARCH" in - x86_64) - DOWNLOAD_ARCH="amd64" - ;; - aarch64|arm64) - DOWNLOAD_ARCH="arm64" - ;; - *) - echo -e "${RED}ERROR: Unsupported architecture: $ARCH${NC}" - echo -e "${YELLOW}Supported: x86_64 (amd64), aarch64 (arm64)${NC}" - exit 1 - ;; -esac - -echo -e "${BLUE}Detected architecture: $ARCH (using linux-$DOWNLOAD_ARCH)${NC}" -echo "" - -# Function to detect existing installation using our sophisticated system -detect_existing_agent() { - echo -e "${YELLOW}Detecting existing RedFlag agent installation...${NC}" - - # DEBUGGING: Start comprehensive debugging trace - echo "=== DEBUGGING: detect_existing_agent() ===" - echo "DEBUG: Starting detection process..." - - # Check for config files in both new and old locations - echo "DEBUG: Checking for config files in all locations..." - - # Check new location first - echo "DEBUG: Checking new config file: /etc/redflag/config.json" - if [ -f "/etc/redflag/config.json" ]; then - echo "DEBUG: New config file exists!" - CONFIG_FILE="/etc/redflag/config.json" - CONFIG_LOCATION="new" - else - echo "DEBUG: New config file does not exist, checking legacy location..." - - # Check old location - if [ -f "/etc/aggregator/config.json" ]; then - echo "DEBUG: Found legacy config file: /etc/aggregator/config.json" - CONFIG_FILE="/etc/aggregator/config.json" - CONFIG_LOCATION="old" - else - echo "DEBUG: No config file found in either location" - CONFIG_FILE="" - CONFIG_LOCATION="none" - fi - fi - - # If we found a config file, try to extract agent_id (using single reliable method) - if [ -n "$CONFIG_FILE" ]; then - echo "DEBUG: Processing config file: $CONFIG_FILE (location: $CONFIG_LOCATION)" - - # Check file permissions - echo "DEBUG: File permissions:" - ls -la "$CONFIG_FILE" - - # Check file ownership - echo "DEBUG: File ownership:" - stat -c "%U:%G" "$CONFIG_FILE" - - # Try reading file content - echo "DEBUG: Attempting to read file content..." - echo "DEBUG: Method 1 - Direct cat:" - if sudo cat "$CONFIG_FILE" 2>/dev/null; then - echo "DEBUG: Direct cat successful" - else - echo "DEBUG: Direct cat failed" - fi - - # Extract agent_id using single reliable method - echo "DEBUG: Extracting agent_id with grep:" - agent_id=$(grep -o '"agent_id": *"[^"]*"' "$CONFIG_FILE" 2>/dev/null | cut -d'"' -f4) - echo "DEBUG: Extracted agent_id: '$agent_id'" - - # Check if agent_id looks valid (UUID format) - if [ -n "$agent_id" ]; then - if echo "$agent_id" | grep -qE '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'; then - echo "DEBUG: Agent ID appears to be valid UUID format" - else - echo "DEBUG: Agent ID does not appear to be valid UUID format" - fi - else - echo "DEBUG: Agent ID is empty or null" - fi - - # Note if migration is needed - if [ "$CONFIG_LOCATION" = "old" ]; then - echo "DEBUG: *** MIGRATION REQUIRED - Config found in legacy location ***" - fi - else - echo "DEBUG: No config files found, checking directories..." - - # Check if directories exist for debugging - for dir_path in "/etc/redflag" "/etc/aggregator" "/var/lib/redflag" "/var/lib/aggregator"; do - if [ -d "$dir_path" ]; then - echo "DEBUG: Found directory: $dir_path" - echo "DEBUG: Directory contents:" - ls -la "$dir_path/" 2>/dev/null || echo "DEBUG: Cannot list contents (permissions?)" - else - echo "DEBUG: Directory does not exist: $dir_path" - fi - done - fi - - # Check if systemd service exists - echo "DEBUG: Checking systemd service..." - if systemctl list-unit-files | grep -q "redflag-agent.service"; then - echo "DEBUG: Systemd service file exists" - - # Check service status - echo "DEBUG: Service status:" - systemctl status redflag-agent --no-pager -l || echo "DEBUG: Could not get service status" - - # Check if service is enabled - if systemctl is-enabled --quiet redflag-agent 2>/dev/null; then - echo "DEBUG: Service is enabled" - else - echo "DEBUG: Service is not enabled" - fi - - # Check if service is active - if systemctl is-active --quiet redflag-agent 2>/dev/null; then - echo "DEBUG: Service is active" - else - echo "DEBUG: Service is not active" - fi - else - echo "DEBUG: Systemd service file does not exist" - fi - - # Check if binary exists - echo "DEBUG: Checking for agent binary..." - for binary_path in "/usr/local/bin/redflag-agent" "/usr/bin/redflag-agent" "/opt/redflag-agent/bin/redflag-agent"; do - if [ -f "$binary_path" ]; then - echo "DEBUG: Found agent binary at: $binary_path" - echo "DEBUG: Binary permissions:" - ls -la "$binary_path" - break - fi - done - - # Test server connectivity - echo "DEBUG: Testing server connectivity..." - echo "DEBUG: Server URL: ${REDFLAG_SERVER}" - - # Test basic connectivity - echo "DEBUG: Testing basic HTTP connectivity..." - if curl -s --connect-timeout 5 "${REDFLAG_SERVER}/api/v1/health" >/dev/null 2>&1; then - echo "DEBUG: Server connectivity test successful" - else - echo "DEBUG: Server connectivity test failed" - echo "DEBUG: curl exit code: $?" - fi - - # Call detection API with debugging - echo "DEBUG: Calling detection API..." - echo "DEBUG: URL: ${REDFLAG_SERVER}/api/v1/build/detect" - echo "DEBUG: Payload: {\"agent_id\": \"${agent_id}\"}" - - DETECTION_RESPONSE=$(curl -s -X POST "${REDFLAG_SERVER}/api/v1/build/detect" \ - -H "Content-Type: application/json" \ - -d '{"agent_id": "'"$agent_id"'"}' 2>/dev/null) - - echo "DEBUG: curl exit code: $?" - echo "DEBUG: Detection response: '$DETECTION_RESPONSE'" - - if [ $? -eq 0 ] && [ -n "$DETECTION_RESPONSE" ]; then - echo "DEBUG: Successfully received detection response" - - # Parse JSON response with debugging - echo "DEBUG: Parsing detection response..." - - HAS_AGENT=$(echo "$DETECTION_RESPONSE" | grep -o '"has_existing_agent":[^,]*' | cut -d':' -f2 | tr -d ' ') - echo "DEBUG: Extracted has_existing_agent: '$HAS_AGENT'" - - AGENT_ID=$(echo "$DETECTION_RESPONSE" | grep -o '"agent_id":"[^"]*"' | cut -d'"' -f4) - echo "DEBUG: Extracted agent_id from response: '$AGENT_ID'" - - REQUIRES_MIGRATION=$(echo "$DETECTION_RESPONSE" | grep -o '"requires_migration":[^,]*' | cut -d':' -f2 | tr -d ' ') - echo "DEBUG: Extracted requires_migration: '$REQUIRES_MIGRATION'" - - CURRENT_VERSION=$(echo "$DETECTION_RESPONSE" | grep -o '"current_version":"[^"]*"' | cut -d'"' -f4) - echo "DEBUG: Extracted current_version: '$CURRENT_VERSION'" - - # Check conditions for successful detection - if [ "$HAS_AGENT" = "true" ] && [ -n "$AGENT_ID" ]; then - echo "DEBUG: Detection SUCCESS - existing agent found" - echo -e "${GREEN}✓ Existing agent detected: ${AGENT_ID}${NC}" - echo -e "${BLUE} Current version: ${CURRENT_VERSION}${NC}" - if [ "$REQUIRES_MIGRATION" = "true" ]; then - echo -e "${YELLOW}⚠ Migration will be performed during upgrade${NC}" - fi - echo "=== END DEBUGGING: detect_existing_agent() ===" - return 0 # Upgrade path - else - echo "DEBUG: Detection indicates no existing agent" - echo "DEBUG: has_existing_agent: '$HAS_AGENT'" - echo "DEBUG: agent_id from response: '$AGENT_ID'" - fi - else - echo "DEBUG: Detection API call failed or returned empty response" - echo "DEBUG: curl exit code: $?" - echo "DEBUG: response length: ${#DETECTION_RESPONSE}" - fi - - echo "DEBUG: Returning new installation path" - echo -e "${GREEN}✓ No existing agent detected - performing new installation${NC}" - echo "=== END DEBUGGING: detect_existing_agent() ===" - return 1 # New installation path -} - -# Function to perform migration from old paths -perform_migration() { - echo "" - echo -e "${BLUE}=== Migration Required ===${NC}" - - # Create backup directories with timestamp - BACKUP_TIMESTAMP=$(date +%%Y%%m%%d_%%H%%M%%S) - OLD_CONFIG_BACKUP="${OLD_CONFIG_DIR}.backup.${BACKUP_TIMESTAMP}" - OLD_STATE_BACKUP="${OLD_STATE_DIR}.backup.${BACKUP_TIMESTAMP}" - - # Backup old directories if they exist - if [ -d "$OLD_CONFIG_DIR" ]; then - echo -e "${YELLOW}Backing up old configuration: ${OLD_CONFIG_DIR} -> ${OLD_CONFIG_BACKUP}${NC}" - mv "$OLD_CONFIG_DIR" "$OLD_CONFIG_BACKUP" - fi - - if [ -d "$OLD_STATE_DIR" ]; then - echo -e "${YELLOW}Backing up old state: ${OLD_STATE_DIR} -> ${OLD_STATE_BACKUP}${NC}" - mv "$OLD_STATE_DIR" "$OLD_STATE_BACKUP" - fi - - # Migrate configuration data if backup exists - if [ -d "$OLD_CONFIG_BACKUP" ]; then - echo -e "${YELLOW}Migrating configuration data to new location...${NC}" - mkdir -p "$CONFIG_DIR" - - # Copy config files, preserving permissions when possible - cp -r "$OLD_CONFIG_BACKUP"/* "$CONFIG_DIR/" 2>/dev/null || true - - # Set proper ownership for new location - chown -R "$AGENT_USER:$AGENT_USER" "$CONFIG_DIR" 2>/dev/null || true - chmod 755 "$CONFIG_DIR" 2>/dev/null || true - - # Ensure config file has correct permissions - if [ -f "$CONFIG_DIR/config.json" ]; then - chmod 600 "$CONFIG_DIR/config.json" 2>/dev/null || true - chown "$AGENT_USER:$AGENT_USER" "$CONFIG_DIR/config.json" 2>/dev/null || true - fi - fi - - # Migrate state data if backup exists - if [ -d "$OLD_STATE_BACKUP" ]; then - echo -e "${YELLOW}Migrating state data to new location...${NC}" - mkdir -p "$STATE_DIR" - cp -r "$OLD_STATE_BACKUP"/* "$STATE_DIR/" 2>/dev/null || true - chown -R "$AGENT_USER:$AGENT_USER" "$STATE_DIR" 2>/dev/null || true - fi - - # Migrate secrets to Docker secrets if available - migrate_secrets_to_docker - - echo -e "${GREEN}✓ Migration completed${NC}" -} - -# Function to migrate secrets from filesystem to Docker secrets -migrate_secrets_to_docker() { - echo -e "${YELLOW}Checking for secrets migration...${NC}" - - # Look for potential secret files in old locations - local secrets_found=false - - # Check for common secret file patterns - for secret_pattern in "agent.key" "private_key" "secrets.json" ".env" "credentials.json"; do - if [ -f "$OLD_CONFIG_BACKUP/$secret_pattern" ] || [ -f "$OLD_STATE_BACKUP/$secret_pattern" ]; then - echo -e "${YELLOW}Found potential secret file: $secret_pattern${NC}" - secrets_found=true - fi - done - - # Check for agent private keys or certificates - for key_path in "$OLD_CONFIG_BACKUP" "$OLD_STATE_BACKUP"; do - if [ -d "$key_path" ]; then - # Look for key files - find "$key_path" -type f \( -name "*.key" -o -name "*.pem" -o -name "*.crt" -o -name "id_*" \) 2>/dev/null | while read -r key_file; do - echo -e "${YELLOW}Found key file: $(basename "$key_file")${NC}" - secrets_found=true - done - fi - done - - if [ "$secrets_found" = true ]; then - echo -e "${BLUE}Secrets migration available${NC}" - echo -e "${YELLOW}Note: Secrets will be migrated to Docker secrets when the agent starts${NC}" - echo -e "${YELLOW}The agent will automatically detect and migrate filesystem secrets to Docker storage${NC}" - - # Create a migration marker for the agent to find - mkdir -p "$CONFIG_DIR" - echo '{"secrets_migration_required": true, "migration_timestamp": "'$(date -Iseconds)'"}' > "$CONFIG_DIR/secrets_migration.json" - chown "$AGENT_USER:$AGENT_USER" "$CONFIG_DIR/secrets_migration.json" 2>/dev/null || true - chmod 600 "$CONFIG_DIR/secrets_migration.json" 2>/dev/null || true - else - echo -e "${GREEN}No secrets requiring migration found${NC}" - fi -} - -# Function to perform new installation using build orchestrator -perform_new_installation() { - echo "" - echo -e "${BLUE}=== New Agent Installation ===${NC}" - - # Call build/new endpoint to get proper configuration and upgrade logic - echo -e "${YELLOW}Requesting agent build configuration...${NC}" - BUILD_RESPONSE=$(curl -s -X POST "${REDFLAG_SERVER}/api/v1/build/new" \ - -H "Content-Type: application/json" \ - -d '{ - "server_url": "'"${REDFLAG_SERVER}"'", - "environment": "production", - "agent_type": "linux-server", - "organization": "default", - "registration_token": "'"${REGISTRATION_TOKEN}"'" - }' 2>/dev/null) - - if [ $? -ne 0 ] || [ -z "$BUILD_RESPONSE" ]; then - echo -e "${RED}✗ Failed to request agent build configuration${NC}" - exit 1 - fi - - # Extract agent ID from build response - AGENT_ID=$(echo "$BUILD_RESPONSE" | grep -o '"agent_id":"[^"]*"' | cut -d'"' -f4) - - if [ -z "$AGENT_ID" ]; then - echo -e "${RED}✗ Invalid response from server${NC}" - exit 1 - fi - - echo -e "${GREEN}✓ Agent configuration created: ${AGENT_ID}${NC}" - - # Download native agent binary - echo -e "${YELLOW}Downloading native signed agent binary...${NC}" - if curl -sL "${REDFLAG_SERVER}/api/v1/downloads/linux-${DOWNLOAD_ARCH}" -o "$AGENT_BINARY"; then - chmod 755 "$AGENT_BINARY" - chown root:root "$AGENT_BINARY" - echo -e "${GREEN}✓ Native signed agent binary installed${NC}" - else - echo -e "${RED}✗ Failed to download agent binary${NC}" - exit 1 - fi - - deploy_agent "$AGENT_ID" "$BUILD_RESPONSE" "new" -} - -# Function to perform upgrade using build orchestrator -perform_upgrade() { - echo "" - echo -e "${BLUE}=== Agent Upgrade ===${NC}" - - # Extract agent ID from detection - AGENT_ID=$(echo "$DETECTION_RESPONSE" | grep -o '"agent_id":"[^"]*"' | cut -d'"' -f4) - - if [ -z "$AGENT_ID" ]; then - echo -e "${RED}✗ Could not extract agent ID for upgrade${NC}" - exit 1 - fi - - echo -e "${YELLOW}Requesting upgrade configuration for agent: ${AGENT_ID}${NC}" - - # Call build/upgrade endpoint to get upgrade configuration - BUILD_RESPONSE=$(curl -s -X POST "${REDFLAG_SERVER}/api/v1/build/upgrade/${AGENT_ID}" \ - -H "Content-Type: application/json" \ - -d '{ - "server_url": "'"${REDFLAG_SERVER}"'", - "environment": "production", - "agent_type": "linux-server", - "preserve_existing": true - }' 2>/dev/null) - - if [ $? -ne 0 ] || [ -z "$BUILD_RESPONSE" ]; then - echo -e "${RED}✗ Failed to request agent upgrade configuration${NC}" - exit 1 - fi - - echo -e "${GREEN}✓ Upgrade configuration prepared for agent: ${AGENT_ID}${NC}" - - # STOP SERVICE BEFORE DOWNLOADING BINARY - echo -e "${YELLOW}Stopping agent service to allow binary replacement...${NC}" - if systemctl is-active --quiet redflag-agent 2>/dev/null; then - systemctl stop redflag-agent - # Wait for service to fully stop - local retry_count=0 - while [ $retry_count -lt 10 ]; do - if ! systemctl is-active --quiet redflag-agent 2>/dev/null; then - echo -e "${GREEN}✓ Service stopped successfully${NC}" - break - fi - echo -e "${YELLOW}Waiting for service to stop... (attempt $((retry_count + 1))/10)${NC}" - sleep 1 - retry_count=$((retry_count + 1)) - done - - if systemctl is-active --quiet redflag-agent 2>/dev/null; then - echo -e "${RED}✗ Failed to stop service, forcing...${NC}" - systemctl kill redflag-agent - sleep 2 - fi - else - echo -e "${BLUE}✓ Service is already stopped${NC}" - fi - - # Download updated native agent binary to temporary location first - echo -e "${YELLOW}Downloading updated native signed agent binary...${NC}" - TEMP_BINARY="${AGENT_BINARY}.new" - - # Remove any existing temp binary - rm -f "$TEMP_BINARY" - - if curl -sL "${REDFLAG_SERVER}/api/v1/downloads/linux-${DOWNLOAD_ARCH}" -o "$TEMP_BINARY"; then - # Verify the download - if [ -f "$TEMP_BINARY" ] && [ -s "$TEMP_BINARY" ]; then - chmod 755 "$TEMP_BINARY" - chown root:root "$TEMP_BINARY" - - # Atomic move to replace binary - mv "$TEMP_BINARY" "$AGENT_BINARY" - - # Verify the replacement - if [ -f "$AGENT_BINARY" ] && [ -s "$AGENT_BINARY" ]; then - echo -e "${GREEN}✓ Native signed agent binary updated successfully${NC}" - else - echo -e "${RED}✗ Binary replacement verification failed${NC}" - exit 1 - fi - else - echo -e "${RED}✗ Downloaded binary is empty or missing${NC}" - rm -f "$TEMP_BINARY" - exit 1 - fi - else - echo -e "${RED}✗ Failed to download agent binary${NC}" - rm -f "$TEMP_BINARY" - exit 1 - fi - - deploy_agent "$AGENT_ID" "$BUILD_RESPONSE" "upgrade" -} - -# Function to deploy native agent with systemd -deploy_agent() { - local AGENT_ID="$1" - local BUILD_RESPONSE="$2" - local INSTALL_TYPE="$3" - - echo "" - echo -e "${BLUE}=== Agent Deployment ===${NC}" - - # Create agent user if it doesn't exist - if ! id "$AGENT_USER" &>/dev/null; then - echo -e "${YELLOW}Creating agent user: $AGENT_USER${NC}" - useradd -r -s /bin/false -d "$AGENT_HOME" -m "$AGENT_USER" - fi - - # Note: Service is already stopped for upgrades, but check for new installations - if [ "$INSTALL_TYPE" = "new" ] && systemctl is-active --quiet redflag-agent 2>/dev/null; then - echo -e "${YELLOW}Stopping existing agent service...${NC}" - systemctl stop redflag-agent - sleep 2 - fi - - # Save build response for potential recovery and debugging - echo "$BUILD_RESPONSE" > "${CONFIG_DIR}/build_response.json" - chown "$AGENT_USER:$AGENT_USER" "${CONFIG_DIR}/build_response.json" - chmod 600 "${CONFIG_DIR}/build_response.json" - - # Create directories - mkdir -p "$CONFIG_DIR" "$STATE_DIR" - - # Install sudoers configuration if not exists - if [ ! -f "$SUDOERS_FILE" ]; then - echo -e "${YELLOW}Installing sudoers configuration...${NC}" - cat > "$SUDOERS_FILE" << 'SUDOERS_EOF' -# RedFlag Agent minimal sudo permissions -# Generated automatically during RedFlag agent installation - -# APT package management commands (Debian/Ubuntu) -redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get update -redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get install -y * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get upgrade -y * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get install --dry-run --yes * - -# DNF package management commands (RHEL/Fedora/Rocky/Alma) -redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf makecache -redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf install -y * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf upgrade -y * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf install --assumeno --downloadonly * - -# Docker operations -redflag-agent ALL=(root) NOPASSWD: /usr/bin/docker pull * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/docker image inspect * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/docker manifest inspect * - -# Directory operations for RedFlag -redflag-agent ALL=(root) NOPASSWD: /bin/mkdir -p /etc/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/mkdir -p /var/lib/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/chown redflag-agent:redflag-agent /etc/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/chown redflag-agent:redflag-agent /var/lib/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/chmod 755 /etc/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/chmod 755 /var/lib/redflag - -# Migration operations (for existing installations) -redflag-agent ALL=(root) NOPASSWD: /bin/mv /etc/aggregator /etc/redflag.backup.* -redflag-agent ALL=(root) NOPASSWD: /bin/mv /var/lib/aggregator/* /var/lib/redflag/ -redflag-agent ALL=(root) NOPASSWD: /bin/rmdir /var/lib/aggregator 2>/dev/null || true -redflag-agent ALL=(root) NOPASSWD: /bin/rmdir /etc/aggregator 2>/dev/null || true -SUDOERS_EOF - - chmod 440 "$SUDOERS_FILE" - - # Validate sudoers file - if visudo -c -f "$SUDOERS_FILE" &>/dev/null; then - echo -e "${GREEN}✓ Sudoers configuration installed${NC}" - else - echo -e "${RED}✗ Invalid sudoers configuration${NC}" - rm -f "$SUDOERS_FILE" - exit 1 - fi - fi - - # Install/update systemd service - echo -e "${YELLOW}Installing systemd service...${NC}" - cat > "$SERVICE_FILE" << EOF -[Unit] -Description=RedFlag Update Agent -After=network.target -Documentation=https://github.com/Fimeg/RedFlag - -[Service] -Type=simple -User=$AGENT_USER -Group=$AGENT_USER -WorkingDirectory=$AGENT_HOME -ExecStart=$AGENT_BINARY -Restart=always -RestartSec=30 - -# Security hardening -ProtectSystem=strict -ProtectHome=true -ReadWritePaths=$AGENT_HOME /var/log $CONFIG_DIR $STATE_DIR -PrivateTmp=true - -# Logging -StandardOutput=journal -StandardError=journal -SyslogIdentifier=redflag-agent - -[Install] -WantedBy=multi-user.target -EOF - - chmod 644 "$SERVICE_FILE" - systemctl daemon-reload - - # Set directory permissions - chown "$AGENT_USER:$AGENT_USER" "$CONFIG_DIR" "$STATE_DIR" - chmod 755 "$CONFIG_DIR" "$STATE_DIR" - - # Start and enable service - systemctl enable redflag-agent - systemctl restart redflag-agent - - # Wait for service to start - sleep 3 - - # Verify deployment - if systemctl is-active --quiet redflag-agent; then - echo -e "${GREEN}✓ Native agent deployed successfully${NC}" - - # Check logs briefly - echo -e "${BLUE}Agent status:${NC}" - systemctl status redflag-agent --no-pager -l | head -n 10 - - echo "" - echo -e "${GREEN}=== Installation Complete ===${NC}" - echo -e "${BLUE}Agent Details:${NC}" - echo -e " • Agent ID: ${AGENT_ID}" - echo -e " • Binary: ${AGENT_BINARY}" - echo -e " • Service: redflag-agent" - echo -e " • Config: ${CONFIG_DIR}/config.json" - echo -e " • Build Response: ${CONFIG_DIR}/build_response.json" - if [ "$INSTALL_TYPE" = "upgrade" ]; then - echo -e " • ${GREEN}Seat preserved: No additional license consumed${NC}" - else - echo -e " • ${GREEN}Seat allocated: One license consumed${NC}" - fi - echo "" - echo -e "${BLUE}Management Commands:${NC}" - echo -e " • Status: systemctl status redflag-agent" - echo -e " • Logs: journalctl -u redflag-agent -f" - echo -e " • Restart: systemctl restart redflag-agent" - echo -e " • Update: Re-run this installer script" - echo "" - else - echo -e "${RED}✗ Failed to start agent service${NC}" - echo -e "${YELLOW}Troubleshooting:${NC}" - echo -e " • Check logs: journalctl -u redflag-agent -n 50" - echo -e " • Check binary: ls -la ${AGENT_BINARY}" - echo -e " • Check service: systemctl status redflag-agent" - exit 1 - fi -} - -# Main execution -echo -e "${BLUE}Starting smart installation process...${NC}" - -# Detect existing installation using our sophisticated system -if detect_existing_agent; then - # Check if migration is needed by looking for old directories - if [ -d "$OLD_CONFIG_DIR" ] || [ -d "$OLD_STATE_DIR" ]; then - perform_migration - fi - # Upgrade path - perform_upgrade -else - # Check if migration is needed for new install - if [ -d "$OLD_CONFIG_DIR" ] || [ -d "$OLD_STATE_DIR" ]; then - perform_migration - fi - # New installation path - perform_new_installation -fi - -echo "" -echo -e "${GREEN}=== Smart Installer Complete ===${NC}" -echo -e "${BLUE}Thank you for using RedFlag!${NC}" -`, baseURL) -} - -func (h *DownloadHandler) generateWindowsScript(baseURL string) string { - return fmt.Sprintf(`@echo off -REM RedFlag Agent Installation Script for Windows -REM This script downloads the agent and sets up Windows service -REM -REM Usage: -REM install.bat - Interactive mode (prompts for token) -REM install.bat YOUR_TOKEN_HERE - Automatic mode (uses provided token) - -set REDFLAG_SERVER=%s -set AGENT_DIR=%%ProgramFiles%%\RedFlag -set AGENT_BINARY=%%AGENT_DIR%%\redflag-agent.exe -set CONFIG_DIR=%%ProgramData%%\RedFlag - -echo === RedFlag Agent Installation === -echo. - -REM Check for admin privileges -net session >nul 2>&1 -if %%errorLevel%% neq 0 ( - echo ERROR: This script must be run as Administrator - echo Right-click and select "Run as administrator" - pause - exit /b 1 -) - -REM Detect architecture -if "%%PROCESSOR_ARCHITECTURE%%"=="AMD64" ( - set DOWNLOAD_ARCH=amd64 -) else if "%%PROCESSOR_ARCHITECTURE%%"=="ARM64" ( - set DOWNLOAD_ARCH=arm64 -) else ( - echo ERROR: Unsupported architecture: %%PROCESSOR_ARCHITECTURE%% - echo Supported: AMD64, ARM64 - pause - exit /b 1 -) - -echo Detected architecture: %%PROCESSOR_ARCHITECTURE%% (using windows-%%DOWNLOAD_ARCH%%) -echo. - -REM Create installation directory -echo Creating installation directory... -if not exist "%%AGENT_DIR%%" mkdir "%%AGENT_DIR%%" -echo [OK] Installation directory created - -REM Create config directory -if not exist "%%CONFIG_DIR%%" mkdir "%%CONFIG_DIR%%" -echo [OK] Configuration directory created - -REM Grant full permissions to SYSTEM and Administrators on config directory -echo Setting permissions on configuration directory... -icacls "%%CONFIG_DIR%%" /grant "SYSTEM:(OI)(CI)F" -icacls "%%CONFIGDIR%%" /grant "Administrators:(OI)(CI)F" -echo [OK] Permissions set -echo. - -REM Stop existing service if running (to allow binary update) -sc query RedFlagAgent >nul 2>&1 -if %%errorLevel%% equ 0 ( - echo Existing service detected - stopping to allow update... - sc stop RedFlagAgent >nul 2>&1 - timeout /t 3 /nobreak >nul - echo [OK] Service stopped -) - -REM Download agent binary -echo Downloading agent binary... -echo From: %%REDFLAG_SERVER%%/api/v1/downloads/windows-%%DOWNLOAD_ARCH%% -curl -sfL "%%REDFLAG_SERVER%%/api/v1/downloads/windows-%%DOWNLOAD_ARCH%%" -o "%%AGENT_BINARY%%" -if %%errorLevel%% neq 0 ( - echo ERROR: Failed to download agent binary - echo Please ensure %%REDFLAG_SERVER%% is accessible - pause - exit /b 1 -) -echo [OK] Agent binary downloaded -echo. - -REM Agent registration -echo === Agent Registration === -echo. - -REM Check if token was provided as command-line argument -if not "%%1"=="" ( - set TOKEN=%%1 - echo Using provided registration token -) else ( - echo IMPORTANT: You need a registration token to enroll this agent. - echo. - echo To get a token: - echo 1. Visit: %%REDFLAG_SERVER%%/settings/tokens - echo 2. Create a new registration token - echo 3. Copy the token - echo. - set /p TOKEN="Enter registration token (or press Enter to skip): " -) - -REM Check if agent is already registered -if exist "%%CONFIG_DIR%%\config.json" ( - echo. - echo [INFO] Agent already registered - configuration file exists - echo [INFO] Skipping registration to preserve agent history - echo [INFO] If you need to re-register, delete: %%CONFIG_DIR%%\config.json - echo. -) else if not "%%TOKEN%%"=="" ( - echo. - echo === Registering Agent === - echo. - - REM Attempt registration - "%%AGENT_BINARY%%" --server "%%REDFLAG_SERVER%%" --token "%%TOKEN%%" --register - - REM Check exit code - if %%errorLevel%% equ 0 ( - echo [OK] Agent registered successfully - echo [OK] Configuration saved to: %%CONFIG_DIR%%\config.json - echo. - ) else ( - echo. - echo [ERROR] Registration failed - echo. - echo Please check: - echo 1. Server is accessible: %%REDFLAG_SERVER%% - echo 2. Registration token is valid and not expired - echo 3. Token has available seats remaining - echo. - echo To try again: - echo "%%AGENT_BINARY%%" --server "%%REDFLAG_SERVER%%" --token "%%TOKEN%%" --register - echo. - pause - exit /b 1 - ) -) else ( - echo. - echo [INFO] No registration token provided - skipping registration - echo. - echo To register later: - echo "%%AGENT_BINARY%%" --server "%%REDFLAG_SERVER%%" --token YOUR_TOKEN --register -) - -REM Check if service already exists -echo. -echo === Configuring Windows Service === -echo. - -sc query RedFlagAgent >nul 2>&1 -if %%errorLevel%% equ 0 ( - echo [INFO] RedFlag Agent service already installed - echo [INFO] Service will be restarted with updated binary - echo. -) else ( - echo Installing RedFlag Agent service... - "%%AGENT_BINARY%%" -install-service - if %%errorLevel%% equ 0 ( - echo [OK] Service installed successfully - echo. - - REM Give Windows SCM time to register the service - timeout /t 2 /nobreak >nul - ) else ( - echo [ERROR] Failed to install service - echo. - pause - exit /b 1 - ) -) - -REM Start the service if agent is registered -if exist "%%CONFIG_DIR%%\config.json" ( - echo Starting RedFlag Agent service... - "%%AGENT_BINARY%%" -start-service - if %%errorLevel%% equ 0 ( - echo [OK] RedFlag Agent service started - echo. - echo Agent is now running as a Windows service in the background. - echo You can verify it is working by checking the agent status in the web UI. - ) else ( - echo [WARNING] Failed to start service. You can start it manually: - echo "%%AGENT_BINARY%%" -start-service - echo Or use Windows Services: services.msc - ) -) else ( - echo [WARNING] Service not started (agent not registered) - echo To register and start the service: - echo 1. Register: "%%AGENT_BINARY%%" --server "%%REDFLAGSERVER%%" --token YOUR_TOKEN --register - echo 2. Start: "%%AGENT_BINARY%%" -start-service -) - -echo. -echo === Installation Complete === -echo. -echo The RedFlag agent has been installed as a Windows service. -echo Configuration file: %%CONFIG_DIR%%\config.json -echo Agent binary: %%AGENT_BINARY%% -echo. - -echo Managing the RedFlag Agent service: -echo Check status: "%%AGENT_BINARY%%" -service-status -echo Start manually: "%%AGENT_BINARY%%" -start-service -echo Stop service: "%%AGENT_BINARY%%" -stop-service -echo Remove service: "%%AGENT_BINARY%%" -remove-service -echo. - -echo Alternative management with Windows Services: -echo Open services.msc and look for "RedFlag Update Agent" -echo. - -echo To run the agent directly (for debugging): -echo "%%AGENT_BINARY%%" -echo. - -echo To verify the agent is working: -echo 1. Check the web UI for the agent status -echo 2. Look for recent check-ins from this machine -echo. - -pause -`, baseURL) -} \ No newline at end of file diff --git a/aggregator-server/internal/services/install_template_service.go b/aggregator-server/internal/services/install_template_service.go new file mode 100644 index 0000000..3ba537c --- /dev/null +++ b/aggregator-server/internal/services/install_template_service.go @@ -0,0 +1,102 @@ +package services + +import ( + "bytes" + "embed" + "fmt" + "strings" + "text/template" + + "github.com/Fimeg/RedFlag/aggregator-server/internal/models" +) + +//go:embed templates/install/scripts/*.tmpl +var installScriptTemplates embed.FS + +// InstallTemplateService renders installation scripts from templates +type InstallTemplateService struct{} + +// NewInstallTemplateService creates a new template service +func NewInstallTemplateService() *InstallTemplateService { + return &InstallTemplateService{} +} + +// RenderInstallScript renders an installation script for the specified platform +func (s *InstallTemplateService) RenderInstallScript(agent *models.Agent, binaryURL, configURL string) (string, error) { + // Define template data + data := struct { + AgentID string + BinaryURL string + ConfigURL string + Platform string + Version string + }{ + AgentID: agent.ID.String(), + BinaryURL: binaryURL, + ConfigURL: configURL, + Platform: agent.OSType, + Version: agent.CurrentVersion, + } + + // Choose template based on platform + var templateName string + if strings.Contains(agent.OSType, "windows") { + templateName = "templates/install/scripts/windows.ps1.tmpl" + } else { + templateName = "templates/install/scripts/linux.sh.tmpl" + } + + // Load and parse template + tmpl, err := template.ParseFS(installScriptTemplates, templateName) + if err != nil { + return "", fmt.Errorf("failed to load template: %w", err) + } + + // Render template + var buf bytes.Buffer + if err := tmpl.Execute(&buf, data); err != nil { + return "", fmt.Errorf("failed to render template: %w", err) + } + + return buf.String(), nil +} + +// RenderInstallScriptFromBuild renders script using build response +func (s *InstallTemplateService) RenderInstallScriptFromBuild( + agentID string, + platform string, + version string, + binaryURL string, + configURL string, +) (string, error) { + data := struct { + AgentID string + BinaryURL string + ConfigURL string + Platform string + Version string + }{ + AgentID: agentID, + BinaryURL: binaryURL, + ConfigURL: configURL, + Platform: platform, + Version: version, + } + + templateName := "templates/install/scripts/linux.sh.tmpl" + if strings.Contains(platform, "windows") { + templateName = "templates/install/scripts/windows.ps1.tmpl" + } + + tmpl, err := template.ParseFS(installScriptTemplates, templateName) + if err != nil { + return "", err + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, data); err != nil { + return "", err + } + + return buf.String(), nil +} diff --git a/aggregator-server/internal/services/templates/install/scripts/linux.sh.tmpl b/aggregator-server/internal/services/templates/install/scripts/linux.sh.tmpl new file mode 100644 index 0000000..5f605c5 --- /dev/null +++ b/aggregator-server/internal/services/templates/install/scripts/linux.sh.tmpl @@ -0,0 +1,66 @@ +#!/bin/bash +# RedFlag Agent Installer - Linux +# Generated for agent: {{.AgentID}} +# Platform: {{.Platform}} +# Version: {{.Version}} + +set -e + +AGENT_ID="{{.AgentID}}" +BINARY_URL="{{.BinaryURL}}" +CONFIG_URL="{{.ConfigURL}}" +INSTALL_DIR="/usr/local/bin" +CONFIG_DIR="/etc/redflag" +SERVICE_NAME="redflag-agent" +VERSION="{{.Version}}" + +echo "=== RedFlag Agent v${VERSION} Installation ===" +echo "Agent ID: ${AGENT_ID}" +echo "Platform: {{.Platform}}" +echo "Installing to: ${INSTALL_DIR}/${SERVICE_NAME}" +echo + +# Step 1: Create directories +echo "Creating directories..." +sudo mkdir -p "${CONFIG_DIR}" +sudo mkdir -p "/var/lib/redflag" +sudo mkdir -p "/var/log/redflag" + +# Step 2: Download agent binary +echo "Downloading agent binary..." +sudo curl -sSL -o "${INSTALL_DIR}/${SERVICE_NAME}" "${BINARY_URL}" +sudo chmod +x "${INSTALL_DIR}/${SERVICE_NAME}" + +# Step 3: Download configuration +echo "Downloading configuration..." +sudo curl -sSL -o "${CONFIG_DIR}/config.json" "${CONFIG_URL}" +sudo chmod 600 "${CONFIG_DIR}/config.json" + +# Step 4: Create systemd service +echo "Creating systemd service..." +cat <