fleet/server/chart/datasets.go
Scott Gress 4334017b38
Add Vulnerabilities exposure dataset (#44124)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** For #43769

# Details

Adds methods to collect data for the `cve` dataset. As with all sets
this is collected at hourly granularity, but unlike the `uptime` set,
the `cve` set uses the "snapshot" strategy so that we record at most one
change (the most recent) per hour.

For this first iteration, we are _recording_ data for all CVEs (i.e.,
which hosts were exposed to which CVEs at a given time), but we are only
_reporting_ a subset of CVEs for the dashboard chart. See [this
comment](https://github.com/fleetdm/fleet/pull/44124#discussion_r3155554405)
for more info.

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [X] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.

## Testing

- [X] Added/updated automated tests
- [X] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [X] QA'd all new/changed functionality manually
- [X] Spot-checked the CVEs chosen by the `trackedCVESoftwareMatchers`
and didn't find any outside of the expected
- [X] With [front-end PR](https://github.com/fleetdm/fleet/pull/44261),
generated chart:
<img width="706" height="421" alt="image"
src="https://github.com/user-attachments/assets/539d9877-6573-4406-a159-1d2a711a045f"
/>



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Host vulnerability (CVE) chart added to the dashboard; CVE chart data
collection is now active.
  * Critical CVE tracking surfaces high-severity vulnerabilities.

* **Improvements**
* CVE chart refreshes every 3 hours (was daily) for more timely
insights.
* Snapshot collection reconciles and closes prior data during empty runs
to keep charts accurate.
* CVE queries may produce zero datapoints when no tracked CVEs exist,
without affecting other metrics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-29 09:30:31 -05:00

56 lines
2.1 KiB
Go

package chart
import (
"context"
"time"
"github.com/fleetdm/fleet/v4/server/chart/api"
)
// uptimeRecentlySeenWindow must match the cron schedule cadence so each sample
// reflects activity since the last run.
const uptimeRecentlySeenWindow = 10 * time.Minute
// UptimeDataset implements api.Dataset for host uptime tracking.
type UptimeDataset struct{}
func (u *UptimeDataset) Name() string { return "uptime" }
func (u *UptimeDataset) DefaultResolutionHours() int { return 3 }
func (u *UptimeDataset) SampleStrategy() api.SampleStrategy { return api.SampleStrategyAccumulate }
func (u *UptimeDataset) DefaultVisualization() string { return "checkerboard" }
func (u *UptimeDataset) Collect(ctx context.Context, store api.DatasetStore, now time.Time) error {
hostIDs, err := store.FindRecentlySeenHostIDs(ctx, now.Add(-uptimeRecentlySeenWindow))
if err != nil {
return err
}
if len(hostIDs) == 0 {
return nil
}
bucketStart := now.UTC().Truncate(time.Hour)
return store.RecordBucketData(ctx, u.Name(), bucketStart, time.Hour, u.SampleStrategy(),
// The empty string key means "all entities" since uptime isn't tracked per host.
// The value is a bitmap of host IDs that were active in this bucket.
map[string][]byte{"": HostIDsToBlob(hostIDs)})
}
// CVEDataset implements api.Dataset for host CVE tracking.
type CVEDataset struct{}
func (c *CVEDataset) Name() string { return "cve" }
func (c *CVEDataset) DefaultResolutionHours() int { return 3 }
func (c *CVEDataset) SampleStrategy() api.SampleStrategy { return api.SampleStrategySnapshot }
func (c *CVEDataset) DefaultVisualization() string { return "line" }
func (c *CVEDataset) Collect(ctx context.Context, store api.DatasetStore, now time.Time) error {
hostIDsByCVE, err := store.AffectedHostIDsByCVE(ctx)
if err != nil {
return err
}
bitmaps := make(map[string][]byte, len(hostIDsByCVE))
for cve, hostIDs := range hostIDsByCVE {
bitmaps[cve] = HostIDsToBlob(hostIDs)
}
bucketStart := now.UTC().Truncate(time.Hour)
return store.RecordBucketData(ctx, c.Name(), bucketStart, time.Hour, c.SampleStrategy(), bitmaps)
}