mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
The OVAL analyzer falsely assumes that any vulnerabilities detected on a host only come from OVAL. However, it is possible that NVD detects vulnerabilities on these hosts even though it excludes software from deb_packages and rpm_packages. For example, a python package twisted v22.20 has a vulnerability CVE-2022-39348 detected by NVD. The OVAL analyzer would delete this vulnerability, and it would be re-inserted by the NVD scanner on the next run. This creates a loop. The fix is to only delete vulnerabilities that are actually detected using OVAL. We already store this in the source column in the software_cve table.
164 lines
3.4 KiB
Go
164 lines
3.4 KiB
Go
package utils
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
)
|
|
|
|
// RecentVulns filters vulnerabilities based on whether the vulnerability cve is contained in 'meta'.
|
|
// Returns the filtered vulnerabilities and their meta data.
|
|
func RecentVulns[T fleet.Vulnerability](
|
|
vulns []T,
|
|
meta []fleet.CVEMeta,
|
|
) ([]T, map[string]fleet.CVEMeta) {
|
|
if len(vulns) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
recent := make(map[string]fleet.CVEMeta)
|
|
for _, r := range meta {
|
|
recent[r.CVE] = r
|
|
}
|
|
|
|
seen := make(map[string]bool)
|
|
var r []T
|
|
|
|
for _, v := range vulns {
|
|
if _, ok := recent[v.GetCVE()]; ok && !seen[v.Key()] {
|
|
seen[v.Key()] = true
|
|
r = append(r, v)
|
|
}
|
|
}
|
|
|
|
return r, recent
|
|
}
|
|
|
|
func BatchProcess[T fleet.Vulnerability](
|
|
values map[string]T,
|
|
dsFunc func(v []T) error,
|
|
batchSize int,
|
|
) error {
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
|
|
bSize := batchSize
|
|
if bSize > len(values) {
|
|
bSize = len(values)
|
|
}
|
|
|
|
buffer := make([]T, bSize)
|
|
var offset, i int
|
|
for _, v := range values {
|
|
buffer[offset] = v
|
|
offset++
|
|
i++
|
|
|
|
// Consume buffer if full or if we are at the last iteration
|
|
if offset == bSize || i >= len(values) {
|
|
err := dsFunc(buffer[:offset])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
offset = 0
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// VulnsDelta compares what vulnerabilities already exists with what new vulnerabilities were found
|
|
// and returns what to insert and what to delete.
|
|
func VulnsDelta[T fleet.Vulnerability](
|
|
found []T,
|
|
existing []T,
|
|
) (toInsert []T, toDelete []T) {
|
|
toDelete = make([]T, 0)
|
|
toInsert = make([]T, 0)
|
|
|
|
existingSet := make(map[string]bool)
|
|
for _, e := range existing {
|
|
existingSet[e.Key()] = true
|
|
}
|
|
|
|
foundSet := make(map[string]bool)
|
|
for _, f := range found {
|
|
foundSet[f.Key()] = true
|
|
}
|
|
|
|
for _, e := range existing {
|
|
if _, ok := foundSet[e.Key()]; !ok {
|
|
// existing not in found, delete
|
|
toDelete = append(toDelete, e)
|
|
}
|
|
}
|
|
|
|
for _, f := range found {
|
|
if _, ok := existingSet[f.Key()]; !ok {
|
|
// found not in existing, insert
|
|
toInsert = append(toInsert, f)
|
|
}
|
|
}
|
|
|
|
return toInsert, toDelete
|
|
}
|
|
|
|
// ProductIDsIntersect given two sets of product IDs returns whether they have any elements in common
|
|
func ProductIDsIntersect(a map[string]bool, b map[string]bool) bool {
|
|
smallest := a
|
|
biggest := b
|
|
|
|
if len(a) > len(b) {
|
|
smallest = b
|
|
biggest = a
|
|
}
|
|
|
|
for pID := range smallest {
|
|
if biggest[pID] {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// LatestFile returns the path of 'fileName' in 'dir' if the file exists, otherwise it will
|
|
// return the most recent file (based on the timestamp contained in 'fileName').
|
|
func LatestFile(fileName string, dir string) (string, error) {
|
|
target := filepath.Join(dir, fileName)
|
|
ext := filepath.Ext(target)
|
|
|
|
switch _, err := os.Stat(target); {
|
|
case err == nil:
|
|
return target, nil
|
|
case errors.Is(err, fs.ErrNotExist):
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
prefix := strings.Split(fileName, "-")[0]
|
|
var latest os.FileInfo
|
|
for _, f := range files {
|
|
if strings.HasPrefix(f.Name(), prefix) && strings.HasSuffix(f.Name(), ext) {
|
|
info, err := f.Info()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if latest == nil || info.ModTime().After(latest.ModTime()) {
|
|
latest = info
|
|
}
|
|
}
|
|
}
|
|
if latest == nil {
|
|
return "", fmt.Errorf("file not found '%s' in '%s'", fileName, dir)
|
|
}
|
|
return filepath.Join(dir, latest.Name()), nil
|
|
default:
|
|
return "", fmt.Errorf("failed to stat %q: %w", target, err)
|
|
}
|
|
}
|