fleet/server/datastore/mysql/operating_system_vulnerabilities_test.go
Victor Lyuboslavsky 23f9065522
Profiles batch activity (#21604)
#20757
API endpoint `/api/v1/fleet/mdm/profiles/batch` will now not log an
activity for profile types that did not change in the database (Apple
configuration profiles, Windows configuration profiles, or Apple
declarations).

Demo video: https://www.loom.com/share/8b75cbd8e7394c12ac6b56746b72c244

# Checklist for submitter
- [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] Added/updated tests
- [x] If database migrations are included, checked table schema to
confirm autoupdate
- [x] Manual QA for all new/changed functionality
2024-08-30 16:00:35 -05:00

327 lines
10 KiB
Go

package mysql
import (
"context"
"testing"
"time"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestOperatingSystemVulnerabilities(t *testing.T) {
ds := CreateMySQLDS(t)
cases := []struct {
name string
fn func(t *testing.T, ds *Datastore)
}{
{"ListOSVulnerabilitiesEmpty", testListOSVulnerabilitiesByOSEmpty},
{"ListOSVulnerabilities", testListOSVulnerabilitiesByOS},
{"ListVulnssByOsNameAndVersion", testListVulnsByOsNameAndVersion},
{"InsertOSVulnerabilities", testInsertOSVulnerabilities},
{"InsertSingleOSVulnerability", testInsertOSVulnerability},
{"DeleteOSVulnerabilitiesEmpty", testDeleteOSVulnerabilitiesEmpty},
{"DeleteOSVulnerabilities", testDeleteOSVulnerabilities},
{"DeleteOutOfDateOSVulnerabilities", testDeleteOutOfDateOSVulnerabilities},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer TruncateTables(t, ds)
c.fn(t, ds)
})
}
}
func testListOSVulnerabilitiesByOSEmpty(t *testing.T, ds *Datastore) {
ctx := context.Background()
actual, err := ds.ListOSVulnerabilitiesByOS(ctx, 1)
require.NoError(t, err)
require.Empty(t, actual)
}
func testListOSVulnerabilitiesByOS(t *testing.T, ds *Datastore) {
ctx := context.Background()
vulns := []fleet.OSVulnerability{
{CVE: "cve-1", OSID: 1, ResolvedInVersion: ptr.String("1.2.3")},
{CVE: "cve-3", OSID: 1, ResolvedInVersion: ptr.String("10.14.2")},
{CVE: "cve-2", OSID: 1, ResolvedInVersion: ptr.String("8.123.1")},
{CVE: "cve-1", OSID: 2, ResolvedInVersion: ptr.String("1.2.3")},
{CVE: "cve-5", OSID: 2, ResolvedInVersion: ptr.String("10.14.2")},
}
for _, v := range vulns {
_, err := ds.InsertOSVulnerability(ctx, v, fleet.MSRCSource)
require.NoError(t, err)
}
t.Run("returns matching", func(t *testing.T) {
expected := []fleet.OSVulnerability{
{CVE: "cve-1", OSID: 1, ResolvedInVersion: ptr.String("1.2.3"), Source: fleet.MSRCSource},
{CVE: "cve-3", OSID: 1, ResolvedInVersion: ptr.String("10.14.2"), Source: fleet.MSRCSource},
{CVE: "cve-2", OSID: 1, ResolvedInVersion: ptr.String("8.123.1"), Source: fleet.MSRCSource},
}
actual, err := ds.ListOSVulnerabilitiesByOS(ctx, 1)
require.NoError(t, err)
require.ElementsMatch(t, expected, actual)
})
}
func testListVulnsByOsNameAndVersion(t *testing.T, ds *Datastore) {
ctx := context.Background()
seedOS := []fleet.OperatingSystem{
{
Name: "Microsoft Windows 11 Pro 21H2",
Version: "10.0.22000.795",
Arch: "64-bit",
KernelVersion: "10.0.22000.795",
Platform: "windows",
DisplayVersion: "21H2",
},
{
Name: "Microsoft Windows 11 Pro 21H2",
Version: "10.0.22000.795",
Arch: "ARM 64-bit",
KernelVersion: "10.0.22000.795",
Platform: "windows",
DisplayVersion: "21H2",
},
{
Name: "Microsoft Windows 11 Pro 22H2",
Version: "10.0.22621.890",
Arch: "64-bit",
KernelVersion: "10.0.22621.890",
Platform: "windows",
DisplayVersion: "22H2",
},
}
dbOS := []fleet.OperatingSystem{}
for _, seed := range seedOS {
os, err := newOperatingSystemDB(context.Background(), ds.writer(context.Background()), seed)
require.NoError(t, err)
dbOS = append(dbOS, *os)
}
cves, err := ds.ListVulnsByOsNameAndVersion(ctx, "Microsoft Windows 11 Pro 21H2", "10.0.22000.795", false)
require.NoError(t, err)
require.Empty(t, cves)
mockTime := time.Date(2024, time.January, 18, 10, 0, 0, 0, time.UTC)
cveMeta := []fleet.CVEMeta{
{
CVE: "CVE-2021-1234",
CVSSScore: ptr.Float64(9.7),
EPSSProbability: ptr.Float64(4.2),
CISAKnownExploit: ptr.Bool(true),
Published: ptr.Time(mockTime),
Description: "A bad vulnerability",
},
{
CVE: "CVE-2021-1235",
CVSSScore: ptr.Float64(9.8),
EPSSProbability: ptr.Float64(0.1),
CISAKnownExploit: ptr.Bool(false),
Published: ptr.Time(mockTime),
Description: "A worse vulnerability",
},
{
CVE: "CVE-2021-1236",
CVSSScore: ptr.Float64(9.8),
EPSSProbability: ptr.Float64(0.1),
CISAKnownExploit: ptr.Bool(false),
Published: ptr.Time(mockTime),
Description: "A terrible vulnerability",
},
}
err = ds.InsertCVEMeta(ctx, cveMeta)
require.NoError(t, err)
// add CVEs for each OS with different architectures
vulns := []fleet.OSVulnerability{
{CVE: "CVE-2021-1234", OSID: dbOS[0].ID, ResolvedInVersion: ptr.String("1.2.3")},
{CVE: "CVE-2021-1234", OSID: dbOS[1].ID, ResolvedInVersion: ptr.String("1.2.3")}, // same OS, different arch
{CVE: "CVE-2021-1235", OSID: dbOS[1].ID, ResolvedInVersion: ptr.String("10.14.2")},
{CVE: "CVE-2021-1236", OSID: dbOS[2].ID, ResolvedInVersion: ptr.String("103.2.1")},
}
_, err = ds.InsertOSVulnerabilities(ctx, vulns, fleet.MSRCSource)
require.NoError(t, err)
// test without CVS meta
cves, err = ds.ListVulnsByOsNameAndVersion(ctx, "Microsoft Windows 11 Pro 21H2", "10.0.22000.795", false)
require.NoError(t, err)
expected := []string{"CVE-2021-1234", "CVE-2021-1235"}
require.Len(t, cves, 2)
for _, cve := range cves {
require.Contains(t, expected, cve.CVE)
require.Greater(t, cve.CreatedAt, time.Now().Add(-time.Hour)) // assert non-zero time
}
// test with CVS meta
cves, err = ds.ListVulnsByOsNameAndVersion(ctx, "Microsoft Windows 11 Pro 21H2", "10.0.22000.795", true)
require.NoError(t, err)
require.Len(t, cves, 2)
require.Equal(t, cveMeta[0].CVE, cves[0].CVE)
require.Equal(t, &cveMeta[0].CVSSScore, cves[0].CVSSScore)
require.Equal(t, &cveMeta[0].EPSSProbability, cves[0].EPSSProbability)
require.Equal(t, &cveMeta[0].CISAKnownExploit, cves[0].CISAKnownExploit)
require.Equal(t, cveMeta[0].Published, *cves[0].CVEPublished)
require.Equal(t, cveMeta[0].Description, **cves[0].Description)
require.Equal(t, cveMeta[1].CVE, cves[1].CVE)
require.Equal(t, &cveMeta[1].CVSSScore, cves[1].CVSSScore)
require.Equal(t, &cveMeta[1].EPSSProbability, cves[1].EPSSProbability)
require.Equal(t, &cveMeta[1].CISAKnownExploit, cves[1].CISAKnownExploit)
require.Equal(t, cveMeta[1].Published, *cves[1].CVEPublished)
require.Equal(t, cveMeta[1].Description, **cves[1].Description)
}
func testInsertOSVulnerabilities(t *testing.T, ds *Datastore) {
ctx := context.Background()
vulns := []fleet.OSVulnerability{
{CVE: "cve-1", OSID: 1},
{CVE: "cve-3", OSID: 1},
{CVE: "cve-2", OSID: 1},
}
c, err := ds.InsertOSVulnerabilities(ctx, vulns, fleet.MSRCSource)
require.NoError(t, err)
require.Equal(t, int64(3), c)
expected := []fleet.OSVulnerability{
{CVE: "cve-1", OSID: 1, Source: fleet.MSRCSource},
{CVE: "cve-3", OSID: 1, Source: fleet.MSRCSource},
{CVE: "cve-2", OSID: 1, Source: fleet.MSRCSource},
}
actual, err := ds.ListOSVulnerabilitiesByOS(ctx, 1)
require.NoError(t, err)
require.ElementsMatch(t, expected, actual)
}
func testInsertOSVulnerability(t *testing.T, ds *Datastore) {
ctx := context.Background()
vulns := fleet.OSVulnerability{
CVE: "cve-1", OSID: 1, ResolvedInVersion: ptr.String("1.2.3"),
}
vulnsUpdate := fleet.OSVulnerability{
CVE: "cve-1", OSID: 1, ResolvedInVersion: ptr.String("1.2.4"),
}
vulnNoCVE := fleet.OSVulnerability{
OSID: 1, ResolvedInVersion: ptr.String("1.2.4"),
}
// Inserting a vulnerability with no CVE should not insert anything
didInsert, err := ds.InsertOSVulnerability(ctx, vulnNoCVE, fleet.MSRCSource)
require.Error(t, err)
require.False(t, didInsert)
// Inserting a vulnerability with a CVE should insert
didInsert, err = ds.InsertOSVulnerability(ctx, vulns, fleet.MSRCSource)
require.NoError(t, err)
require.True(t, didInsert)
// Inserting the same vulnerability should not insert, but update
didInsertOrUpdate, err := ds.InsertOSVulnerability(ctx, vulnsUpdate, fleet.MSRCSource)
require.NoError(t, err)
assert.True(t, didInsertOrUpdate)
// Inserting the exact same vulnerability again should not insert and not update
didInsertOrUpdate, err = ds.InsertOSVulnerability(ctx, vulnsUpdate, fleet.MSRCSource)
require.NoError(t, err)
assert.False(t, didInsertOrUpdate)
expected := vulnsUpdate
expected.Source = fleet.MSRCSource
list1, err := ds.ListOSVulnerabilitiesByOS(ctx, 1)
require.NoError(t, err)
require.Len(t, list1, 1)
require.Equal(t, expected, list1[0])
}
func testDeleteOSVulnerabilitiesEmpty(t *testing.T, ds *Datastore) {
ctx := context.Background()
vulns := []fleet.OSVulnerability{
{CVE: "cve-1", OSID: 1},
{CVE: "cve-1", OSID: 1},
{CVE: "cve-3", OSID: 1},
{CVE: "cve-2", OSID: 1},
}
err := ds.DeleteOSVulnerabilities(ctx, vulns)
require.NoError(t, err)
}
func testDeleteOSVulnerabilities(t *testing.T, ds *Datastore) {
ctx := context.Background()
vulns := []fleet.OSVulnerability{
{CVE: "cve-1", OSID: 1},
{CVE: "cve-2", OSID: 1},
{CVE: "cve-3", OSID: 1},
}
c, err := ds.InsertOSVulnerabilities(ctx, vulns, fleet.MSRCSource)
require.NoError(t, err)
require.Equal(t, int64(3), c)
toDelete := []fleet.OSVulnerability{
{CVE: "cve-2", OSID: 1},
}
err = ds.DeleteOSVulnerabilities(ctx, toDelete)
require.NoError(t, err)
actual, err := ds.ListOSVulnerabilitiesByOS(ctx, 1)
require.NoError(t, err)
require.ElementsMatch(t, []fleet.OSVulnerability{
{CVE: "cve-1", OSID: 1, Source: fleet.MSRCSource},
{CVE: "cve-3", OSID: 1, Source: fleet.MSRCSource},
}, actual)
}
func testDeleteOutOfDateOSVulnerabilities(t *testing.T, ds *Datastore) {
ctx := context.Background()
yesterday := time.Now().Add(-3 * time.Hour).Format("2006-01-02 15:04:05")
oldVuln := fleet.OSVulnerability{
CVE: "cve-1", OSID: 1,
}
newVuln := fleet.OSVulnerability{
CVE: "cve-2", OSID: 1,
}
_, err := ds.InsertOSVulnerability(ctx, oldVuln, fleet.NVDSource)
require.NoError(t, err)
_, err = ds.writer(ctx).ExecContext(ctx, "UPDATE operating_system_vulnerabilities SET updated_at = ?", yesterday)
require.NoError(t, err)
_, err = ds.InsertOSVulnerability(ctx, newVuln, fleet.NVDSource)
require.NoError(t, err)
// Delete out of date vulns
err = ds.DeleteOutOfDateOSVulnerabilities(ctx, fleet.NVDSource, 2*time.Hour)
require.NoError(t, err)
actual, err := ds.ListOSVulnerabilitiesByOS(ctx, 1)
require.NoError(t, err)
require.Len(t, actual, 1)
require.ElementsMatch(t, []fleet.OSVulnerability{newVuln}, actual)
}