package middleware import ( "log" "net/http" "github.com/Fimeg/RedFlag/aggregator-server/internal/database/queries" "github.com/Fimeg/RedFlag/aggregator-server/internal/utils" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // MachineBindingMiddleware validates machine ID matches database record // This prevents agent impersonation via config file copying to different machines func MachineBindingMiddleware(agentQueries *queries.AgentQueries, minAgentVersion string) gin.HandlerFunc { return func(c *gin.Context) { // Skip if not authenticated (handled by auth middleware) agentIDVal, exists := c.Get("agent_id") if !exists { c.Next() return } agentID, ok := agentIDVal.(uuid.UUID) if !ok { log.Printf("[MachineBinding] Invalid agent_id type in context") c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid agent ID"}) c.Abort() return } // Get agent from database agent, err := agentQueries.GetAgentByID(agentID) if err != nil { log.Printf("[MachineBinding] Agent %s not found: %v", agentID, err) c.JSON(http.StatusUnauthorized, gin.H{"error": "agent not found"}) c.Abort() return } // Check minimum version (hard cutoff for legacy de-support) if agent.CurrentVersion != "" && minAgentVersion != "" { if !utils.IsNewerOrEqualVersion(agent.CurrentVersion, minAgentVersion) { log.Printf("[MachineBinding] Agent %s version %s below minimum %s - rejecting", agent.Hostname, agent.CurrentVersion, minAgentVersion) c.JSON(http.StatusUpgradeRequired, gin.H{ "error": "agent version too old - upgrade required for security", "current_version": agent.CurrentVersion, "minimum_version": minAgentVersion, "upgrade_instructions": "Please upgrade to the latest agent version and re-register", }) c.Abort() return } } // Extract X-Machine-ID header reportedMachineID := c.GetHeader("X-Machine-ID") if reportedMachineID == "" { log.Printf("[MachineBinding] Agent %s (%s) missing X-Machine-ID header", agent.Hostname, agentID) c.JSON(http.StatusForbidden, gin.H{ "error": "missing machine ID header - agent version too old or tampered", "hint": "Please upgrade to the latest agent version (v0.1.22+)", }) c.Abort() return } // Validate machine ID matches database if agent.MachineID == nil { log.Printf("[MachineBinding] Agent %s (%s) has no machine_id in database - legacy agent", agent.Hostname, agentID) c.JSON(http.StatusForbidden, gin.H{ "error": "agent not bound to machine - re-registration required", "hint": "This agent was registered before v0.1.22. Please re-register with a new registration token.", }) c.Abort() return } if *agent.MachineID != reportedMachineID { log.Printf("[MachineBinding] ⚠️ SECURITY ALERT: Agent %s (%s) machine ID mismatch! DB=%s, Reported=%s", agent.Hostname, agentID, *agent.MachineID, reportedMachineID) c.JSON(http.StatusForbidden, gin.H{ "error": "machine ID mismatch - config file copied to different machine", "hint": "Agent configuration is bound to the original machine. Please register this machine with a new registration token.", "security_note": "This prevents agent impersonation attacks", }) c.Abort() return } // Machine ID validated - allow request log.Printf("[MachineBinding] ✓ Agent %s (%s) machine ID validated: %s", agent.Hostname, agentID, reportedMachineID[:16]+"...") c.Next() } }