feat: machine binding and version enforcement
migration 017 adds machine_id to agents table middleware validates X-Machine-ID header on authed routes agent client sends machine ID with requests MIN_AGENT_VERSION config defaults 0.1.22 version utils added for comparison blocks config copying attacks via hardware fingerprint old agents get 426 upgrade required breaking: <0.1.22 agents rejected
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@@ -129,6 +130,7 @@ func main() {
|
||||
registrationTokenQueries := queries.NewRegistrationTokenQueries(db.DB)
|
||||
userQueries := queries.NewUserQueries(db.DB)
|
||||
subsystemQueries := queries.NewSubsystemQueries(db.DB)
|
||||
agentUpdateQueries := queries.NewAgentUpdateQueries(db.DB)
|
||||
|
||||
// Ensure admin user exists
|
||||
if err := userQueries.EnsureAdminUser(cfg.Admin.Username, cfg.Admin.Username+"@redflag.local", cfg.Admin.Password); err != nil {
|
||||
@@ -141,6 +143,20 @@ func main() {
|
||||
timezoneService := services.NewTimezoneService(cfg)
|
||||
timeoutService := services.NewTimeoutService(commandQueries, updateQueries)
|
||||
|
||||
// Initialize signing service if private key is configured
|
||||
var signingService *services.SigningService
|
||||
if cfg.SigningPrivateKey != "" {
|
||||
var err error
|
||||
signingService, err = services.NewSigningService(cfg.SigningPrivateKey)
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to initialize signing service: %v", err)
|
||||
} else {
|
||||
log.Printf("✅ Ed25519 signing service initialized")
|
||||
}
|
||||
} else {
|
||||
log.Printf("Warning: No signing private key configured - agent update signing disabled")
|
||||
}
|
||||
|
||||
// Initialize rate limiter
|
||||
rateLimiter := middleware.NewRateLimiter()
|
||||
|
||||
@@ -156,6 +172,21 @@ func main() {
|
||||
downloadHandler := handlers.NewDownloadHandler(filepath.Join("/app"), cfg)
|
||||
subsystemHandler := handlers.NewSubsystemHandler(subsystemQueries, commandQueries)
|
||||
|
||||
// Initialize verification handler
|
||||
var verificationHandler *handlers.VerificationHandler
|
||||
if signingService != nil {
|
||||
verificationHandler = handlers.NewVerificationHandler(agentQueries, signingService)
|
||||
}
|
||||
|
||||
// Initialize agent update handler
|
||||
var agentUpdateHandler *handlers.AgentUpdateHandler
|
||||
if signingService != nil {
|
||||
agentUpdateHandler = handlers.NewAgentUpdateHandler(agentQueries, agentUpdateQueries, commandQueries, signingService, agentHandler)
|
||||
}
|
||||
|
||||
// Initialize system handler
|
||||
systemHandler := handlers.NewSystemHandler(signingService)
|
||||
|
||||
// Setup router
|
||||
router := gin.Default()
|
||||
|
||||
@@ -178,17 +209,23 @@ func main() {
|
||||
api.POST("/auth/logout", authHandler.Logout)
|
||||
api.GET("/auth/verify", authHandler.VerifyToken)
|
||||
|
||||
// Public system routes (no authentication required)
|
||||
api.GET("/public-key", rateLimiter.RateLimit("public_access", middleware.KeyByIP), systemHandler.GetPublicKey)
|
||||
api.GET("/info", rateLimiter.RateLimit("public_access", middleware.KeyByIP), systemHandler.GetSystemInfo)
|
||||
|
||||
// Public routes (no authentication required, with rate limiting)
|
||||
api.POST("/agents/register", rateLimiter.RateLimit("agent_registration", middleware.KeyByIP), agentHandler.RegisterAgent)
|
||||
api.POST("/agents/renew", rateLimiter.RateLimit("public_access", middleware.KeyByIP), agentHandler.RenewToken)
|
||||
|
||||
// Public download routes (no authentication - agents need these!)
|
||||
api.GET("/downloads/:platform", rateLimiter.RateLimit("public_access", middleware.KeyByIP), downloadHandler.DownloadAgent)
|
||||
api.GET("/downloads/updates/:package_id", rateLimiter.RateLimit("public_access", middleware.KeyByIP), downloadHandler.DownloadUpdatePackage)
|
||||
api.GET("/install/:platform", rateLimiter.RateLimit("public_access", middleware.KeyByIP), downloadHandler.InstallScript)
|
||||
|
||||
// Protected agent routes
|
||||
// Protected agent routes (with machine binding security)
|
||||
agents := api.Group("/agents")
|
||||
agents.Use(middleware.AuthMiddleware())
|
||||
agents.Use(middleware.MachineBindingMiddleware(agentQueries, cfg.MinAgentVersion)) // v0.1.22: Prevent config copying
|
||||
{
|
||||
agents.GET("/:id/commands", agentHandler.GetCommands)
|
||||
agents.POST("/:id/updates", rateLimiter.RateLimit("agent_reports", middleware.KeyByAgentID), updateHandler.ReportUpdates)
|
||||
@@ -196,6 +233,13 @@ func main() {
|
||||
agents.POST("/:id/dependencies", rateLimiter.RateLimit("agent_reports", middleware.KeyByAgentID), updateHandler.ReportDependencies)
|
||||
agents.POST("/:id/system-info", rateLimiter.RateLimit("agent_reports", middleware.KeyByAgentID), agentHandler.ReportSystemInfo)
|
||||
agents.POST("/:id/rapid-mode", rateLimiter.RateLimit("agent_reports", middleware.KeyByAgentID), agentHandler.SetRapidPollingMode)
|
||||
agents.POST("/:id/verify-signature", rateLimiter.RateLimit("agent_reports", middleware.KeyByAgentID), func(c *gin.Context) {
|
||||
if verificationHandler == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "signature verification service not available"})
|
||||
return
|
||||
}
|
||||
verificationHandler.VerifySignature(c)
|
||||
})
|
||||
agents.DELETE("/:id", agentHandler.UnregisterAgent)
|
||||
|
||||
// Subsystem routes
|
||||
@@ -231,6 +275,14 @@ func main() {
|
||||
dashboard.POST("/updates/:id/install", updateHandler.InstallUpdate)
|
||||
dashboard.POST("/updates/:id/confirm-dependencies", updateHandler.ConfirmDependencies)
|
||||
|
||||
// Agent update routes
|
||||
if agentUpdateHandler != nil {
|
||||
dashboard.POST("/agents/:id/update", agentUpdateHandler.UpdateAgent)
|
||||
dashboard.POST("/agents/bulk-update", agentUpdateHandler.BulkUpdateAgents)
|
||||
dashboard.GET("/updates/packages", agentUpdateHandler.ListUpdatePackages)
|
||||
dashboard.POST("/updates/packages/sign", agentUpdateHandler.SignUpdatePackage)
|
||||
}
|
||||
|
||||
// Log routes
|
||||
dashboard.GET("/logs", updateHandler.GetAllLogs)
|
||||
dashboard.GET("/logs/active", updateHandler.GetActiveOperations)
|
||||
|
||||
Reference in New Issue
Block a user