fix: comma-ok guards on agent-supplied metadata and gin context
Malformed agent metadata could panic the server (rapid_polling fields, buffered event metadata, timeout params). scanner_config asserted uuid.UUID on a user_id the middleware stores as string — guaranteed panic on both admin endpoints.
This commit is contained in:
parent
86b0944551
commit
230b2f9ab9
5 changed files with 49 additions and 39 deletions
|
|
@ -527,6 +527,8 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
|
|||
message := getStringFromMap(eventMap, "message")
|
||||
|
||||
if eventType != "" && eventSubtype != "" && severity != "" {
|
||||
// metadata is optional and agent-supplied; don't trust its shape
|
||||
metadata, _ := eventMap["metadata"].(map[string]interface{})
|
||||
event := &models.SystemEvent{
|
||||
AgentID: &agentID,
|
||||
EventType: eventType,
|
||||
|
|
@ -534,7 +536,7 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
|
|||
Severity: severity,
|
||||
Component: component,
|
||||
Message: message,
|
||||
Metadata: eventMap["metadata"].(map[string]interface{}),
|
||||
Metadata: metadata,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
|
||||
|
|
@ -637,12 +639,12 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
|
|||
|
||||
// Process heartbeat metadata from agent check-ins
|
||||
if metrics.Metadata != nil {
|
||||
if rapidPollingEnabled, exists := metrics.Metadata["rapid_polling_enabled"]; exists {
|
||||
if rapidPollingUntil, exists := metrics.Metadata["rapid_polling_until"]; exists {
|
||||
if rapidPollingEnabled, ok := metrics.Metadata["rapid_polling_enabled"].(bool); ok {
|
||||
if rapidPollingUntil, ok := metrics.Metadata["rapid_polling_until"].(string); ok {
|
||||
// Parse the until timestamp
|
||||
if untilTime, err := time.Parse(time.RFC3339, rapidPollingUntil.(string)); err == nil {
|
||||
if untilTime, err := time.Parse(time.RFC3339, rapidPollingUntil); err == nil {
|
||||
// Validate if rapid polling is still active (not expired)
|
||||
isActive := rapidPollingEnabled.(bool) && time.Now().UTC().Before(untilTime)
|
||||
isActive := rapidPollingEnabled && time.Now().UTC().Before(untilTime)
|
||||
|
||||
// Store heartbeat status in agent metadata
|
||||
agent.Metadata["rapid_polling_enabled"] = rapidPollingEnabled
|
||||
|
|
@ -683,12 +685,12 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
|
|||
if metrics.Metadata != nil {
|
||||
agent, err := h.agentQueries.GetAgentByID(agentID)
|
||||
if err == nil && agent.Metadata != nil {
|
||||
if rapidPollingEnabled, exists := metrics.Metadata["rapid_polling_enabled"]; exists {
|
||||
if rapidPollingUntil, exists := metrics.Metadata["rapid_polling_until"]; exists {
|
||||
if rapidPollingEnabled, ok := metrics.Metadata["rapid_polling_enabled"].(bool); ok {
|
||||
if rapidPollingUntil, ok := metrics.Metadata["rapid_polling_until"].(string); ok {
|
||||
// Parse the until timestamp
|
||||
if untilTime, err := time.Parse(time.RFC3339, rapidPollingUntil.(string)); err == nil {
|
||||
if untilTime, err := time.Parse(time.RFC3339, rapidPollingUntil); err == nil {
|
||||
// Validate if rapid polling is still active (not expired)
|
||||
isActive := rapidPollingEnabled.(bool) && time.Now().UTC().Before(untilTime)
|
||||
isActive := rapidPollingEnabled && time.Now().UTC().Before(untilTime)
|
||||
|
||||
// Store heartbeat status in agent metadata
|
||||
agent.Metadata["rapid_polling_enabled"] = rapidPollingEnabled
|
||||
|
|
@ -899,8 +901,8 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
|
|||
// Check if agent is reporting heartbeat in this check-in
|
||||
agentReportingHeartbeat := false
|
||||
if metrics.Metadata != nil {
|
||||
if agentEnabled, exists := metrics.Metadata["rapid_polling_enabled"]; exists {
|
||||
agentReportingHeartbeat = agentEnabled.(bool)
|
||||
if agentEnabled, ok := metrics.Metadata["rapid_polling_enabled"].(bool); ok {
|
||||
agentReportingHeartbeat = agentEnabled
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1131,28 +1133,28 @@ func (h *AgentHandler) GetHeartbeatStatus(c *gin.Context) {
|
|||
|
||||
if agent.Metadata != nil {
|
||||
// Check if heartbeat is enabled in metadata
|
||||
if enabled, exists := agent.Metadata["rapid_polling_enabled"]; exists {
|
||||
response["enabled"] = enabled.(bool)
|
||||
if enabled, ok := agent.Metadata["rapid_polling_enabled"].(bool); ok {
|
||||
response["enabled"] = enabled
|
||||
|
||||
// If enabled, get the until time and check if still active
|
||||
if enabled.(bool) {
|
||||
if untilStr, exists := agent.Metadata["rapid_polling_until"]; exists {
|
||||
response["until"] = untilStr.(string)
|
||||
if enabled {
|
||||
if untilStr, ok := agent.Metadata["rapid_polling_until"].(string); ok {
|
||||
response["until"] = untilStr
|
||||
|
||||
// Parse the until timestamp to check if still active
|
||||
if untilTime, err := time.Parse(time.RFC3339, untilStr.(string)); err == nil {
|
||||
if untilTime, err := time.Parse(time.RFC3339, untilStr); err == nil {
|
||||
response["active"] = time.Now().UTC().Before(untilTime)
|
||||
}
|
||||
}
|
||||
|
||||
// Get duration if available
|
||||
if duration, exists := agent.Metadata["rapid_polling_duration_minutes"]; exists {
|
||||
response["duration_minutes"] = duration.(float64)
|
||||
if duration, ok := agent.Metadata["rapid_polling_duration_minutes"].(float64); ok {
|
||||
response["duration_minutes"] = duration
|
||||
}
|
||||
|
||||
// Get source if available
|
||||
if source, exists := agent.Metadata["heartbeat_source"]; exists {
|
||||
response["source"] = source.(string)
|
||||
if source, ok := agent.Metadata["heartbeat_source"].(string); ok {
|
||||
response["source"] = source
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,15 +68,9 @@ func (h *RateLimitHandler) ResetRateLimitSettings(c *gin.Context) {
|
|||
func (h *RateLimitHandler) GetRateLimitStats(c *gin.Context) {
|
||||
settings := h.rateLimiter.GetSettings()
|
||||
|
||||
// Calculate total requests and windows
|
||||
stats := gin.H{
|
||||
"total_configured_limits": 6,
|
||||
"enabled_limits": 0,
|
||||
"total_requests_per_minute": 0,
|
||||
"settings": settings,
|
||||
}
|
||||
|
||||
// Count enabled limits and total requests
|
||||
enabledLimits := 0
|
||||
totalRequestsPerMinute := 0
|
||||
for _, config := range []middleware.RateLimitConfig{
|
||||
settings.AgentRegistration,
|
||||
settings.AgentCheckIn,
|
||||
|
|
@ -86,9 +80,16 @@ func (h *RateLimitHandler) GetRateLimitStats(c *gin.Context) {
|
|||
settings.PublicAccess,
|
||||
} {
|
||||
if config.Enabled {
|
||||
stats["enabled_limits"] = stats["enabled_limits"].(int) + 1
|
||||
enabledLimits++
|
||||
}
|
||||
stats["total_requests_per_minute"] = stats["total_requests_per_minute"].(int) + config.Requests
|
||||
totalRequestsPerMinute += config.Requests
|
||||
}
|
||||
|
||||
stats := gin.H{
|
||||
"total_configured_limits": 6,
|
||||
"enabled_limits": enabledLimits,
|
||||
"total_requests_per_minute": totalRequestsPerMinute,
|
||||
"settings": settings,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, stats)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
|
@ -77,7 +76,8 @@ func (h *ScannerConfigHandler) UpdateScannerTimeout(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Create audit event in History table (ETHOS compliance)
|
||||
userID := c.MustGet("user_id").(uuid.UUID)
|
||||
// user_id is stored as a string by WebAuthMiddleware
|
||||
userID := c.GetString("user_id")
|
||||
/*
|
||||
event := &models.SystemEvent{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
|
|
@ -129,7 +129,7 @@ func (h *ScannerConfigHandler) ResetScannerTimeout(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Audit log
|
||||
userID := c.MustGet("user_id").(uuid.UUID)
|
||||
userID := c.GetString("user_id")
|
||||
log.Printf("[AUDIT] User %s reset scanner timeout: %s to default %v", userID, scannerName, defaultTimeout)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
|
|
|
|||
|
|
@ -187,9 +187,10 @@ func (h *SecurityHandler) MachineBindingStatus(c *gin.Context) {
|
|||
response["checks"].(map[string]interface{})["version_compliance"] = compliantAgents
|
||||
}
|
||||
|
||||
// Set recent violations based on version compliance gap
|
||||
boundAgents := response["checks"].(map[string]interface{})["bound_agents"].(int)
|
||||
versionCompliance := response["checks"].(map[string]interface{})["version_compliance"].(int)
|
||||
// Set recent violations based on version compliance gap.
|
||||
// Either count may be absent if its query failed above; default to 0.
|
||||
boundAgents, _ := response["checks"].(map[string]interface{})["bound_agents"].(int)
|
||||
versionCompliance, _ := response["checks"].(map[string]interface{})["version_compliance"].(int)
|
||||
violations := boundAgents - versionCompliance
|
||||
if violations < 0 {
|
||||
violations = 0
|
||||
|
|
|
|||
|
|
@ -257,9 +257,15 @@ func (ts *TimeoutService) updateRelatedPackageStatus(command *models.AgentComman
|
|||
"failure_reason": "operation timed out",
|
||||
}
|
||||
|
||||
packageType, typeOK := command.Params["package_type"].(string)
|
||||
packageName, nameOK := command.Params["package_name"].(string)
|
||||
if !typeOK || !nameOK {
|
||||
return fmt.Errorf("command %s has update_id but missing package_type/package_name params", command.ID)
|
||||
}
|
||||
|
||||
return ts.updateQueries.UpdatePackageStatus(command.AgentID,
|
||||
command.Params["package_type"].(string),
|
||||
command.Params["package_name"].(string),
|
||||
packageType,
|
||||
packageName,
|
||||
models.StatusFailed,
|
||||
metadata,
|
||||
nil) // nil = use time.Now().UTC() for timeout operations
|
||||
|
|
|
|||
Loading…
Reference in a new issue