diff --git a/pkg/fanal/analyzer/os/release/release.go b/pkg/fanal/analyzer/os/release/release.go index f26fcd48ca..2f237f3303 100644 --- a/pkg/fanal/analyzer/os/release/release.go +++ b/pkg/fanal/analyzer/os/release/release.go @@ -27,8 +27,7 @@ var requiredFiles = []string{ type osReleaseAnalyzer struct{} func (a osReleaseAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { - var family types.OSType - var versionID string + var id, osName, versionID string scanner := bufio.NewScanner(input.Content) for scanner.Scan() { line := scanner.Text() @@ -40,26 +39,31 @@ func (a osReleaseAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInp key, value := strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1]) switch key { + case "NAME": + osName = strings.Trim(value, `"'`) case "ID": - id := strings.Trim(value, `"'`) - family = idToOSFamily(id) + id = strings.Trim(value, `"'`) case "VERSION_ID": versionID = strings.Trim(value, `"'`) default: continue } - - if family != "" && versionID != "" { - return &analyzer.AnalysisResult{ - OS: types.OS{ - Family: family, - Name: versionID, - }, - }, nil - } } - return nil, nil + // Determine OS family: ID is the primary source + family := idToOSFamily(id) + if family == "" || versionID == "" { + return nil, nil + } + // Refine OS family based on NAME field + family = refineOSFamily(family, osName) + return &analyzer.AnalysisResult{ + OS: types.OS{ + Family: family, + Name: versionID, + }, + }, nil + } //nolint:gocyclo @@ -113,6 +117,20 @@ func idToOSFamily(id string) types.OSType { return "" } +// refineOSFamily refines the OS family based on the NAME field. +// This handles exceptional cases where the ID field doesn't provide enough specificity, +// and we need to refine the detection using the NAME field. +func refineOSFamily(family types.OSType, name string) types.OSType { + // CentOS: Distinguish between regular CentOS and CentOS Stream + // ID is "centos" for both, but NAME differs + if family == types.CentOS && name == "CentOS Stream" { + return types.CentOSStream + } + + // Return family unchanged for normal cases + return family +} + func (a osReleaseAnalyzer) Required(filePath string, _ os.FileInfo) bool { return slices.Contains(requiredFiles, filePath) } diff --git a/pkg/fanal/analyzer/os/release/release_test.go b/pkg/fanal/analyzer/os/release/release_test.go index 006ffe4b87..c893d07e0f 100644 --- a/pkg/fanal/analyzer/os/release/release_test.go +++ b/pkg/fanal/analyzer/os/release/release_test.go @@ -49,6 +49,16 @@ func Test_osReleaseAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "CentOS Stream", + inputFile: "testdata/centos-stream", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.CentOSStream, + Name: "8", + }, + }, + }, { name: "Rocky Linux", inputFile: "testdata/rocky", diff --git a/pkg/fanal/analyzer/os/release/testdata/centos-stream b/pkg/fanal/analyzer/os/release/testdata/centos-stream new file mode 100644 index 0000000000..131b4b5eba --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/centos-stream @@ -0,0 +1,13 @@ +NAME="CentOS Stream" +VERSION="8" +ID="centos" +ID_LIKE="rhel fedora" +VERSION_ID="8" +PLATFORM_ID="platform:el8" +PRETTY_NAME="CentOS Stream 8" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:centos:centos:8" +HOME_URL="https://centos.org/" +BUG_REPORT_URL="https://bugzilla.redhat.com/" +REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux 8" +REDHAT_SUPPORT_PRODUCT_VERSION="CentOS Stream" \ No newline at end of file diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 1a673a639f..b52228508a 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -28,6 +28,7 @@ const ( Bottlerocket OSType = "bottlerocket" CBLMariner OSType = "cbl-mariner" CentOS OSType = "centos" + CentOSStream OSType = "centos-stream" Chainguard OSType = "chainguard" CoreOS OSType = "coreos" Debian OSType = "debian" @@ -114,6 +115,7 @@ var ( Azure, CBLMariner, CentOS, + CentOSStream, Chainguard, CoreOS, Debian,