2022-10-28 15:12:21 +00:00
package mysql
import (
"context"
2025-08-14 14:13:37 +00:00
"sort"
2022-10-28 15:12:21 +00:00
"testing"
2024-01-24 19:18:57 +00:00
"time"
2022-10-28 15:12:21 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
2024-01-24 19:18:57 +00:00
"github.com/fleetdm/fleet/v4/server/ptr"
2025-08-14 14:13:37 +00:00
"github.com/fleetdm/fleet/v4/server/test"
2025-07-10 20:42:26 +00:00
"github.com/jmoiron/sqlx"
2024-08-30 21:00:35 +00:00
"github.com/stretchr/testify/assert"
2022-10-28 15:12:21 +00:00
"github.com/stretchr/testify/require"
)
func TestOperatingSystemVulnerabilities ( t * testing . T ) {
ds := CreateMySQLDS ( t )
cases := [ ] struct {
name string
fn func ( t * testing . T , ds * Datastore )
} {
2024-01-24 19:18:57 +00:00
{ "ListOSVulnerabilitiesEmpty" , testListOSVulnerabilitiesByOSEmpty } ,
{ "ListOSVulnerabilities" , testListOSVulnerabilitiesByOS } ,
2025-07-15 19:03:25 +00:00
{ "ListVulnsByOsNameAndVersion" , testListVulnsByOsNameAndVersion } ,
2022-10-28 15:12:21 +00:00
{ "InsertOSVulnerabilities" , testInsertOSVulnerabilities } ,
2024-01-24 19:18:57 +00:00
{ "InsertSingleOSVulnerability" , testInsertOSVulnerability } ,
2022-10-28 15:12:21 +00:00
{ "DeleteOSVulnerabilitiesEmpty" , testDeleteOSVulnerabilitiesEmpty } ,
{ "DeleteOSVulnerabilities" , testDeleteOSVulnerabilities } ,
2024-01-24 19:18:57 +00:00
{ "DeleteOutOfDateOSVulnerabilities" , testDeleteOutOfDateOSVulnerabilities } ,
2025-08-14 14:13:37 +00:00
{ "TestListKernelsByOS" , testListKernelsByOS } ,
{ "TestKernelVulnsHostCount" , testKernelVulnsHostCount } ,
2022-10-28 15:12:21 +00:00
}
for _ , c := range cases {
t . Run ( c . name , func ( t * testing . T ) {
defer TruncateTables ( t , ds )
c . fn ( t , ds )
} )
}
}
2024-01-24 19:18:57 +00:00
func testListOSVulnerabilitiesByOSEmpty ( t * testing . T , ds * Datastore ) {
2022-10-28 15:12:21 +00:00
ctx := context . Background ( )
2024-01-24 19:18:57 +00:00
actual , err := ds . ListOSVulnerabilitiesByOS ( ctx , 1 )
2022-10-28 15:12:21 +00:00
require . NoError ( t , err )
require . Empty ( t , actual )
}
2024-01-24 19:18:57 +00:00
func testListOSVulnerabilitiesByOS ( t * testing . T , ds * Datastore ) {
2022-10-28 15:12:21 +00:00
ctx := context . Background ( )
vulns := [ ] fleet . OSVulnerability {
2024-01-24 19:18:57 +00:00
{ 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" ) } ,
2022-10-28 15:12:21 +00:00
}
for _ , v := range vulns {
2024-01-24 19:18:57 +00:00
_ , err := ds . InsertOSVulnerability ( ctx , v , fleet . MSRCSource )
2022-10-28 15:12:21 +00:00
require . NoError ( t , err )
}
t . Run ( "returns matching" , func ( t * testing . T ) {
expected := [ ] fleet . OSVulnerability {
2024-02-07 13:19:59 +00:00
{ 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 } ,
2022-10-28 15:12:21 +00:00
}
2024-01-24 19:18:57 +00:00
actual , err := ds . ListOSVulnerabilitiesByOS ( ctx , 1 )
2022-10-28 15:12:21 +00:00
require . NoError ( t , err )
require . ElementsMatch ( t , expected , actual )
} )
}
2024-01-24 19:18:57 +00:00
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" ,
} ,
}
2025-07-15 19:03:25 +00:00
var dbOS [ ] fleet . OperatingSystem
2024-01-24 19:18:57 +00:00
for _ , seed := range seedOS {
os , err := newOperatingSystemDB ( context . Background ( ) , ds . writer ( context . Background ( ) ) , seed )
require . NoError ( t , err )
dbOS = append ( dbOS , * os )
}
2025-08-14 14:13:37 +00:00
cves , err := ds . ListVulnsByOsNameAndVersion ( ctx , "Microsoft Windows 11 Pro 21H2" , "10.0.22000.795" , false , nil )
2024-01-24 19:18:57 +00:00
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-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 )
2025-07-15 19:03:25 +00:00
// push other vulns into the past to ensure "SELECT DISTINCT" wouldn't deduplicate properly
_ , err = ds . writer ( ctx ) . ExecContext ( ctx , "UPDATE operating_system_vulnerabilities SET created_at = NOW() - INTERVAL 5 SECOND" )
require . NoError ( t , err )
_ , err = ds . InsertOSVulnerabilities ( ctx , [ ] fleet . OSVulnerability {
{ CVE : "CVE-2021-1234" , OSID : dbOS [ 1 ] . ID , ResolvedInVersion : ptr . String ( "1.2.3" ) } , // same OS, different arch
} , fleet . MSRCSource )
require . NoError ( t , err )
2024-01-24 19:18:57 +00:00
// test without CVS meta
2025-08-14 14:13:37 +00:00
cves , err = ds . ListVulnsByOsNameAndVersion ( ctx , "Microsoft Windows 11 Pro 21H2" , "10.0.22000.795" , false , nil )
2024-01-24 19:18:57 +00:00
require . NoError ( t , err )
2024-07-09 17:09:16 +00:00
expected := [ ] string { "CVE-2021-1234" , "CVE-2021-1235" }
2024-01-24 19:18:57 +00:00
require . Len ( t , cves , 2 )
2024-07-09 17:09:16 +00:00
for _ , cve := range cves {
require . Contains ( t , expected , cve . CVE )
require . Greater ( t , cve . CreatedAt , time . Now ( ) . Add ( - time . Hour ) ) // assert non-zero time
}
2024-01-24 19:18:57 +00:00
// test with CVS meta
2025-08-14 14:13:37 +00:00
cves , err = ds . ListVulnsByOsNameAndVersion ( ctx , "Microsoft Windows 11 Pro 21H2" , "10.0.22000.795" , true , nil )
2024-01-24 19:18:57 +00:00
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 )
}
2022-10-28 15:12:21 +00:00
func testInsertOSVulnerabilities ( t * testing . T , ds * Datastore ) {
ctx := context . Background ( )
vulns := [ ] fleet . OSVulnerability {
2024-01-24 19:18:57 +00:00
{ CVE : "cve-1" , OSID : 1 } ,
{ CVE : "cve-3" , OSID : 1 } ,
{ CVE : "cve-2" , OSID : 1 } ,
2022-10-28 15:12:21 +00:00
}
c , err := ds . InsertOSVulnerabilities ( ctx , vulns , fleet . MSRCSource )
require . NoError ( t , err )
require . Equal ( t , int64 ( 3 ) , c )
2024-02-07 13:19:59 +00:00
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 } ,
}
2024-01-24 19:18:57 +00:00
actual , err := ds . ListOSVulnerabilitiesByOS ( ctx , 1 )
require . NoError ( t , err )
2024-02-07 13:19:59 +00:00
require . ElementsMatch ( t , expected , actual )
2024-01-24 19:18:57 +00:00
}
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" ) ,
2022-10-28 15:12:21 +00:00
}
2024-01-24 19:18:57 +00:00
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 )
2024-08-30 21:00:35 +00:00
// Inserting the same vulnerability should not insert, but update
2025-07-10 20:42:26 +00:00
didInsert , err = ds . InsertOSVulnerability ( ctx , vulnsUpdate , fleet . MSRCSource )
2022-10-28 15:12:21 +00:00
require . NoError ( t , err )
2025-07-10 20:42:26 +00:00
assert . False ( t , didInsert )
2024-08-30 21:00:35 +00:00
2025-07-10 20:42:26 +00:00
// Inserting the exact same vulnerability again may or may not change updated_at, but qualifies as an update
didInsert , err = ds . InsertOSVulnerability ( ctx , vulnsUpdate , fleet . MSRCSource )
require . NoError ( t , err )
assert . False ( t , didInsert )
// simulate vuln in the past to make sure updated_at gets set
_ , err = ds . writer ( ctx ) . ExecContext ( ctx , "UPDATE operating_system_vulnerabilities SET updated_at = NOW() - INTERVAL 5 MINUTE WHERE operating_system_id = 1" )
require . NoError ( t , err )
2025-04-22 13:07:18 +00:00
2025-07-10 20:42:26 +00:00
// Inserting the exact same vulnerability again will update again, as we need to bump updated_at
didInsert , err = ds . InsertOSVulnerability ( ctx , vulnsUpdate , fleet . MSRCSource )
2024-08-30 21:00:35 +00:00
require . NoError ( t , err )
2025-07-10 20:42:26 +00:00
assert . False ( t , didInsert )
// make sure the update happened
var recentRows uint
require . NoError ( t , sqlx . Get ( ds . writer ( ctx ) , & recentRows , "SELECT COUNT(*) FROM operating_system_vulnerabilities WHERE operating_system_id = 1 AND updated_at > NOW() - INTERVAL 5 SECOND" ) )
require . Equal ( t , uint ( 1 ) , recentRows )
2024-01-24 19:18:57 +00:00
2024-02-07 13:19:59 +00:00
expected := vulnsUpdate
expected . Source = fleet . MSRCSource
2024-01-24 19:18:57 +00:00
list1 , err := ds . ListOSVulnerabilitiesByOS ( ctx , 1 )
require . NoError ( t , err )
require . Len ( t , list1 , 1 )
2024-02-07 13:19:59 +00:00
require . Equal ( t , expected , list1 [ 0 ] )
2022-10-28 15:12:21 +00:00
}
func testDeleteOSVulnerabilitiesEmpty ( t * testing . T , ds * Datastore ) {
ctx := context . Background ( )
vulns := [ ] fleet . OSVulnerability {
2024-01-24 19:18:57 +00:00
{ CVE : "cve-1" , OSID : 1 } ,
{ CVE : "cve-1" , OSID : 1 } ,
{ CVE : "cve-3" , OSID : 1 } ,
{ CVE : "cve-2" , OSID : 1 } ,
2022-10-28 15:12:21 +00:00
}
err := ds . DeleteOSVulnerabilities ( ctx , vulns )
require . NoError ( t , err )
}
func testDeleteOSVulnerabilities ( t * testing . T , ds * Datastore ) {
ctx := context . Background ( )
vulns := [ ] fleet . OSVulnerability {
2024-01-24 19:18:57 +00:00
{ CVE : "cve-1" , OSID : 1 } ,
{ CVE : "cve-2" , OSID : 1 } ,
{ CVE : "cve-3" , OSID : 1 } ,
2022-10-28 15:12:21 +00:00
}
c , err := ds . InsertOSVulnerabilities ( ctx , vulns , fleet . MSRCSource )
require . NoError ( t , err )
require . Equal ( t , int64 ( 3 ) , c )
toDelete := [ ] fleet . OSVulnerability {
2024-01-24 19:18:57 +00:00
{ CVE : "cve-2" , OSID : 1 } ,
2022-10-28 15:12:21 +00:00
}
err = ds . DeleteOSVulnerabilities ( ctx , toDelete )
require . NoError ( t , err )
2024-01-24 19:18:57 +00:00
actual , err := ds . ListOSVulnerabilitiesByOS ( ctx , 1 )
2022-10-28 15:12:21 +00:00
require . NoError ( t , err )
require . ElementsMatch ( t , [ ] fleet . OSVulnerability {
2024-02-07 13:19:59 +00:00
{ CVE : "cve-1" , OSID : 1 , Source : fleet . MSRCSource } ,
{ CVE : "cve-3" , OSID : 1 , Source : fleet . MSRCSource } ,
2022-10-28 15:12:21 +00:00
} , actual )
2024-01-24 19:18:57 +00:00
}
2022-10-28 15:12:21 +00:00
2024-01-24 19:18:57 +00:00
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 )
2022-10-28 15:12:21 +00:00
require . NoError ( t , err )
2024-01-24 19:18:57 +00:00
_ , 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
2025-07-29 15:14:14 +00:00
err = ds . DeleteOutOfDateOSVulnerabilities ( ctx , fleet . NVDSource , time . Now ( ) . UTC ( ) . Add ( - time . Hour ) )
2024-01-24 19:18:57 +00:00
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 )
2022-10-28 15:12:21 +00:00
}
2025-08-14 14:13:37 +00:00
func testListKernelsByOS ( t * testing . T , ds * Datastore ) {
ctx := context . Background ( )
kernel1 := fleet . Software { Name : "linux-image-6.11.0-9-generic" , Version : "6.11.0-9.9" , Source : "deb_packages" , IsKernel : true }
kernel2 := fleet . Software { Name : "linux-image-7.11.0-10-generic" , Version : "7.11.0-10.10" , Source : "deb_packages" , IsKernel : true }
kernel3 := fleet . Software { Name : "linux-image-8.11.0-11-generic" , Version : "8.11.0-11.11" , Source : "deb_packages" , IsKernel : true }
software := [ ] fleet . Software {
kernel1 ,
kernel2 ,
kernel3 , // this one will have 0 vulns
}
cases := [ ] struct {
name string
team bool
host * fleet . Host
software [ ] fleet . Software
vulns [ ] fleet . SoftwareVulnerability
vulnsByKernelVersion map [ string ] [ ] string
os fleet . OperatingSystem
} {
{
name : "ubuntu no team" ,
team : false ,
host : test . NewHost ( t , ds , "host_ubuntu2410" , "" , "hostkey_ubuntu2410" , "hostuuid_ubuntu2410" , time . Now ( ) , test . WithPlatform ( "linux" ) ) ,
vulns : [ ] fleet . SoftwareVulnerability { { CVE : "CVE-2025-0001" } , { CVE : "CVE-2025-0002" } , { CVE : "CVE-2025-0003" } } ,
vulnsByKernelVersion : map [ string ] [ ] string {
kernel1 . Version : { "CVE-2025-0001" , "CVE-2025-0002" } ,
kernel2 . Version : { "CVE-2025-0003" } ,
kernel3 . Version : nil ,
} ,
software : software ,
os : fleet . OperatingSystem { Name : "Ubuntu" , Version : "24.10" , Arch : "x86_64" , KernelVersion : "6.11.0-9-generic" , Platform : "ubuntu" } ,
} ,
{
name : "ubuntu with team" ,
team : true ,
host : test . NewHost ( t , ds , "host_ubuntu2404" , "" , "hostkey_ubuntu2404" , "hostuuid_ubuntu2404" , time . Now ( ) , test . WithPlatform ( "linux" ) ) ,
software : software [ 1 : ] ,
vulns : [ ] fleet . SoftwareVulnerability { { CVE : "CVE-2025-0004" } , { CVE : "CVE-2025-0005" } , { CVE : "CVE-2025-0003" } } , // Note the overlap; kernel2 has 0003 from the previous test
vulnsByKernelVersion : map [ string ] [ ] string {
kernel2 . Version : { "CVE-2025-0004" , "CVE-2025-0005" , "CVE-2025-0003" } ,
kernel3 . Version : nil ,
} ,
os : fleet . OperatingSystem { Name : "Ubuntu" , Version : "24.04" , Arch : "x86_64" , KernelVersion : "6.11.0-9-generic" , Platform : "ubuntu" } ,
} ,
{
name : "amazon linux with team" ,
team : true ,
host : test . NewHost ( t , ds , "host_amzn2023" , "" , "hostkey_amzn2023" , "hostuuid_amzn2023" , time . Now ( ) , test . WithPlatform ( "fedora" ) ) ,
software : [ ] fleet . Software { { Name : "kernel" , Version : "6.1.144" , Arch : "x86_64" , Source : "rpm_packages" , IsKernel : true } } ,
vulns : [ ] fleet . SoftwareVulnerability { { CVE : "CVE-2025-0006" } } ,
vulnsByKernelVersion : map [ string ] [ ] string {
"6.1.144" : { "CVE-2025-0006" } ,
} ,
os : fleet . OperatingSystem { Name : "Amazon Linux" , Version : "2023.0.0" , Arch : "x86_64" , KernelVersion : "6.1.144-170.251.amzn2023.x86_64" , Platform : "amzn" } ,
} ,
{
name : "RHEL with team" ,
team : true ,
host : test . NewHost ( t , ds , "host_fedora41" , "" , "hostkey_fedora41" , "hostuuid_fedora41" , time . Now ( ) , test . WithPlatform ( "rhel" ) ) ,
software : [ ] fleet . Software { { Name : "kernel-core" , Version : "6.11.4" , Arch : "aarch64" , Source : "rpm_packages" , IsKernel : true } } ,
vulns : [ ] fleet . SoftwareVulnerability { { CVE : "CVE-2025-0007" } } ,
vulnsByKernelVersion : map [ string ] [ ] string {
"6.11.4" : { "CVE-2025-0007" } ,
} ,
os : fleet . OperatingSystem { Name : "Fedora Linux" , Version : "41.0.0" , Arch : "aarch64" , KernelVersion : "6.11.4-301.fc41.aarch64" , Platform : "rhel" } ,
} ,
}
for _ , tt := range cases {
t . Run ( tt . name , func ( t * testing . T ) {
var teamID uint
if tt . team {
team1 , err := ds . NewTeam ( ctx , & fleet . Team { Name : "team1_" + tt . name } )
require . NoError ( t , err )
require . NoError ( t , ds . AddHostsToTeam ( ctx , fleet . NewAddHostsToTeamParams ( & team1 . ID , [ ] uint { tt . host . ID } ) ) )
teamID = team1 . ID
}
require . NoError ( t , ds . UpdateHostOperatingSystem ( ctx , tt . host . ID , tt . os ) )
os , err := ds . GetHostOperatingSystem ( ctx , tt . host . ID )
require . NoError ( t , err )
_ , err = ds . UpdateHostSoftware ( ctx , tt . host . ID , tt . software )
require . NoError ( t , err )
require . NoError ( t , ds . LoadHostSoftware ( ctx , tt . host , false ) )
// Sort the host software by name to enforce a deterministic order
sort . Slice ( tt . host . Software , func ( i , j int ) bool {
return tt . host . Software [ i ] . Name < tt . host . Software [ j ] . Name
} )
softwareIDByVersion := make ( map [ string ] uint )
for _ , s := range tt . host . Software {
softwareIDByVersion [ s . Version ] = s . ID
}
cpes := [ ] fleet . SoftwareCPE {
{ SoftwareID : tt . host . Software [ 0 ] . ID , CPE : "somecpe" } ,
}
_ , err = ds . UpsertSoftwareCPEs ( ctx , cpes )
require . NoError ( t , err )
require . NoError ( t , ds . LoadHostSoftware ( ctx , tt . host , false ) )
var vulnsToInsert [ ] fleet . SoftwareVulnerability
for k , v := range tt . vulnsByKernelVersion {
for _ , s := range v {
vulnsToInsert = append ( vulnsToInsert , fleet . SoftwareVulnerability {
SoftwareID : softwareIDByVersion [ k ] ,
CVE : s ,
} )
}
}
for _ , v := range vulnsToInsert {
_ , err = ds . InsertSoftwareVulnerability ( ctx , v , fleet . NVDSource )
require . NoError ( t , err )
}
require . NoError ( t , ds . LoadHostSoftware ( ctx , tt . host , false ) )
require . NoError ( t , ds . UpdateOSVersions ( ctx ) )
require . NoError ( t , ds . SyncHostsSoftware ( ctx , time . Now ( ) ) )
require . NoError ( t , ds . ReconcileSoftwareTitles ( ctx ) )
require . NoError ( t , ds . SyncHostsSoftwareTitles ( ctx , time . Now ( ) ) )
require . NoError ( t , ds . InsertKernelSoftwareMapping ( ctx ) )
kernels , err := ds . ListKernelsByOS ( ctx , os . OSVersionID , & teamID )
require . NoError ( t , err )
require . Len ( t , kernels , len ( tt . software ) )
for _ , kernel := range kernels {
expectedVulns , ok := tt . vulnsByKernelVersion [ kernel . Version ]
require . True ( t , ok )
require . ElementsMatchf ( t , expectedVulns , kernel . Vulnerabilities , "unexpected vulnerabilities for kernel %s" , kernel . Version )
require . Equal ( t , kernel . HostsCount , uint ( 1 ) )
}
expectedSet := make ( map [ string ] struct { } )
for _ , v := range tt . vulns {
expectedSet [ v . CVE ] = struct { } { }
}
cves , err := ds . ListVulnsByOsNameAndVersion ( ctx , os . Name , os . Version , false , & teamID )
require . NoError ( t , err )
for _ , g := range cves {
_ , ok := expectedSet [ g . CVE ]
assert . Truef ( t , ok , "got unexpected CVE: %s" , g . CVE )
}
assert . Len ( t , cves , len ( tt . vulns ) )
cves , err = ds . ListVulnsByOsNameAndVersion ( ctx , os . Name , "not_found" , false , nil )
require . NoError ( t , err )
require . Empty ( t , cves )
cves , err = ds . ListVulnsByOsNameAndVersion ( ctx , os . Name , os . Version , true , nil )
require . NoError ( t , err )
require . Len ( t , cves , len ( tt . vulns ) )
for _ , g := range cves {
_ , ok := expectedSet [ g . CVE ]
assert . True ( t , ok )
}
cves , err = ds . ListVulnsByOsNameAndVersion ( ctx , os . Name , "not_found" , true , nil )
require . NoError ( t , err )
require . Empty ( t , cves )
} )
}
}
func testKernelVulnsHostCount ( t * testing . T , ds * Datastore ) {
ctx := context . Background ( )
host1 := test . NewHost ( t , ds , "host_ubuntu2410" , "" , "hostkey_ubuntu2410" , "hostuuid_ubuntu2410" , time . Now ( ) , test . WithPlatform ( "ubuntu" ) )
host2 := test . NewHost ( t , ds , "host_ubuntu2404" , "" , "hostkey_ubuntu2404" , "hostuuid_ubuntu2404" , time . Now ( ) , test . WithPlatform ( "ubuntu" ) )
host3 := test . NewHost ( t , ds , "host_ubuntu2404_2" , "" , "hostkey_ubuntu2404_2" , "hostuuid_ubuntu2404_2" , time . Now ( ) , test . WithPlatform ( "ubuntu" ) )
// Same as host 2 and 3, but on a different team
host4 := test . NewHost ( t , ds , "host_ubuntu2404_3" , "" , "hostkey_ubuntu2404_3" , "hostuuid_ubuntu2404_3" , time . Now ( ) , test . WithPlatform ( "ubuntu" ) )
os1 := & fleet . OperatingSystem { Name : "Ubuntu" , Version : "24.10" , Arch : "x86_64" , KernelVersion : "6.11.0-9-generic" , Platform : "ubuntu" }
os2 := & fleet . OperatingSystem { Name : "Ubuntu" , Version : "24.04" , Arch : "x86_64" , KernelVersion : "6.11.0-9-generic" , Platform : "ubuntu" }
kernel := fleet . Software { Name : "linux-image-6.11.0-9-generic" , Version : "6.11.0-9.9" , Source : "deb_packages" , IsKernel : true }
team1 , err := ds . NewTeam ( ctx , & fleet . Team { Name : "team1_" + t . Name ( ) } )
require . NoError ( t , err )
team2 , err := ds . NewTeam ( ctx , & fleet . Team { Name : "team2_" + t . Name ( ) } )
require . NoError ( t , err )
require . NoError ( t , ds . AddHostsToTeam ( ctx , fleet . NewAddHostsToTeamParams ( & team1 . ID , [ ] uint { host1 . ID , host2 . ID , host3 . ID } ) ) )
require . NoError ( t , ds . AddHostsToTeam ( ctx , fleet . NewAddHostsToTeamParams ( & team2 . ID , [ ] uint { host4 . ID } ) ) )
require . NoError ( t , ds . UpdateHostOperatingSystem ( ctx , host1 . ID , * os1 ) )
require . NoError ( t , ds . UpdateHostOperatingSystem ( ctx , host2 . ID , * os2 ) )
require . NoError ( t , ds . UpdateHostOperatingSystem ( ctx , host3 . ID , * os2 ) )
require . NoError ( t , ds . UpdateHostOperatingSystem ( ctx , host4 . ID , * os2 ) )
os1 , err = ds . GetHostOperatingSystem ( ctx , host1 . ID )
require . NoError ( t , err )
os2 , err = ds . GetHostOperatingSystem ( ctx , host2 . ID )
require . NoError ( t , err )
addKernelToHost := func ( h * fleet . Host ) {
var vulnsToInsert [ ] fleet . SoftwareVulnerability
_ , err = ds . UpdateHostSoftware ( ctx , h . ID , [ ] fleet . Software { kernel } )
require . NoError ( t , err )
require . NoError ( t , ds . LoadHostSoftware ( ctx , h , false ) )
_ , err = ds . UpsertSoftwareCPEs ( ctx , [ ] fleet . SoftwareCPE { { SoftwareID : h . Software [ 0 ] . ID , CPE : "somecpe" } } )
require . NoError ( t , err )
for _ , cve := range [ ] string { "CVE-2025-0001" , "CVE-2025-0002" } {
vulnsToInsert = append ( vulnsToInsert , fleet . SoftwareVulnerability {
SoftwareID : h . Software [ 0 ] . ID ,
CVE : cve ,
} )
}
for _ , v := range vulnsToInsert {
_ , err = ds . InsertSoftwareVulnerability ( ctx , v , fleet . NVDSource )
require . NoError ( t , err )
}
}
for _ , h := range [ ] * fleet . Host { host1 , host2 , host3 , host4 } {
addKernelToHost ( h )
}
for _ , h := range [ ] * fleet . Host { host1 , host2 , host3 , host4 } {
require . NoError ( t , ds . LoadHostSoftware ( ctx , h , false ) )
}
2025-08-26 00:55:57 +00:00
updateMappings := func ( ) {
require . NoError ( t , ds . UpdateOSVersions ( ctx ) )
require . NoError ( t , ds . SyncHostsSoftware ( ctx , time . Now ( ) ) )
require . NoError ( t , ds . ReconcileSoftwareTitles ( ctx ) )
require . NoError ( t , ds . SyncHostsSoftwareTitles ( ctx , time . Now ( ) ) )
require . NoError ( t , ds . InsertKernelSoftwareMapping ( ctx ) )
}
updateMappings ( )
2025-08-14 14:13:37 +00:00
expectedCVEs := [ ] string { "CVE-2025-0001" , "CVE-2025-0002" }
kernels , err := ds . ListKernelsByOS ( ctx , os1 . OSVersionID , & team1 . ID )
require . NoError ( t , err )
require . Len ( t , kernels , 1 )
assert . ElementsMatchf ( t , expectedCVEs , kernels [ 0 ] . Vulnerabilities , "unexpected vulnerabilities for kernel %s" , kernels [ 0 ] . Version )
assert . Equal ( t , uint ( 1 ) , kernels [ 0 ] . HostsCount ) // host1
kernels , err = ds . ListKernelsByOS ( ctx , os2 . OSVersionID , & team1 . ID )
require . NoError ( t , err )
require . Len ( t , kernels , 1 )
assert . ElementsMatchf ( t , expectedCVEs , kernels [ 0 ] . Vulnerabilities , "unexpected vulnerabilities for kernel %s" , kernels [ 0 ] . Version )
require . Equal ( t , uint ( 2 ) , kernels [ 0 ] . HostsCount ) // host2, host3
kernels , err = ds . ListKernelsByOS ( ctx , os2 . OSVersionID , & team2 . ID )
require . NoError ( t , err )
require . Len ( t , kernels , 1 )
assert . ElementsMatchf ( t , expectedCVEs , kernels [ 0 ] . Vulnerabilities , "unexpected vulnerabilities for kernel %s" , kernels [ 0 ] . Version )
assert . Equal ( t , uint ( 1 ) , kernels [ 0 ] . HostsCount ) // host4
// "All teams" (aka team ID is nil)
// For os2, should be 3 since it's on host2, host3, and host4
kernels , err = ds . ListKernelsByOS ( ctx , os2 . OSVersionID , nil )
require . NoError ( t , err )
require . Len ( t , kernels , 1 )
assert . ElementsMatchf ( t , expectedCVEs , kernels [ 0 ] . Vulnerabilities , "unexpected vulnerabilities for kernel %s" , kernels [ 0 ] . Version )
assert . Equal ( t , uint ( 3 ) , kernels [ 0 ] . HostsCount )
// For os1, should be 1 since it's on host1
kernels , err = ds . ListKernelsByOS ( ctx , os1 . OSVersionID , nil )
require . NoError ( t , err )
require . Len ( t , kernels , 1 )
assert . ElementsMatchf ( t , expectedCVEs , kernels [ 0 ] . Vulnerabilities , "unexpected vulnerabilities for kernel %s" , kernels [ 0 ] . Version )
assert . Equal ( t , uint ( 1 ) , kernels [ 0 ] . HostsCount )
// Add another host to team1, counts should update
host5 := test . NewHost ( t , ds , "host_ubuntu2404_4" , "" , "hostkey_ubuntu2404_4" , "hostuuid_ubuntu2404_4" , time . Now ( ) , test . WithPlatform ( "ubuntu" ) )
require . NoError ( t , ds . AddHostsToTeam ( ctx , fleet . NewAddHostsToTeamParams ( & team1 . ID , [ ] uint { host5 . ID } ) ) )
require . NoError ( t , ds . UpdateHostOperatingSystem ( ctx , host5 . ID , * os2 ) )
addKernelToHost ( host5 )
2025-08-26 00:55:57 +00:00
updateMappings ( )
2025-08-14 14:13:37 +00:00
kernels , err = ds . ListKernelsByOS ( ctx , os2 . OSVersionID , & team1 . ID )
require . NoError ( t , err )
require . Len ( t , kernels , 1 )
assert . ElementsMatchf ( t , expectedCVEs , kernels [ 0 ] . Vulnerabilities , "unexpected vulnerabilities for kernel %s" , kernels [ 0 ] . Version )
assert . Equal ( t , uint ( 3 ) , kernels [ 0 ] . HostsCount ) // host2, host3, host5
// "All teams" (aka team ID is nil)
// For os2, should be 4 since it's on host2, host3, host4, and now host5
kernels , err = ds . ListKernelsByOS ( ctx , os2 . OSVersionID , nil )
require . NoError ( t , err )
require . Len ( t , kernels , 1 )
assert . ElementsMatchf ( t , expectedCVEs , kernels [ 0 ] . Vulnerabilities , "unexpected vulnerabilities for kernel %s" , kernels [ 0 ] . Version )
assert . Equal ( t , uint ( 4 ) , kernels [ 0 ] . HostsCount )
2025-08-26 00:55:57 +00:00
// Delete host 1. We should see the count for the kernel go down to 0.
require . NoError ( t , ds . DeleteHost ( ctx , host1 . ID ) )
updateMappings ( )
ExecAdhocSQL ( t , ds , func ( q sqlx . ExtContext ) error {
var count uint
err := sqlx . GetContext ( ctx , q , & count , "SELECT hosts_count FROM kernel_host_counts WHERE os_version_id = ?" , os1 . OSVersionID )
require . NoError ( t , err )
assert . Zero ( t , count )
return nil
} )
kernels , err = ds . ListKernelsByOS ( ctx , os1 . OSVersionID , nil )
require . NoError ( t , err )
require . Empty ( t , kernels )
2025-08-14 14:13:37 +00:00
}