mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Scan goval-dict for rhel kernel vulnerabilities(#39749)
This commit is contained in:
parent
573bf877fe
commit
fb2ddde9bf
9 changed files with 105 additions and 5 deletions
1
changes/33990-rhel-kernel-vulns
Normal file
1
changes/33990-rhel-kernel-vulns
Normal file
|
|
@ -0,0 +1 @@
|
|||
- added ability to scan for kernel vulnerabilities on RHEL based hosts
|
||||
|
|
@ -119,7 +119,7 @@ const generateDefaultTableHeaders = (
|
|||
tipContent={
|
||||
<>
|
||||
Vulnerabilities on Linux are currently supported <br />
|
||||
for Ubuntu, Debian, and Amazon Linux.
|
||||
for Ubuntu, Debian, and RHEL based systems.
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3060,6 +3060,10 @@ func (ds *Datastore) ListSoftwareForVulnDetection(
|
|||
baseSQL += "JOIN host_software hs ON s.id = hs.software_id "
|
||||
}
|
||||
|
||||
if filters.KernelsOnly {
|
||||
baseSQL += "JOIN software_titles st ON s.title_id = st.id "
|
||||
}
|
||||
|
||||
conditions := []string{}
|
||||
|
||||
if filters.HostID != nil {
|
||||
|
|
@ -3077,6 +3081,10 @@ func (ds *Datastore) ListSoftwareForVulnDetection(
|
|||
args = append(args, filters.Source)
|
||||
}
|
||||
|
||||
if filters.KernelsOnly {
|
||||
conditions = append(conditions, "st.is_kernel = 1")
|
||||
}
|
||||
|
||||
if len(conditions) > 0 {
|
||||
sqlstmt = baseSQL + "WHERE " + strings.Join(conditions, " AND ")
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2804,6 +2804,36 @@ func testListSoftwareForVulnDetection(t *testing.T, ds *Datastore) {
|
|||
require.Equal(t, "baz", result[0].Name)
|
||||
require.Equal(t, "biz", result[1].Name)
|
||||
})
|
||||
|
||||
t.Run("KernelsOnly filter returns only kernel software", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
host := test.NewHost(t, ds, "host_kernel_test", "", "hostkernelkey", "hostkerneluuid", time.Now())
|
||||
host.Platform = "rhel"
|
||||
require.NoError(t, ds.UpdateHost(ctx, host))
|
||||
|
||||
software := []fleet.Software{
|
||||
{Name: "kernel", Version: "5.14.0-503.38.1.el9_5", Release: "", Arch: "x86_64", Source: "rpm_packages", IsKernel: true},
|
||||
{Name: "kernel-modules", Version: "5.14.0", Release: "503.38.1.el9_5", Arch: "x86_64", Source: "rpm_packages", IsKernel: false},
|
||||
{Name: "bash", Version: "5.1.8", Release: "6.el9_1", Arch: "x86_64", Source: "rpm_packages", IsKernel: false},
|
||||
{Name: "openssl", Version: "3.0.7", Release: "18.el9_2", Arch: "x86_64", Source: "rpm_packages", IsKernel: false},
|
||||
}
|
||||
_, err := ds.UpdateHostSoftware(ctx, host.ID, software)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test KernelsOnly filter
|
||||
filter := fleet.VulnSoftwareFilter{HostID: &host.ID, KernelsOnly: true}
|
||||
result, err := ds.ListSoftwareForVulnDetection(ctx, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, result, 1)
|
||||
require.Equal(t, "kernel", result[0].Name)
|
||||
|
||||
// Verify non-kernel filter returns all software
|
||||
filter = fleet.VulnSoftwareFilter{HostID: &host.ID, KernelsOnly: false}
|
||||
result, err = ds.ListSoftwareForVulnDetection(ctx, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, result, 4)
|
||||
})
|
||||
}
|
||||
|
||||
func testSoftwareByIDNoDuplicatedVulns(t *testing.T, ds *Datastore) {
|
||||
|
|
|
|||
|
|
@ -255,9 +255,10 @@ type VulnerableSoftware struct {
|
|||
}
|
||||
|
||||
type VulnSoftwareFilter struct {
|
||||
HostID *uint
|
||||
Name string // LIKE filter
|
||||
Source string // exact match
|
||||
HostID *uint
|
||||
Name string // LIKE filter
|
||||
Source string // exact match
|
||||
KernelsOnly bool // filter to kernel packages only (for RHEL goval-dictionary scanning)
|
||||
}
|
||||
|
||||
type SliceString []string
|
||||
|
|
|
|||
|
|
@ -2275,6 +2275,13 @@ func MutateSoftwareOnIngestion(s *fleet.Software, logger log.Logger) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For RHEL kernels, join version and release to match OVAL format.
|
||||
// See server/vulnerabilities/goval_dictionary/database.Eval
|
||||
if s != nil && s.Source == "rpm_packages" && s.Name == rpmKernelName && s.Release != "" {
|
||||
s.Version = fmt.Sprintf("%s-%s", s.Version, s.Release)
|
||||
s.Release = "" // Clear release to avoid issues with vulnerability matching
|
||||
}
|
||||
}
|
||||
|
||||
// shouldRemoveSoftware returns whether or not we should remove the given Software item from this
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ func Analyze(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// For kernel-only platforms (e.g., RHEL), we only scan kernel packages via goval-dictionary.
|
||||
// Non-kernel packages are scanned via regular OVAL processing. This keeps the testing
|
||||
// surface smaller. We can consider expanding scope to all packages in the future if needed.
|
||||
kernelsOnly := platform.IsGovalDictionaryKernelOnly()
|
||||
|
||||
// Since hosts and software have a M:N relationship, the following sets are used to
|
||||
// avoid doing duplicated inserts/delete operations (a vulnerable software might be
|
||||
// present in many hosts).
|
||||
|
|
@ -61,7 +66,10 @@ func Analyze(
|
|||
foundInBatch := make(map[uint][]fleet.SoftwareVulnerability)
|
||||
for _, hostID := range hostIDs {
|
||||
hostID := hostID
|
||||
software, err := ds.ListSoftwareForVulnDetection(ctx, fleet.VulnSoftwareFilter{HostID: &hostID})
|
||||
software, err := ds.ListSoftwareForVulnDetection(ctx, fleet.VulnSoftwareFilter{
|
||||
HostID: &hostID,
|
||||
KernelsOnly: kernelsOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,17 @@ var SupportedGovalPlatforms = []string{
|
|||
"amzn_02",
|
||||
"amzn_2022",
|
||||
"amzn_2023",
|
||||
"rhel_07",
|
||||
"rhel_08",
|
||||
"rhel_09",
|
||||
}
|
||||
|
||||
// GovalKernelOnlyPlatforms are platforms where goval-dictionary is used only for kernel vulnerability scanning.
|
||||
// These platforms use the regular OVAL scanning for non-kernel packages.
|
||||
var GovalKernelOnlyPlatforms = []string{
|
||||
"rhel_07",
|
||||
"rhel_08",
|
||||
"rhel_09",
|
||||
}
|
||||
|
||||
// getMajorMinorVer returns the major and minor version of an 'os_version'.
|
||||
|
|
@ -129,6 +140,17 @@ func (op Platform) IsGovalDictionarySupported() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// IsGovalDictionaryKernelOnly returns true if this platform uses goval-dictionary
|
||||
// only for kernel vulnerability scanning (non-kernel packages use regular OVAL).
|
||||
func (op Platform) IsGovalDictionaryKernelOnly() bool {
|
||||
for _, p := range GovalKernelOnlyPlatforms {
|
||||
if strings.HasPrefix(string(op), p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUbuntu checks whether the current Platform targets Ubuntu.
|
||||
func (op Platform) IsUbuntu() bool {
|
||||
return strings.HasPrefix(string(op), "ubuntu")
|
||||
|
|
|
|||
|
|
@ -104,4 +104,27 @@ func TestOvalPlatform(t *testing.T) {
|
|||
require.Equal(t, c.expected, plat.ToGovalDatabaseFilename())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("IsGovalDictionaryKernelOnly", func(t *testing.T) {
|
||||
cases := []struct {
|
||||
platform string
|
||||
osVersion string
|
||||
kernelOnly bool
|
||||
}{
|
||||
// Amazon Linux is NOT kernel-only (full goval-dictionary scanning)
|
||||
{"amzn", "Amazon Linux 1.0.0", false},
|
||||
{"amzn", "Amazon Linux 2.0.0", false},
|
||||
{"amzn", "Amazon Linux 2023.0.0", false},
|
||||
// RHEL is kernel-only (only kernel packages scanned via goval-dictionary)
|
||||
{"rhel", "CentOS Linux 7.9.2009", true},
|
||||
{"rhel", "CentOS Linux 8.3.2011", true},
|
||||
{"rhel", "Red Hat Enterprise Linux 9.0.0", true},
|
||||
// Ubuntu is not supported by goval-dictionary at all
|
||||
{"ubuntu", "Ubuntu 20.4.0", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
plat := NewPlatform(c.platform, c.osVersion)
|
||||
require.Equal(t, c.kernelOnly, plat.IsGovalDictionaryKernelOnly(), "platform=%s, osVersion=%s", c.platform, c.osVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue