mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
#20934 This is tied to https://github.com/fleetdm/vulnerabilities/pull/14; for supported OS versions (currently Amazon Linux 1/2/2022/2023) we'll pull XZ'd sqlite files from the vulnerabilities repo and query them to determine what's vulnerable. See the associated issue for how I self-QA'd this. This replaced OVAL parsing for Amazon Linux 2, as we were using the wrong data source there (Amazon has backported a bunch of fixes to their own-named releases, so any RHEL fixes don't match). Some checklist items are missing here; getting this set up in draft to get code feedback now, and I'll push updates with e.g. docs changes, as well ass an addition to the changes file. # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [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/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) - [x] Added/updated tests - [x] Add tests to oval_platform - [x] Add sync_test - [x] Add database_test - [x] Manual QA for all new/changed functionality - [x] Update vulnerability management docs
139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
package goval_dictionary
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/vulnerabilities/oval"
|
|
"github.com/fleetdm/fleet/v4/server/vulnerabilities/utils"
|
|
kitlog "github.com/go-kit/log"
|
|
)
|
|
|
|
const (
|
|
hostsBatchSize = 500
|
|
vulnBatchSize = 500
|
|
)
|
|
|
|
var ErrUnsupportedPlatform = errors.New("unsupported platform")
|
|
|
|
// Analyze scans all hosts for vulnerabilities based on the sqlite output of goval-dictionary
|
|
// for their platform, inserting any new vulnerabilities and deleting anything patched.
|
|
// Returns nil, nil when the platform isn't supported.
|
|
func Analyze(
|
|
ctx context.Context,
|
|
ds fleet.Datastore,
|
|
ver fleet.OSVersion,
|
|
vulnPath string,
|
|
collectVulns bool,
|
|
logger kitlog.Logger,
|
|
) ([]fleet.SoftwareVulnerability, error) {
|
|
platform := oval.NewPlatform(ver.Platform, ver.Name)
|
|
source := fleet.GovalDictionarySource
|
|
if !platform.IsGovalDictionarySupported() {
|
|
return nil, ErrUnsupportedPlatform
|
|
}
|
|
db, err := loadDb(platform, vulnPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Since hosts and software have a M:N relationship, the following sets are used to
|
|
// avoid doing duplicated inserts/delete operations (a vulnerable software might be
|
|
// present in many hosts).
|
|
toInsertSet := make(map[string]fleet.SoftwareVulnerability)
|
|
toDeleteSet := make(map[string]fleet.SoftwareVulnerability)
|
|
|
|
var offset int
|
|
for {
|
|
hostIDs, err := ds.HostIDsByOSVersion(ctx, ver, offset, hostsBatchSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(hostIDs) == 0 {
|
|
break
|
|
}
|
|
offset += hostsBatchSize
|
|
|
|
foundInBatch := make(map[uint][]fleet.SoftwareVulnerability)
|
|
for _, hostID := range hostIDs {
|
|
hostID := hostID
|
|
software, err := ds.ListSoftwareForVulnDetection(ctx, fleet.VulnSoftwareFilter{HostID: &hostID})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vulnerabilities := db.Eval(software, logger)
|
|
foundInBatch[hostID] = vulnerabilities
|
|
}
|
|
|
|
existingInBatch, err := ds.ListSoftwareVulnerabilitiesByHostIDsSource(ctx, hostIDs, source)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, hostID := range hostIDs {
|
|
inserts, deletes := utils.VulnsDelta(foundInBatch[hostID], existingInBatch[hostID])
|
|
for _, i := range inserts {
|
|
toInsertSet[i.Key()] = i
|
|
}
|
|
for _, d := range deletes {
|
|
toDeleteSet[d.Key()] = d
|
|
}
|
|
}
|
|
}
|
|
|
|
err = utils.BatchProcess(toDeleteSet, func(v []fleet.SoftwareVulnerability) error {
|
|
return ds.DeleteSoftwareVulnerabilities(ctx, v)
|
|
}, vulnBatchSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var inserted []fleet.SoftwareVulnerability
|
|
if collectVulns {
|
|
inserted = make([]fleet.SoftwareVulnerability, 0, len(toInsertSet))
|
|
}
|
|
|
|
err = utils.BatchProcess(toInsertSet, func(vulns []fleet.SoftwareVulnerability) error {
|
|
for _, v := range vulns {
|
|
ok, err := ds.InsertSoftwareVulnerability(ctx, v, source)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if collectVulns && ok {
|
|
inserted = append(inserted, v)
|
|
}
|
|
}
|
|
return nil
|
|
}, vulnBatchSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return inserted, nil
|
|
}
|
|
|
|
// loadDb returns the latest goval_dictionary database for the given platform.
|
|
func loadDb(platform oval.Platform, vulnPath string) (*Database, error) {
|
|
if !platform.IsGovalDictionarySupported() {
|
|
return nil, fmt.Errorf("platform %q not supported", platform)
|
|
}
|
|
|
|
fileName := platform.ToGovalDictionaryFilename()
|
|
latest, err := utils.LatestFile(fileName, vulnPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sqlite, err := sql.Open("sqlite3", latest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
db := NewDB(sqlite, platform)
|
|
return db, nil
|
|
}
|