fleet/server/vulnerabilities/utils/utils.go
Michal Nicpon 9ad1721efd
fix issue with duplicate vulns detected using nvd (#8613)
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.
2022-11-10 10:28:00 -07:00

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)
}
}