mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
810 lines
21 KiB
Go
810 lines
21 KiB
Go
package osv
|
|
|
|
import (
|
|
"compress/gzip"
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestIsPlatformSupported(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
platform string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Ubuntu lowercase",
|
|
platform: "ubuntu",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Ubuntu mixed case",
|
|
platform: "Ubuntu",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Ubuntu uppercase",
|
|
platform: "UBUNTU",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "RHEL lowercase",
|
|
platform: "rhel",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "RHEL uppercase",
|
|
platform: "RHEL",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Windows not supported",
|
|
platform: "windows",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Empty string not supported",
|
|
platform: "",
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := IsPlatformSupported(tt.platform)
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractUbuntuVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
version string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Ubuntu 22.04 LTS",
|
|
version: "22.04.8 LTS",
|
|
expected: "2204",
|
|
},
|
|
{
|
|
name: "Ubuntu 20.04 LTS",
|
|
version: "20.04.1 LTS",
|
|
expected: "2004",
|
|
},
|
|
{
|
|
name: "Ubuntu 18.04",
|
|
version: "18.04",
|
|
expected: "1804",
|
|
},
|
|
{
|
|
name: "Ubuntu 24.04 no LTS suffix",
|
|
version: "24.04.0",
|
|
expected: "2404",
|
|
},
|
|
{
|
|
name: "Ubuntu 16.04 with extra spaces",
|
|
version: "16.04.7 LTS ",
|
|
expected: "1604",
|
|
},
|
|
{
|
|
name: "Invalid version - single digit",
|
|
version: "22",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Empty string",
|
|
version: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Interim release 23.10",
|
|
version: "23.10",
|
|
expected: "2310",
|
|
},
|
|
{
|
|
name: "Interim release 24.10 with patch",
|
|
version: "24.10.1",
|
|
expected: "2410",
|
|
},
|
|
{
|
|
name: "Version with codename suffix",
|
|
version: "22.04.1 LTS (Jammy Jellyfish)",
|
|
expected: "2204",
|
|
},
|
|
{
|
|
name: "Very old version 14.04",
|
|
version: "14.04.6 LTS",
|
|
expected: "1404",
|
|
},
|
|
{
|
|
name: "Future version 25.04",
|
|
version: "25.04 LTS",
|
|
expected: "2504",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := extractUbuntuVersion(tt.version)
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNormalizeKernelVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
version string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Generic kernel",
|
|
version: "5.15.0-94-generic",
|
|
expected: "5.15.0-94",
|
|
},
|
|
{
|
|
name: "Lowlatency kernel",
|
|
version: "5.15.0-94-lowlatency",
|
|
expected: "5.15.0-94",
|
|
},
|
|
{
|
|
name: "Generic 64k kernel",
|
|
version: "6.8.0-31-generic-64k",
|
|
expected: "6.8.0-31",
|
|
},
|
|
{
|
|
name: "Kernel with only one part",
|
|
version: "5.15.0",
|
|
expected: "5.15.0",
|
|
},
|
|
{
|
|
name: "Empty string",
|
|
version: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Already normalized",
|
|
version: "5.15.0-94",
|
|
expected: "5.15.0-94",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := normalizeKernelVersion(tt.version)
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKernelVersionMatches(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
softwareVersion string
|
|
osvVersion string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Exact match after normalization",
|
|
softwareVersion: "5.15.0-94-generic",
|
|
osvVersion: "5.15.0-94",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "OSV version with build number",
|
|
softwareVersion: "5.15.0-94-generic",
|
|
osvVersion: "5.15.0-94.104",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "OSV version with different build",
|
|
softwareVersion: "5.15.0-94-lowlatency",
|
|
osvVersion: "5.15.0-94.103",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Different kernel versions",
|
|
softwareVersion: "5.15.0-93-generic",
|
|
osvVersion: "5.15.0-94.104",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Different major versions",
|
|
softwareVersion: "6.8.0-31-generic",
|
|
osvVersion: "5.15.0-94.104",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "OSV prefix but not with dot",
|
|
softwareVersion: "5.15.0-94-generic",
|
|
osvVersion: "5.15.0-941",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Exact match without normalization needed",
|
|
softwareVersion: "5.15.0-94",
|
|
osvVersion: "5.15.0-94",
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := kernelVersionMatches(tt.softwareVersion, tt.osvVersion)
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsVulnerable(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
softwareVersion string
|
|
vuln OSVVulnerability
|
|
isKernelPackage bool
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Kernel - version in explicit list",
|
|
softwareVersion: "5.15.0-94-generic",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-1234",
|
|
Versions: []string{"5.15.0-94.104", "5.15.0-93.103"},
|
|
},
|
|
isKernelPackage: true,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Kernel - version not in list",
|
|
softwareVersion: "5.15.0-95-generic",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-1234",
|
|
Versions: []string{"5.15.0-94.104", "5.15.0-93.103"},
|
|
},
|
|
isKernelPackage: true,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Regular package - exact version match",
|
|
softwareVersion: "1.2.3-4ubuntu1",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-5678",
|
|
Versions: []string{"1.2.3-4ubuntu1", "1.2.3-3ubuntu1"},
|
|
},
|
|
isKernelPackage: false,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Regular package - no match",
|
|
softwareVersion: "1.2.3-5ubuntu1",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-5678",
|
|
Versions: []string{"1.2.3-4ubuntu1", "1.2.3-3ubuntu1"},
|
|
},
|
|
isKernelPackage: false,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Range - vulnerable (in range)",
|
|
softwareVersion: "2.0.0",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-9999",
|
|
Introduced: "1.0.0",
|
|
Fixed: "3.0.0",
|
|
},
|
|
isKernelPackage: false,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Range - not vulnerable (below introduced)",
|
|
softwareVersion: "0.9.0",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-9999",
|
|
Introduced: "1.0.0",
|
|
Fixed: "3.0.0",
|
|
},
|
|
isKernelPackage: false,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Range - not vulnerable (at or above fixed)",
|
|
softwareVersion: "3.0.0",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-9999",
|
|
Introduced: "1.0.0",
|
|
Fixed: "3.0.0",
|
|
},
|
|
isKernelPackage: false,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Range - vulnerable from zero",
|
|
softwareVersion: "0.5.0",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-8888",
|
|
Fixed: "1.0.0",
|
|
},
|
|
isKernelPackage: false,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Range - no fixed version (vulnerable)",
|
|
softwareVersion: "2.0.0",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-7777",
|
|
Introduced: "1.0.0",
|
|
},
|
|
isKernelPackage: false,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Empty versions list with no range (vulnerable by default)",
|
|
softwareVersion: "1.0.0",
|
|
vuln: OSVVulnerability{
|
|
CVE: "CVE-2024-6666",
|
|
},
|
|
isKernelPackage: false,
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := isVulnerable(tt.softwareVersion, tt.vuln, tt.isKernelPackage)
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatchSoftwareToOSV(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
software []fleet.Software
|
|
artifact *OSVArtifact
|
|
expected []fleet.SoftwareVulnerability
|
|
}{
|
|
{
|
|
name: "Kernel package mapping",
|
|
software: []fleet.Software{
|
|
{ID: 1, Name: "linux-image-5.15.0-94-generic", Version: "5.15.0-94-generic"},
|
|
{ID: 2, Name: "linux-signed-image-5.15.0-94-generic", Version: "5.15.0-94-generic"},
|
|
},
|
|
artifact: &OSVArtifact{
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
"linux": {
|
|
{CVE: "CVE-2024-1111", Versions: []string{"5.15.0-94.104"}},
|
|
},
|
|
},
|
|
},
|
|
expected: []fleet.SoftwareVulnerability{
|
|
{SoftwareID: 1, CVE: "CVE-2024-1111"},
|
|
{SoftwareID: 2, CVE: "CVE-2024-1111"},
|
|
},
|
|
},
|
|
{
|
|
name: "Regular package exact match",
|
|
software: []fleet.Software{
|
|
{ID: 1, Name: "curl", Version: "7.68.0-1ubuntu2.21"},
|
|
},
|
|
artifact: &OSVArtifact{
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
"curl": {
|
|
{CVE: "CVE-2024-2222", Versions: []string{"7.68.0-1ubuntu2.21"}},
|
|
},
|
|
},
|
|
},
|
|
expected: []fleet.SoftwareVulnerability{
|
|
{SoftwareID: 1, CVE: "CVE-2024-2222"},
|
|
},
|
|
},
|
|
{
|
|
name: "Package not in artifact",
|
|
software: []fleet.Software{
|
|
{ID: 1, Name: "unknown-package", Version: "1.0.0"},
|
|
},
|
|
artifact: &OSVArtifact{
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
"curl": {
|
|
{CVE: "CVE-2024-2222", Versions: []string{"7.68.0-1ubuntu2.21"}},
|
|
},
|
|
},
|
|
},
|
|
expected: []fleet.SoftwareVulnerability{},
|
|
},
|
|
{
|
|
name: "Multiple vulnerabilities for same package",
|
|
software: []fleet.Software{
|
|
{ID: 1, Name: "openssl", Version: "1.1.1f-1ubuntu2.20"},
|
|
},
|
|
artifact: &OSVArtifact{
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
"openssl": {
|
|
{CVE: "CVE-2024-3333", Versions: []string{"1.1.1f-1ubuntu2.20"}},
|
|
{CVE: "CVE-2024-4444", Versions: []string{"1.1.1f-1ubuntu2.20"}},
|
|
},
|
|
},
|
|
},
|
|
expected: []fleet.SoftwareVulnerability{
|
|
{SoftwareID: 1, CVE: "CVE-2024-3333"},
|
|
{SoftwareID: 1, CVE: "CVE-2024-4444"},
|
|
},
|
|
},
|
|
{
|
|
name: "Version doesn't match",
|
|
software: []fleet.Software{
|
|
{ID: 1, Name: "curl", Version: "7.68.0-1ubuntu2.22"},
|
|
},
|
|
artifact: &OSVArtifact{
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
"curl": {
|
|
{CVE: "CVE-2024-2222", Versions: []string{"7.68.0-1ubuntu2.21"}},
|
|
},
|
|
},
|
|
},
|
|
expected: []fleet.SoftwareVulnerability{},
|
|
},
|
|
{
|
|
name: "Kernel headers should not map to linux",
|
|
software: []fleet.Software{
|
|
{ID: 1, Name: "linux-headers-5.15.0-94", Version: "5.15.0-94-generic"},
|
|
},
|
|
artifact: &OSVArtifact{
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
"linux": {
|
|
{CVE: "CVE-2024-1111", Versions: []string{"5.15.0-94.104"}},
|
|
},
|
|
},
|
|
},
|
|
expected: []fleet.SoftwareVulnerability{},
|
|
},
|
|
{
|
|
name: "Range-based vulnerability matching",
|
|
software: []fleet.Software{
|
|
{ID: 1, Name: "apache2", Version: "2.4.41"},
|
|
},
|
|
artifact: &OSVArtifact{
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
"apache2": {
|
|
{CVE: "CVE-2024-5555", Introduced: "2.4.0", Fixed: "2.4.50"},
|
|
},
|
|
},
|
|
},
|
|
expected: []fleet.SoftwareVulnerability{
|
|
{SoftwareID: 1, CVE: "CVE-2024-5555", ResolvedInVersion: ptr.String("2.4.50")},
|
|
},
|
|
},
|
|
{
|
|
name: "Range-based vulnerability matching with multiple fixed versions",
|
|
software: []fleet.Software{
|
|
{ID: 1, Name: "apache2", Version: "2.4.41"},
|
|
},
|
|
artifact: &OSVArtifact{
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
"apache2": {
|
|
{CVE: "CVE-2024-5555", Introduced: "2.4.0", Fixed: "2.4.50"},
|
|
{CVE: "CVE-2024-6666", Introduced: "2.4.10", Fixed: "2.4.48"},
|
|
},
|
|
},
|
|
},
|
|
expected: []fleet.SoftwareVulnerability{
|
|
{SoftwareID: 1, CVE: "CVE-2024-5555", ResolvedInVersion: ptr.String("2.4.50")},
|
|
{SoftwareID: 1, CVE: "CVE-2024-6666", ResolvedInVersion: ptr.String("2.4.48")},
|
|
},
|
|
},
|
|
{
|
|
name: "emacs-common not vulnerable",
|
|
software: []fleet.Software{
|
|
{ID: 60992, Name: "emacs-common", Version: "1:29.3+1-1ubuntu2"},
|
|
},
|
|
artifact: &OSVArtifact{
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
// This avoids OVAL's false positive from USN grouping where
|
|
// CVE-2024-30205 was incorrectly applied to emacs-common
|
|
// because it was grouped with emacs in the same USN advisory
|
|
"emacs": {
|
|
{CVE: "CVE-2024-30205", Versions: []string{"1:26.3+1-1ubuntu2"}},
|
|
},
|
|
},
|
|
},
|
|
expected: []fleet.SoftwareVulnerability{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := matchSoftwareToOSV(tt.software, tt.artifact)
|
|
require.ElementsMatch(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindLatestOSVArtifactForVersion(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create test artifacts for different Ubuntu versions and dates
|
|
artifacts := []struct {
|
|
filename string
|
|
age int // days old (to set mod time)
|
|
}{
|
|
{"osv-ubuntu-2204-2026-03-28.json.gz", 3}, // Older 22.04
|
|
{"osv-ubuntu-2204-2026-03-30.json.gz", 1}, // Newer 22.04 (should be selected)
|
|
{"osv-ubuntu-2204-2026-03-29.json.gz", 2}, // Middle 22.04
|
|
{"osv-ubuntu-2004-2026-03-30.json.gz", 1}, // 20.04 (different version)
|
|
{"osv-ubuntu-1804-2026-03-30.json.gz", 1}, // 18.04 (different version)
|
|
{"other-file.json.gz", 0}, // Non-OSV file
|
|
{"osv-ubuntu-2204-delta-2026-03-30.json.gz", 1}, // Delta file (should be ignored by pattern)
|
|
}
|
|
|
|
for _, a := range artifacts {
|
|
path := filepath.Join(tmpDir, a.filename)
|
|
err := os.WriteFile(path, []byte("test"), 0o644)
|
|
require.NoError(t, err)
|
|
|
|
// Set modification time to simulate different ages
|
|
modTime := time.Now().Add(-time.Duration(a.age) * 24 * time.Hour)
|
|
err = os.Chtimes(path, modTime, modTime)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
ubuntuVersion string
|
|
expectedFile string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "finds latest 22.04 artifact",
|
|
ubuntuVersion: "2204",
|
|
expectedFile: "osv-ubuntu-2204-2026-03-30.json.gz", // Most recent
|
|
},
|
|
{
|
|
name: "finds latest 20.04 artifact",
|
|
ubuntuVersion: "2004",
|
|
expectedFile: "osv-ubuntu-2004-2026-03-30.json.gz",
|
|
},
|
|
{
|
|
name: "finds latest 18.04 artifact",
|
|
ubuntuVersion: "1804",
|
|
expectedFile: "osv-ubuntu-1804-2026-03-30.json.gz",
|
|
},
|
|
{
|
|
name: "returns error for non-existent version",
|
|
ubuntuVersion: "2404",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := findLatestOSVArtifactForVersion(tmpDir, tt.ubuntuVersion)
|
|
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "no OSV artifact found")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, filepath.Join(tmpDir, tt.expectedFile), result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadOSVArtifactZeroTimeUsesLatest(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create artifacts with different dates
|
|
artifacts := []struct {
|
|
filename string
|
|
age int
|
|
}{
|
|
{"osv-ubuntu-2204-2026-03-28.json.gz", 3}, // Older
|
|
{"osv-ubuntu-2204-2026-03-30.json.gz", 1}, // Newer (should be selected)
|
|
}
|
|
|
|
for _, a := range artifacts {
|
|
path := filepath.Join(tmpDir, a.filename)
|
|
|
|
// Create a minimal valid gzipped JSON artifact
|
|
f, err := os.Create(path)
|
|
require.NoError(t, err)
|
|
|
|
gz := gzip.NewWriter(f)
|
|
_, err = gz.Write([]byte(`{"schema_version":"1.0.0","ubuntu_version":"2204","generated":"2026-03-30T00:00:00Z","total_cves":0,"total_packages":0,"vulnerabilities":{}}`))
|
|
require.NoError(t, err)
|
|
gz.Close()
|
|
f.Close()
|
|
|
|
// Set modification time
|
|
modTime := time.Now().Add(-time.Duration(a.age) * 24 * time.Hour)
|
|
err = os.Chtimes(path, modTime, modTime)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Test with zero time (simulates DisableDataSync=true)
|
|
ctx := context.Background()
|
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
ver := fleet.OSVersion{Name: "Ubuntu 22.04.8 LTS", Version: "22.04.8 LTS"}
|
|
|
|
artifact, err := loadOSVArtifact(ctx, ver, tmpDir, logger, time.Time{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, artifact)
|
|
|
|
// Verify it loaded successfully (artifact should have schema_version)
|
|
require.Equal(t, "1.0.0", artifact.SchemaVersion)
|
|
}
|
|
|
|
func TestExtractRHELMajorVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{"RHEL 9.4.0", "9.4.0", "9"},
|
|
{"RHEL 8.10.0", "8.10.0", "8"},
|
|
{"RHEL 7.9.0", "7.9.0", "7"},
|
|
{"Major only", "9", "9"},
|
|
{"Empty string", "", ""},
|
|
{"Whitespace", " 9.4.0 ", "9"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.Equal(t, tt.expected, extractRHELMajorVersion(tt.input))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsVulnerableRPM(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
version string
|
|
release string
|
|
vuln OSVVulnerability
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "vulnerable - older than fixed",
|
|
version: "2.1.3", release: "3.el9",
|
|
vuln: OSVVulnerability{Fixed: "0:2.1.3-4.el9_1", Introduced: "0"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "not vulnerable - at fixed version",
|
|
version: "2.1.3", release: "4.el9_1",
|
|
vuln: OSVVulnerability{Fixed: "0:2.1.3-4.el9_1", Introduced: "0"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "not vulnerable - newer than fixed",
|
|
version: "2.1.3", release: "5.el9_2",
|
|
vuln: OSVVulnerability{Fixed: "0:2.1.3-4.el9_1", Introduced: "0"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "vulnerable - no release field",
|
|
version: "1.0.0", release: "",
|
|
vuln: OSVVulnerability{Fixed: "0:2.0.0-1.el9", Introduced: "0"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "vulnerable - no fixed version (still affected)",
|
|
version: "1.0.0", release: "1.el9",
|
|
vuln: OSVVulnerability{Introduced: "0"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "not vulnerable - below introduced",
|
|
version: "0.9.0", release: "1.el9",
|
|
vuln: OSVVulnerability{Fixed: "0:2.0.0-1.el9", Introduced: "0:1.0.0-1.el9"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "vulnerable - kernel version with epoch",
|
|
version: "5.14.0", release: "503.26.1.el9_5",
|
|
vuln: OSVVulnerability{Fixed: "0:5.14.0-611.8.1.el9_7", Introduced: "0"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "not vulnerable - kernel at fixed",
|
|
version: "5.14.0", release: "611.8.1.el9_7",
|
|
vuln: OSVVulnerability{Fixed: "0:5.14.0-611.8.1.el9_7", Introduced: "0"},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.Equal(t, tt.expected, isVulnerableRPM(tt.version, tt.release, tt.vuln))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatchSoftwareToRHELOSV(t *testing.T) {
|
|
artifact := &RHELOSVArtifact{
|
|
RHELVersion: "9",
|
|
Vulnerabilities: map[string][]OSVVulnerability{
|
|
"curl": {
|
|
{CVE: "CVE-2024-1234", Fixed: "0:7.76.1-29.el9_4.2", Introduced: "0"},
|
|
},
|
|
"kernel": {
|
|
{CVE: "CVE-2025-5678", Fixed: "0:5.14.0-611.8.1.el9_7", Introduced: "0"},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("regular package match", func(t *testing.T) {
|
|
software := []fleet.Software{
|
|
{ID: 1, Name: "curl", Version: "7.76.1", Release: "26.el9_3.2"},
|
|
}
|
|
result := matchSoftwareToRHELOSV(software, artifact)
|
|
require.Len(t, result, 1)
|
|
require.Equal(t, "CVE-2024-1234", result[0].CVE)
|
|
require.Equal(t, uint(1), result[0].SoftwareID)
|
|
require.NotNil(t, result[0].ResolvedInVersion)
|
|
require.Equal(t, "0:7.76.1-29.el9_4.2", *result[0].ResolvedInVersion)
|
|
})
|
|
|
|
t.Run("package not in artifact", func(t *testing.T) {
|
|
software := []fleet.Software{
|
|
{ID: 2, Name: "nginx", Version: "1.0", Release: "1.el9"},
|
|
}
|
|
result := matchSoftwareToRHELOSV(software, artifact)
|
|
require.Empty(t, result)
|
|
})
|
|
|
|
t.Run("kernel-core maps to kernel", func(t *testing.T) {
|
|
software := []fleet.Software{
|
|
{ID: 3, Name: "kernel-core", Version: "5.14.0", Release: "503.26.1.el9_5"},
|
|
}
|
|
result := matchSoftwareToRHELOSV(software, artifact)
|
|
require.Len(t, result, 1)
|
|
require.Equal(t, "CVE-2025-5678", result[0].CVE)
|
|
require.NotNil(t, result[0].ResolvedInVersion)
|
|
require.Equal(t, "0:5.14.0-611.8.1.el9_7", *result[0].ResolvedInVersion)
|
|
})
|
|
|
|
t.Run("kernel-modules maps to kernel", func(t *testing.T) {
|
|
software := []fleet.Software{
|
|
{ID: 4, Name: "kernel-modules", Version: "5.14.0", Release: "503.26.1.el9_5"},
|
|
}
|
|
result := matchSoftwareToRHELOSV(software, artifact)
|
|
require.Len(t, result, 1)
|
|
require.Equal(t, "CVE-2025-5678", result[0].CVE)
|
|
})
|
|
|
|
t.Run("kernel-debug-core maps to kernel", func(t *testing.T) {
|
|
software := []fleet.Software{
|
|
{ID: 5, Name: "kernel-debug-core", Version: "5.14.0", Release: "503.26.1.el9_5"},
|
|
}
|
|
result := matchSoftwareToRHELOSV(software, artifact)
|
|
require.Len(t, result, 1)
|
|
require.Equal(t, "CVE-2025-5678", result[0].CVE)
|
|
})
|
|
|
|
t.Run("patched kernel not vulnerable", func(t *testing.T) {
|
|
software := []fleet.Software{
|
|
{ID: 6, Name: "kernel-core", Version: "5.14.0", Release: "611.8.1.el9_7"},
|
|
}
|
|
result := matchSoftwareToRHELOSV(software, artifact)
|
|
require.Empty(t, result)
|
|
})
|
|
|
|
t.Run("patched curl not vulnerable", func(t *testing.T) {
|
|
software := []fleet.Software{
|
|
{ID: 7, Name: "curl", Version: "7.76.1", Release: "29.el9_4.2"},
|
|
}
|
|
result := matchSoftwareToRHELOSV(software, artifact)
|
|
require.Empty(t, result)
|
|
})
|
|
}
|