Completes tasks 10-15 and fixes what was outstanding: - vulndb.Pull implemented with oras-go, streaming the ~50MB layer and staging both files before replacing either, so a failed pull leaves the previous database intact rather than a half-written one. - db.go: Vulnerability.Severity is a string, not trivy Severity, so the int conversion did not compile. Severity now resolves vendor (highest when vendors disagree) then NVD then unknown, and CVSS is read too. - findings.go: added sweepFixedFindings plus the fleet query, severity counts, rescan flag and accept/unaccept the API needs. - vulnrules.go: added rule CRUD and the digest builder. ResolveTargets returns []models.Server, not []string, so filterByServers was wrong. - api/vulnerabilities.go was an empty file while handlers.go registered twelve routes against it; written, grouped by CVE. - shared/mail: added the missing sender. The templates were orphaned and the HTML one was a copy of the text one, defining "subject" (which html/template would escape) and emitting no markup. render.go parses every template in init(), so a bad one panics server, admin and sitesvc at boot — go build never runs init(), which is why nothing complained. - notify: digests dispatch through their own path so SMTP gets the digest template rather than arriving dressed as a monitor alert.
42 lines
1.4 KiB
Go
42 lines
1.4 KiB
Go
package mail
|
|
|
|
// VulnDigestRow is one newly opened finding as the digest shows it.
|
|
//
|
|
// It lives here rather than in server/ so the templates and the caller agree on
|
|
// the fields without server's model package leaking into shared.
|
|
type VulnDigestRow struct {
|
|
CVEID string
|
|
Severity string
|
|
PackageName string
|
|
ServerName string
|
|
// FixedIn empty means no vendor fix has been published, which the template
|
|
// says explicitly rather than leaving blank — it is a real state, not
|
|
// missing data.
|
|
FixedIn string
|
|
}
|
|
|
|
// VulnDigest is one batch of newly opened findings.
|
|
//
|
|
// One message per rule per scan, never one per finding: a database refresh can
|
|
// open several hundred at once, and one message each would rate-limit the
|
|
// webhook or get the channel muted.
|
|
type VulnDigest struct {
|
|
InstanceName string
|
|
// Count is every newly opened finding in the batch, which may exceed
|
|
// len(Rows) — Rows is capped and More carries the remainder.
|
|
Count int
|
|
TopSeverity string
|
|
Summary string
|
|
Rows []VulnDigestRow
|
|
More int
|
|
// DBAge is pre-formatted by the caller. A digest scanned against a
|
|
// three-week-old database must say so rather than quietly imply freshness.
|
|
DBAge string
|
|
}
|
|
|
|
// SendVulnDigest delivers one digest to an SMTP notification channel's
|
|
// recipients, which may be a comma-separated list.
|
|
func (s Sender) SendVulnDigest(to string, d VulnDigest) error {
|
|
return s.sendTemplate(to, "", "vuln_digest", d)
|
|
}
|