feat: Annotate monitor, secrets and vulnerability routes
Same treatment: named types replace gin.H literals, and every handler gets a swaggo doc block. VulnSummaryResponse uses pointer fields so the db-freshness block stays entirely absent when no vulndb_meta document exists yet, matching the handler's original conditional gin.H exactly.
This commit is contained in:
@@ -21,6 +21,16 @@ func registerMonitorRoutes(g *gin.RouterGroup) {
|
||||
g.GET("/monitors/:id/uptime", getMonitorUptime)
|
||||
}
|
||||
|
||||
// listMonitors godoc
|
||||
//
|
||||
// @Summary List monitors
|
||||
// @Tags monitors
|
||||
// @Produce json
|
||||
// @Success 200 {array} models.Monitor
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /monitors [get]
|
||||
func listMonitors(c *gin.Context) {
|
||||
monitors, err := services.ListMonitors(auth.InstanceID(c))
|
||||
if err != nil {
|
||||
@@ -30,6 +40,20 @@ func listMonitors(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, monitors)
|
||||
}
|
||||
|
||||
// createMonitor godoc
|
||||
//
|
||||
// @Summary Create a monitor
|
||||
// @Tags monitors
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body models.Monitor true "Monitor to create"
|
||||
// @Success 201 {object} models.Monitor
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Failure 403 {object} LimitExceededResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /monitors [post]
|
||||
func createMonitor(c *gin.Context) {
|
||||
var m models.Monitor
|
||||
if err := c.ShouldBindJSON(&m); err != nil {
|
||||
@@ -55,6 +79,18 @@ func createMonitor(c *gin.Context) {
|
||||
c.JSON(http.StatusCreated, created)
|
||||
}
|
||||
|
||||
// getMonitor godoc
|
||||
//
|
||||
// @Summary Get a monitor
|
||||
// @Tags monitors
|
||||
// @Produce json
|
||||
// @Param id path string true "Monitor ID"
|
||||
// @Success 200 {object} models.Monitor
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /monitors/{id} [get]
|
||||
func getMonitor(c *gin.Context) {
|
||||
m, err := services.GetMonitor(auth.InstanceID(c), c.Param("id"))
|
||||
if err != nil {
|
||||
@@ -68,6 +104,20 @@ func getMonitor(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, m)
|
||||
}
|
||||
|
||||
// updateMonitor godoc
|
||||
//
|
||||
// @Summary Update a monitor
|
||||
// @Tags monitors
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Monitor ID"
|
||||
// @Param body body object{name=string,type=string,target=models.MonitorTarget,interval_sec=int,runner=string,retries=int,enabled=bool,channel_ids=[]string} true "Fields to update"
|
||||
// @Success 204
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /monitors/{id} [put]
|
||||
func updateMonitor(c *gin.Context) {
|
||||
var body struct {
|
||||
Name *string `json:"name"`
|
||||
@@ -119,6 +169,16 @@ func updateMonitor(c *gin.Context) {
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// deleteMonitor godoc
|
||||
//
|
||||
// @Summary Delete a monitor
|
||||
// @Tags monitors
|
||||
// @Param id path string true "Monitor ID"
|
||||
// @Success 204
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /monitors/{id} [delete]
|
||||
func deleteMonitor(c *gin.Context) {
|
||||
if err := services.DeleteMonitor(auth.InstanceID(c), c.Param("id")); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
@@ -127,6 +187,18 @@ func deleteMonitor(c *gin.Context) {
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// getMonitorIncidents godoc
|
||||
//
|
||||
// @Summary List a monitor's incidents
|
||||
// @Tags monitors
|
||||
// @Produce json
|
||||
// @Param id path string true "Monitor ID"
|
||||
// @Success 200 {array} models.Incident
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /monitors/{id}/incidents [get]
|
||||
func getMonitorIncidents(c *gin.Context) {
|
||||
m, err := services.GetMonitor(auth.InstanceID(c), c.Param("id"))
|
||||
if err != nil {
|
||||
@@ -145,6 +217,19 @@ func getMonitorIncidents(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, incidents)
|
||||
}
|
||||
|
||||
// getMonitorUptime godoc
|
||||
//
|
||||
// @Summary Get a monitor's uptime rollups
|
||||
// @Description Hourly rollups for the last 30 days.
|
||||
// @Tags monitors
|
||||
// @Produce json
|
||||
// @Param id path string true "Monitor ID"
|
||||
// @Success 200 {array} models.Rollup
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /monitors/{id}/uptime [get]
|
||||
func getMonitorUptime(c *gin.Context) {
|
||||
m, err := services.GetMonitor(auth.InstanceID(c), c.Param("id"))
|
||||
if err != nil {
|
||||
|
||||
@@ -37,6 +37,19 @@ func secretsReadAuth() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// esoGetGroup godoc
|
||||
//
|
||||
// @Summary Read a secret group's values (ESO)
|
||||
// @Description Consumed by Kubernetes External Secrets Operator. Authenticated with a bearer token whose SHA-256 hash is stored in settings — a different credential from an API token, never substitutable for one.
|
||||
// @Tags secrets
|
||||
// @Produce json
|
||||
// @Param group path string true "Secret group name"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 401 {object} ErrorResponse
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security esoAuth
|
||||
// @Router /secrets/{group}/values [get]
|
||||
func esoGetGroup(c *gin.Context) {
|
||||
group := c.Param("group")
|
||||
|
||||
@@ -58,6 +71,16 @@ func esoGetGroup(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, values)
|
||||
}
|
||||
|
||||
// listSecretGroups godoc
|
||||
//
|
||||
// @Summary List secret groups
|
||||
// @Tags secrets
|
||||
// @Produce json
|
||||
// @Success 200 {array} models.GroupSummary
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /secrets [get]
|
||||
func listSecretGroups(c *gin.Context) {
|
||||
groups, err := services.ListSecretGroups(auth.InstanceID(c))
|
||||
if err != nil {
|
||||
@@ -67,6 +90,20 @@ func listSecretGroups(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, groups)
|
||||
}
|
||||
|
||||
// createSecretGroup godoc
|
||||
//
|
||||
// @Summary Create a secret group
|
||||
// @Tags secrets
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body object{group=string,values=map[string]string} true "Group and its initial key/value pairs"
|
||||
// @Success 201 {object} GroupResponse
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Failure 403 {object} LimitExceededResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /secrets [post]
|
||||
func createSecretGroup(c *gin.Context) {
|
||||
var body struct {
|
||||
Group string `json:"group" binding:"required"`
|
||||
@@ -98,9 +135,22 @@ func createSecretGroup(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
services.LogEvent(auth.InstanceID(c), "secret.updated", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' created with keys: %s", body.Group, strings.Join(services.SortedKeys(body.Values), ", ")))
|
||||
c.JSON(http.StatusCreated, gin.H{"group": body.Group})
|
||||
c.JSON(http.StatusCreated, GroupResponse{Group: body.Group})
|
||||
}
|
||||
|
||||
// getSecretGroup godoc
|
||||
//
|
||||
// @Summary Get a secret group's keys
|
||||
// @Description Returns the group's key metadata, not decrypted values. See POST /secrets/{group}/reveal for a value.
|
||||
// @Tags secrets
|
||||
// @Produce json
|
||||
// @Param group path string true "Secret group name"
|
||||
// @Success 200 {object} SecretGroupResponse
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /secrets/{group} [get]
|
||||
func getSecretGroup(c *gin.Context) {
|
||||
group := c.Param("group")
|
||||
secrets, err := services.GetSecretGroup(auth.InstanceID(c), group)
|
||||
@@ -112,9 +162,24 @@ func getSecretGroup(c *gin.Context) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "group not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"group": group, "secrets": secrets})
|
||||
c.JSON(http.StatusOK, SecretGroupResponse{Group: group, Secrets: secrets})
|
||||
}
|
||||
|
||||
// putSecretGroup godoc
|
||||
//
|
||||
// @Summary Replace a secret group's keys
|
||||
// @Tags secrets
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param group path string true "Secret group name"
|
||||
// @Param body body map[string]string true "Key/value pairs"
|
||||
// @Success 200 {object} SavedResponse
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Failure 403 {object} LimitExceededResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /secrets/{group} [put]
|
||||
func putSecretGroup(c *gin.Context) {
|
||||
group := c.Param("group")
|
||||
if !validName(group) {
|
||||
@@ -144,9 +209,23 @@ func putSecretGroup(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
services.LogEvent(auth.InstanceID(c), "secret.updated", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' keys updated: %s", group, strings.Join(services.SortedKeys(values), ", ")))
|
||||
c.JSON(http.StatusOK, gin.H{"saved": true})
|
||||
c.JSON(http.StatusOK, SavedResponse{Saved: true})
|
||||
}
|
||||
|
||||
// revealSecret godoc
|
||||
//
|
||||
// @Summary Reveal a secret value
|
||||
// @Tags secrets
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param group path string true "Secret group name"
|
||||
// @Param body body object{key=string} true "Key to reveal"
|
||||
// @Success 200 {object} RevealSecretResponse
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /secrets/{group}/reveal [post]
|
||||
func revealSecret(c *gin.Context) {
|
||||
group := c.Param("group")
|
||||
var body struct {
|
||||
@@ -162,9 +241,21 @@ func revealSecret(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
services.LogEvent(auth.InstanceID(c), "secret.revealed", actorFromCtx(c), "", "", fmt.Sprintf("value of '%s/%s' revealed", group, body.Key))
|
||||
c.JSON(http.StatusOK, gin.H{"value": value})
|
||||
c.JSON(http.StatusOK, RevealSecretResponse{Value: value})
|
||||
}
|
||||
|
||||
// deleteSecretKey godoc
|
||||
//
|
||||
// @Summary Delete a key from a secret group
|
||||
// @Tags secrets
|
||||
// @Produce json
|
||||
// @Param group path string true "Secret group name"
|
||||
// @Param key path string true "Key name"
|
||||
// @Success 200 {object} DeletedResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /secrets/{group}/{key} [delete]
|
||||
func deleteSecretKey(c *gin.Context) {
|
||||
group := c.Param("group")
|
||||
key := c.Param("key")
|
||||
@@ -173,9 +264,20 @@ func deleteSecretKey(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
services.LogEvent(auth.InstanceID(c), "secret.deleted", actorFromCtx(c), "", "", fmt.Sprintf("key '%s' deleted from group '%s'", key, group))
|
||||
c.JSON(http.StatusOK, gin.H{"deleted": true})
|
||||
c.JSON(http.StatusOK, DeletedResponse{Deleted: true})
|
||||
}
|
||||
|
||||
// deleteSecretGroup godoc
|
||||
//
|
||||
// @Summary Delete a secret group
|
||||
// @Tags secrets
|
||||
// @Produce json
|
||||
// @Param group path string true "Secret group name"
|
||||
// @Success 200 {object} DeletedResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /secrets/{group} [delete]
|
||||
func deleteSecretGroup(c *gin.Context) {
|
||||
group := c.Param("group")
|
||||
if err := services.DeleteSecretGroup(auth.InstanceID(c), group); err != nil {
|
||||
@@ -183,9 +285,19 @@ func deleteSecretGroup(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
services.LogEvent(auth.InstanceID(c), "secretgroup.deleted", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' deleted", group))
|
||||
c.JSON(http.StatusOK, gin.H{"deleted": true})
|
||||
c.JSON(http.StatusOK, DeletedResponse{Deleted: true})
|
||||
}
|
||||
|
||||
// rotateSecretsToken godoc
|
||||
//
|
||||
// @Summary Rotate the ESO read token
|
||||
// @Tags settings
|
||||
// @Produce json
|
||||
// @Success 200 {object} SecretsTokenResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /settings/secrets-token [post]
|
||||
func rotateSecretsToken(c *gin.Context) {
|
||||
token, err := services.RotateSecretsReadToken(auth.InstanceID(c))
|
||||
if err != nil {
|
||||
@@ -193,5 +305,5 @@ func rotateSecretsToken(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
services.LogEvent(auth.InstanceID(c), "secrets.token_rotated", actorFromCtx(c), "", "", "ESO read token rotated")
|
||||
c.JSON(http.StatusOK, gin.H{"token": token})
|
||||
c.JSON(http.StatusOK, SecretsTokenResponse{Token: token})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,22 @@ type vulnGroup struct {
|
||||
Findings []models.VulnFinding `json:"findings"`
|
||||
}
|
||||
|
||||
// listVulnerabilities godoc
|
||||
//
|
||||
// @Summary List vulnerabilities
|
||||
// @Description Groups findings by CVE, most severe first — the same CVE on forty servers is one decision, not forty rows.
|
||||
// @Tags vulnerabilities
|
||||
// @Produce json
|
||||
// @Param severity query string false "Filter by severity"
|
||||
// @Param state query string false "Filter by state (default open)"
|
||||
// @Param server query string false "Filter by server ID"
|
||||
// @Param tag query []string false "Filter by tag as key:value, repeatable"
|
||||
// @Param has_fix query bool false "Filter by whether a vendor fix exists"
|
||||
// @Success 200 {array} vulnGroup
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /vulnerabilities [get]
|
||||
func listVulnerabilities(c *gin.Context) {
|
||||
findings, err := services.ListInstanceFindings(auth.InstanceID(c), services.FindingFilter{
|
||||
Severity: c.Query("severity"),
|
||||
@@ -115,6 +131,17 @@ func tagsFromQuery(c *gin.Context) map[string]string {
|
||||
return out
|
||||
}
|
||||
|
||||
// vulnerabilitySummary godoc
|
||||
//
|
||||
// @Summary Get vulnerability counts and database freshness
|
||||
// @Description Counts travel with the database version and pull time, since a fleet scanned against a stale database must say so wherever its findings are read.
|
||||
// @Tags vulnerabilities
|
||||
// @Produce json
|
||||
// @Success 200 {object} VulnSummaryResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /vulnerabilities/summary [get]
|
||||
func vulnerabilitySummary(c *gin.Context) {
|
||||
counts, err := services.CountOpenFindingsBySeverity(auth.InstanceID(c))
|
||||
if err != nil {
|
||||
@@ -122,24 +149,32 @@ func vulnerabilitySummary(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resp := gin.H{"counts": counts}
|
||||
resp := VulnSummaryResponse{Counts: counts}
|
||||
|
||||
// Database freshness travels with the counts rather than living in
|
||||
// settings: a fleet scanned against a three-week-old database must say so
|
||||
// wherever its findings are read, not somewhere the reader has to go and
|
||||
// look for it.
|
||||
if meta, err := services.GetVulnDBMeta(); err == nil && meta != nil {
|
||||
resp["db_version"] = meta.DBVersion
|
||||
resp["pulled_at"] = meta.PulledAt
|
||||
resp["last_full_scan_at"] = meta.LastFullScanAt
|
||||
if meta.LastError != "" {
|
||||
resp["last_error"] = meta.LastError
|
||||
}
|
||||
resp.DBVersion = &meta.DBVersion
|
||||
resp.PulledAt = &meta.PulledAt
|
||||
resp.LastFullScanAt = &meta.LastFullScanAt
|
||||
resp.LastError = meta.LastError
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// rescanVulnerabilities godoc
|
||||
//
|
||||
// @Summary Queue the fleet for a vulnerability rescan
|
||||
// @Tags vulnerabilities
|
||||
// @Produce json
|
||||
// @Success 200 {object} QueuedResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /vulnerabilities/rescan [post]
|
||||
func rescanVulnerabilities(c *gin.Context) {
|
||||
instanceID := auth.InstanceID(c)
|
||||
|
||||
@@ -151,7 +186,7 @@ func rescanVulnerabilities(c *gin.Context) {
|
||||
|
||||
services.LogEvent(instanceID, "vuln.rescan", actorFromCtx(c), "", "",
|
||||
"queued "+strconv.FormatInt(n, 10)+" server(s) for rescan")
|
||||
c.JSON(http.StatusOK, gin.H{"queued": n})
|
||||
c.JSON(http.StatusOK, QueuedResponse{Queued: n})
|
||||
}
|
||||
|
||||
type acceptFindingRequest struct {
|
||||
@@ -159,6 +194,22 @@ type acceptFindingRequest struct {
|
||||
Until time.Time `json:"until"`
|
||||
}
|
||||
|
||||
// acceptFinding godoc
|
||||
//
|
||||
// @Summary Accept a finding
|
||||
// @Description Requires a reason and a future expiry. Reopens automatically at expiry — permanent dismissal is never allowed.
|
||||
// @Tags vulnerabilities
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Finding ID"
|
||||
// @Param body body acceptFindingRequest true "Reason and expiry"
|
||||
// @Success 200 {object} models.VulnFinding
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /vulnerabilities/{id}/accept [post]
|
||||
func acceptFinding(c *gin.Context) {
|
||||
var req acceptFindingRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -192,6 +243,18 @@ func acceptFinding(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, f)
|
||||
}
|
||||
|
||||
// unacceptFinding godoc
|
||||
//
|
||||
// @Summary Return an accepted finding to open
|
||||
// @Tags vulnerabilities
|
||||
// @Produce json
|
||||
// @Param id path string true "Finding ID"
|
||||
// @Success 200 {object} models.VulnFinding
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /vulnerabilities/{id}/accept [delete]
|
||||
func unacceptFinding(c *gin.Context) {
|
||||
instanceID := auth.InstanceID(c)
|
||||
actor := actorFromCtx(c)
|
||||
@@ -215,6 +278,17 @@ func writeFindingError(c *gin.Context, err error) {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
|
||||
// listServerVulnerabilities godoc
|
||||
//
|
||||
// @Summary List a server's vulnerabilities
|
||||
// @Tags vulnerabilities
|
||||
// @Produce json
|
||||
// @Param id path string true "Server ID"
|
||||
// @Success 200 {array} models.VulnFinding
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /servers/{id}/vulnerabilities [get]
|
||||
func listServerVulnerabilities(c *gin.Context) {
|
||||
findings, err := services.ListFindings(c.Request.Context(), auth.InstanceID(c), c.Param("id"))
|
||||
if err != nil {
|
||||
@@ -227,6 +301,18 @@ func listServerVulnerabilities(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, findings)
|
||||
}
|
||||
|
||||
// getServerPackages godoc
|
||||
//
|
||||
// @Summary Get a server's package inventory
|
||||
// @Description A server that has not reported yet answers reported=false rather than 404 — that is the normal state for the first hour after install.
|
||||
// @Tags vulnerabilities
|
||||
// @Produce json
|
||||
// @Param id path string true "Server ID"
|
||||
// @Success 200 {object} models.ServerPackages
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /servers/{id}/packages [get]
|
||||
func getServerPackages(c *gin.Context) {
|
||||
sp, err := services.ListPackages(auth.InstanceID(c), c.Param("id"))
|
||||
if err != nil {
|
||||
@@ -237,12 +323,24 @@ func getServerPackages(c *gin.Context) {
|
||||
// Not a 404: an agent that has not reported yet is the normal state for
|
||||
// the first hour after install, and is a different thing from a bad
|
||||
// server id.
|
||||
c.JSON(http.StatusOK, gin.H{"reported": false})
|
||||
c.JSON(http.StatusOK, ReportedResponse{Reported: false})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, sp)
|
||||
}
|
||||
|
||||
// searchPackages godoc
|
||||
//
|
||||
// @Summary Search packages fleet-wide
|
||||
// @Tags vulnerabilities
|
||||
// @Produce json
|
||||
// @Param name query string true "Package name"
|
||||
// @Success 200 {array} services.PackageHit
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /packages/search [get]
|
||||
func searchPackages(c *gin.Context) {
|
||||
name := c.Query("name")
|
||||
if name == "" {
|
||||
@@ -257,6 +355,16 @@ func searchPackages(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, hits)
|
||||
}
|
||||
|
||||
// listVulnRules godoc
|
||||
//
|
||||
// @Summary List vulnerability alert rules
|
||||
// @Tags vulnerabilities
|
||||
// @Produce json
|
||||
// @Success 200 {array} models.VulnAlertRule
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /vuln-rules [get]
|
||||
func listVulnRules(c *gin.Context) {
|
||||
rules, err := services.ListVulnRules(auth.InstanceID(c))
|
||||
if err != nil {
|
||||
@@ -266,6 +374,18 @@ func listVulnRules(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, rules)
|
||||
}
|
||||
|
||||
// createVulnRule godoc
|
||||
//
|
||||
// @Summary Create a vulnerability alert rule
|
||||
// @Tags vulnerabilities
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body models.VulnAlertRule true "Rule to create"
|
||||
// @Success 201 {object} models.VulnAlertRule
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /vuln-rules [post]
|
||||
func createVulnRule(c *gin.Context) {
|
||||
var r models.VulnAlertRule
|
||||
if err := c.ShouldBindJSON(&r); err != nil {
|
||||
@@ -284,6 +404,20 @@ func createVulnRule(c *gin.Context) {
|
||||
c.JSON(http.StatusCreated, created)
|
||||
}
|
||||
|
||||
// updateVulnRule godoc
|
||||
//
|
||||
// @Summary Update a vulnerability alert rule
|
||||
// @Tags vulnerabilities
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Rule ID"
|
||||
// @Param body body models.VulnAlertRule true "Rule fields"
|
||||
// @Success 200 {object} StatusResponse
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /vuln-rules/{id} [put]
|
||||
func updateVulnRule(c *gin.Context) {
|
||||
var r models.VulnAlertRule
|
||||
if err := c.ShouldBindJSON(&r); err != nil {
|
||||
@@ -302,9 +436,21 @@ func updateVulnRule(c *gin.Context) {
|
||||
}
|
||||
|
||||
services.LogEvent(instanceID, "vuln.rule_updated", actorFromCtx(c), "", "", "rule "+r.Name)
|
||||
c.JSON(http.StatusOK, gin.H{"status": "updated"})
|
||||
c.JSON(http.StatusOK, StatusResponse{Status: "updated"})
|
||||
}
|
||||
|
||||
// deleteVulnRule godoc
|
||||
//
|
||||
// @Summary Delete a vulnerability alert rule
|
||||
// @Tags vulnerabilities
|
||||
// @Produce json
|
||||
// @Param id path string true "Rule ID"
|
||||
// @Success 200 {object} StatusResponse
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /vuln-rules/{id} [delete]
|
||||
func deleteVulnRule(c *gin.Context) {
|
||||
instanceID := auth.InstanceID(c)
|
||||
if err := services.DeleteVulnRule(instanceID, c.Param("id")); err != nil {
|
||||
@@ -317,5 +463,5 @@ func deleteVulnRule(c *gin.Context) {
|
||||
}
|
||||
|
||||
services.LogEvent(instanceID, "vuln.rule_deleted", actorFromCtx(c), "", "", "rule "+c.Param("id"))
|
||||
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
|
||||
c.JSON(http.StatusOK, StatusResponse{Status: "deleted"})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user