feat: add config sync endpoint and security UI updates

- Add GET /api/v1/agents/:id/config endpoint for server configuration
- Agent fetches config during check-in and applies updates
- Add version tracking to prevent unnecessary config applications
- Clean separation: config sync independent of commands
- Fix agent UI subsystem settings to actually control agent behavior
- Update Security Health UI with frosted glass styling and tooltips
This commit is contained in:
Fimeg
2025-11-03 22:36:26 -05:00
parent eccc38d7c9
commit 38894f64d3
18 changed files with 944 additions and 87 deletions

View File

@@ -19,16 +19,18 @@ type AgentHandler struct {
commandQueries *queries.CommandQueries
refreshTokenQueries *queries.RefreshTokenQueries
registrationTokenQueries *queries.RegistrationTokenQueries
subsystemQueries *queries.SubsystemQueries
checkInInterval int
latestAgentVersion string
}
func NewAgentHandler(aq *queries.AgentQueries, cq *queries.CommandQueries, rtq *queries.RefreshTokenQueries, regTokenQueries *queries.RegistrationTokenQueries, checkInInterval int, latestAgentVersion string) *AgentHandler {
func NewAgentHandler(aq *queries.AgentQueries, cq *queries.CommandQueries, rtq *queries.RefreshTokenQueries, regTokenQueries *queries.RegistrationTokenQueries, sq *queries.SubsystemQueries, checkInInterval int, latestAgentVersion string) *AgentHandler {
return &AgentHandler{
agentQueries: aq,
commandQueries: cq,
refreshTokenQueries: rtq,
registrationTokenQueries: regTokenQueries,
subsystemQueries: sq,
checkInInterval: checkInInterval,
latestAgentVersion: latestAgentVersion,
}
@@ -1136,3 +1138,44 @@ func (h *AgentHandler) TriggerReboot(c *gin.Context) {
"hostname": agent.Hostname,
})
}
// GetAgentConfig returns current subsystem configuration for an agent
// GET /api/v1/agents/:id/config
func (h *AgentHandler) GetAgentConfig(c *gin.Context) {
idStr := c.Param("id")
agentID, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
return
}
// Verify agent exists
agent, err := h.agentQueries.GetAgentByID(agentID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "agent not found"})
return
}
// Get subsystem configuration from database
subsystems, err := h.subsystemQueries.GetSubsystems(agentID)
if err != nil {
log.Printf("Failed to get subsystems for agent %s: %v", agentID, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get subsystem configuration"})
return
}
// Convert to simple format for agent
config := make(map[string]interface{})
for _, subsystem := range subsystems {
config[subsystem.Subsystem] = map[string]interface{}{
"enabled": subsystem.Enabled,
"interval_minutes": subsystem.IntervalMinutes,
"auto_run": subsystem.AutoRun,
}
}
c.JSON(http.StatusOK, gin.H{
"subsystems": config,
"version": time.Now().Unix(), // Simple version timestamp
})
}

View File

@@ -68,13 +68,13 @@ func (h *DockerReportsHandler) ReportDockerImages(c *gin.Context) {
event := models.StoredDockerImage{
ID: uuid.New(),
AgentID: agentID,
PackageType: item.PackageType,
PackageName: item.PackageName,
CurrentVersion: item.CurrentVersion,
AvailableVersion: item.AvailableVersion,
PackageType: "docker_image",
PackageName: item.ImageName + ":" + item.ImageTag,
CurrentVersion: item.ImageID,
AvailableVersion: item.LatestImageID,
Severity: item.Severity,
RepositorySource: item.RepositorySource,
Metadata: models.JSONB(item.Metadata),
Metadata: convertToJSONB(item.Metadata),
EventType: "discovered",
CreatedAt: req.Timestamp,
}
@@ -123,6 +123,8 @@ func (h *DockerReportsHandler) GetAgentDockerImages(c *gin.Context) {
pageSize = 50
}
offset := (page - 1) * pageSize
imageName := c.Query("image_name")
registry := c.Query("registry")
severity := c.Query("severity")
@@ -136,7 +138,7 @@ func (h *DockerReportsHandler) GetAgentDockerImages(c *gin.Context) {
Severity: nil,
HasUpdates: nil,
Limit: &pageSize,
Offset: &((page - 1) * pageSize),
Offset: &(offset),
}
if imageName != "" {
@@ -274,4 +276,13 @@ func countUpdates(images []models.DockerImageInfo) int {
}
}
return count
}
// Helper function to convert map[string]interface{} to models.JSONB
func convertToJSONB(data map[string]interface{}) models.JSONB {
result := make(map[string]interface{})
for k, v := range data {
result[k] = v
}
return models.JSONB(result)
}