Remove duplicate RHEL kernel in os_version (#39746)

This commit is contained in:
Tim Lee
2026-02-12 09:13:24 -07:00
committed by GitHub
parent bc5d0a9a1a
commit 548a2cc8e4
7 changed files with 144 additions and 9 deletions
+1
View File
@@ -0,0 +1 @@
- resolved issue where duplicate kernels were reported in the OS versions API for RHEL-family distributions (RHEL, AlmaLinux, CentOS, Rocky, Fedora)
@@ -0,0 +1,43 @@
package tables
import (
"database/sql"
"fmt"
)
func init() {
MigrationClient.AddMigration(Up_20260211200153, Down_20260211200153)
}
func Up_20260211200153(tx *sql.Tx) error {
// Previously, both "kernel" and "kernel-core" were marked as kernels for RPM-based distributions.
// However, on RHEL-family systems (RHEL, AlmaLinux, CentOS, Rocky, Fedora), both packages exist:
// - "kernel-core"
// - "kernel"
//
// To avoid showing duplicate kernels in the OS versions API, we now only mark "kernel" as the
// kernel package for all RPM-based distributions (including Amazon Linux and RHEL-family).
// This unmarks "kernel-core" as a kernel.
if _, err := tx.Exec(`
UPDATE software_titles
SET is_kernel = 0
WHERE name = 'kernel-core' AND source = 'rpm_packages'
`); err != nil {
return fmt.Errorf("failed to unmark kernel-core as kernel: %w", err)
}
// Clean up kernel_host_counts entries for kernel-core titles that are no longer kernels
if _, err := tx.Exec(`
DELETE khc FROM kernel_host_counts khc
JOIN software_titles st ON st.id = khc.software_title_id
WHERE st.name = 'kernel-core' AND st.source = 'rpm_packages'
`); err != nil {
return fmt.Errorf("failed to clean up kernel_host_counts for kernel-core: %w", err)
}
return nil
}
func Down_20260211200153(tx *sql.Tx) error {
return nil
}
@@ -0,0 +1,92 @@
package tables
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestUp_20260211200153(t *testing.T) {
db := applyUpToPrev(t)
// Insert software titles with is_kernel = 1 for various kernel packages
// kernel-core with rpm_packages source - should be unmarked as kernel
kernelCoreTitleID := execNoErrLastID(t, db, `
INSERT INTO software_titles (name, source, is_kernel)
VALUES ('kernel-core', 'rpm_packages', 1)
`)
// kernel with rpm_packages source - should remain as kernel
kernelTitleID := execNoErrLastID(t, db, `
INSERT INTO software_titles (name, source, is_kernel)
VALUES ('kernel', 'rpm_packages', 1)
`)
// Insert kernel_host_counts for each software title
_, err := db.Exec(`
INSERT INTO kernel_host_counts (software_title_id, software_id, os_version_id, hosts_count, team_id)
VALUES (?, 1, 100, 5, 0)
`, kernelCoreTitleID)
require.NoError(t, err)
_, err = db.Exec(`
INSERT INTO kernel_host_counts (software_title_id, software_id, os_version_id, hosts_count, team_id)
VALUES (?, 2, 100, 3, 0)
`, kernelTitleID)
require.NoError(t, err)
// Verify initial state
var initialCount int
err = db.Get(&initialCount, `SELECT COUNT(*) FROM kernel_host_counts`)
require.NoError(t, err)
require.Equal(t, 2, initialCount, "Should have 2 kernel_host_counts entries before migration")
// Apply migration
applyNext(t, db)
// Verify software_titles is_kernel values after migration
t.Run("kernel-core with rpm_packages is unmarked", func(t *testing.T) {
var isKernel int
err := db.Get(&isKernel, `SELECT is_kernel FROM software_titles WHERE id = ?`, kernelCoreTitleID)
require.NoError(t, err)
require.Equal(t, 0, isKernel, "kernel-core with rpm_packages should have is_kernel = 0")
})
t.Run("kernel with rpm_packages remains marked", func(t *testing.T) {
var isKernel int
err := db.Get(&isKernel, `SELECT is_kernel FROM software_titles WHERE id = ?`, kernelTitleID)
require.NoError(t, err)
require.Equal(t, 1, isKernel, "kernel with rpm_packages should still have is_kernel = 1")
})
// Verify kernel_host_counts cleanup
t.Run("kernel_host_counts for kernel-core rpm_packages deleted", func(t *testing.T) {
var count int
err := db.Get(&count, `
SELECT COUNT(*) FROM kernel_host_counts khc
JOIN software_titles st ON st.id = khc.software_title_id
WHERE st.name = 'kernel-core' AND st.source = 'rpm_packages'
`)
require.NoError(t, err)
require.Equal(t, 0, count, "kernel_host_counts for kernel-core/rpm_packages should be deleted")
})
t.Run("kernel_host_counts for kernel rpm_packages preserved", func(t *testing.T) {
var count int
err := db.Get(&count, `
SELECT COUNT(*) FROM kernel_host_counts khc
JOIN software_titles st ON st.id = khc.software_title_id
WHERE st.name = 'kernel' AND st.source = 'rpm_packages'
`)
require.NoError(t, err)
require.Equal(t, 1, count, "kernel_host_counts for kernel/rpm_packages should be preserved")
})
t.Run("total kernel_host_counts reduced by one", func(t *testing.T) {
var finalCount int
err := db.Get(&finalCount, `SELECT COUNT(*) FROM kernel_host_counts`)
require.NoError(t, err)
require.Equal(t, 1, finalCount, "Should have 1 kernel_host_counts entries after migration (one deleted)")
})
}
@@ -425,7 +425,7 @@ func testListKernelsByOS(t *testing.T, ds *Datastore) {
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}},
software: []fleet.Software{{Name: "kernel", 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"},
File diff suppressed because one or more lines are too long
@@ -65,7 +65,7 @@ func (s *integrationEnterpriseTestSuite) TestLinuxOSVulns() {
{
name: "RHEL",
host: test.NewHost(t, s.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}},
software: []fleet.Software{{Name: "kernel", 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"},
+4 -5
View File
@@ -2015,10 +2015,9 @@ func directIngestScheduledQueryStats(ctx context.Context, logger log.Logger, hos
}
const (
linuxImageRegex = `^linux-image-[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+-[[:digit:]]+-[[:alnum:]]+`
amazonLinuxKernelName = "kernel"
rhelKernelName = "kernel-core"
archKernelName = `^linux(?:-(?:lts|zen|hardened))?$`
linuxImageRegex = `^linux-image-[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+-[[:digit:]]+-[[:alnum:]]+`
rpmKernelName = "kernel"
archKernelName = `^linux(?:-(?:lts|zen|hardened))?$`
)
var (
@@ -2064,7 +2063,7 @@ func directIngestSoftware(ctx context.Context, logger log.Logger, host *fleet.Ho
continue
}
if fleet.IsLinux(host.Platform) && (kernelRegex.MatchString(s.Name) || s.Name == amazonLinuxKernelName || s.Name == rhelKernelName || archKernelRegex.MatchString(s.Name)) {
if fleet.IsLinux(host.Platform) && (kernelRegex.MatchString(s.Name) || s.Name == rpmKernelName || archKernelRegex.MatchString(s.Name)) {
s.IsKernel = true
}