diff --git a/pkg/process/v6/transformers/os/transform.go b/pkg/process/v6/transformers/os/transform.go index f86792f4..aba7a131 100644 --- a/pkg/process/v6/transformers/os/transform.go +++ b/pkg/process/v6/transformers/os/transform.go @@ -60,9 +60,8 @@ func getAffectedPackages(vuln unmarshal.OSVulnerability) []grypeDB.AffectedPacka } aph := grypeDB.AffectedPackageHandle{ - OperatingSystem: getOperatingSystem(group.osName, group.osVersion), + OperatingSystem: getOperatingSystem(group.osName, group.id, group.osVersion), Package: getPackage(group), - BlobValue: &grypeDB.AffectedPackageBlob{ CVEs: getAliases(vuln), Qualifiers: qualifiers, @@ -170,6 +169,7 @@ func deriveConstraintFromFix(fixVersion, vulnerabilityID string) string { type groupIndex struct { name string + id string osName string osVersion string module string @@ -177,7 +177,7 @@ type groupIndex struct { func groupFixedIns(vuln unmarshal.OSVulnerability) map[groupIndex][]unmarshal.OSFixedIn { grouped := make(map[groupIndex][]unmarshal.OSFixedIn) - osName, osVersion := getOSInfo(vuln.Vulnerability.NamespaceName) + osName, osID, osVersion := getOSInfo(vuln.Vulnerability.NamespaceName) for _, fixedIn := range vuln.Vulnerability.FixedIn { var mod string @@ -186,6 +186,7 @@ func groupFixedIns(vuln unmarshal.OSVulnerability) map[groupIndex][]unmarshal.OS } g := groupIndex{ name: fixedIn.Name, + id: osID, osName: osName, osVersion: osVersion, module: mod, @@ -218,42 +219,36 @@ func getPackage(group groupIndex) *grypeDB.Package { } } -func getOSInfo(group string) (string, string) { - // derived from enterprise feed groups, expected to be of the form {distroID}:{version} +func getOSInfo(group string) (string, string, string) { + // derived from enterprise feed groups, expected to be of the form {distro release ID}:{version} feedGroupComponents := strings.Split(group, ":") - return normalizeOsName(feedGroupComponents[0], feedGroupComponents[1]), feedGroupComponents[1] -} - -// add new fields to OS schema: release-id, release-version-id -// update vunnel providers to emit these fields (they are based on the /etc/os-release values) -// update this code to STOP parsing namespace and start using those new fields -// now when a user searches by OS (from the /etc/os-release values) they will get the correct results -// what's missing: -// - when to search by major version vs major.minor version... -// - edge/rolling behavior -// - aliases: user has centos 8, but the feed has rhel 8, use that instead -func normalizeOsName(name, version string) string { - if strings.ToLower(name) == "mariner" { + id := feedGroupComponents[0] + version := feedGroupComponents[1] + if strings.ToLower(id) == "mariner" { verFields := strings.Split(version, ".") majorVersionStr := verFields[0] majorVer, err := strconv.Atoi(majorVersionStr) if err == nil { if majorVer >= 3 { - name = string(distro.Azure) + id = string(distro.Azure) } } } - d, ok := distro.IDMapping[name] + + return normalizeOsName(id), id, version +} + +func normalizeOsName(id string) string { + d, ok := distro.IDMapping[id] if !ok { - log.WithFields("distro", name).Warn("unknown distro name") + log.WithFields("distro", id).Warn("unknown distro name") - return name + return id } distroName := d.String() - // TODO: this doesn't seem right switch d { case distro.OracleLinux: distroName = "oracle" @@ -263,7 +258,7 @@ func normalizeOsName(name, version string) string { return distroName } -func getOperatingSystem(osName, osVersion string) *grypeDB.OperatingSystem { +func getOperatingSystem(osName, osID, osVersion string) *grypeDB.OperatingSystem { if osName == "" || osVersion == "" { return nil } @@ -284,6 +279,7 @@ func getOperatingSystem(osName, osVersion string) *grypeDB.OperatingSystem { return &grypeDB.OperatingSystem{ Name: osName, + ReleaseID: osID, MajorVersion: majorVersion, MinorVersion: minorVersion, LabelVersion: labelVersion, diff --git a/pkg/process/v6/transformers/os/transform_test.go b/pkg/process/v6/transformers/os/transform_test.go index f496a64e..a6625dab 100644 --- a/pkg/process/v6/transformers/os/transform_test.go +++ b/pkg/process/v6/transformers/os/transform_test.go @@ -44,32 +44,45 @@ func expectedProvider(name string) *grypeDB.Provider { func TestTransform(t *testing.T) { + alpineOS := &grypeDB.OperatingSystem{ + Name: "alpine", + ReleaseID: "alpine", + MajorVersion: "3", + MinorVersion: "9", + } + amazonOS := &grypeDB.OperatingSystem{ Name: "amazon", + ReleaseID: "amzn", MajorVersion: "2", } azure3OS := &grypeDB.OperatingSystem{ Name: "azurelinux", + ReleaseID: "azurelinux", MajorVersion: "3", MinorVersion: "0", // TODO: is this right? } debian8OS := &grypeDB.OperatingSystem{ Name: "debian", + ReleaseID: "debian", MajorVersion: "8", Codename: "jessie", } mariner2OS := &grypeDB.OperatingSystem{ Name: "mariner", + ReleaseID: "mariner", MajorVersion: "2", MinorVersion: "0", // TODO: is this right? } ol8OS := &grypeDB.OperatingSystem{ Name: "oracle", + ReleaseID: "ol", MajorVersion: "8", } rhel8OS := &grypeDB.OperatingSystem{ Name: "redhat", + ReleaseID: "rhel", MajorVersion: "8", } tests := []struct { @@ -105,7 +118,7 @@ func TestTransform(t *testing.T) { }, Related: affectedPkgSlice( grypeDB.AffectedPackageHandle{ - OperatingSystem: &grypeDB.OperatingSystem{Name: "alpine", MajorVersion: "3", MinorVersion: "9"}, + OperatingSystem: alpineOS, Package: &grypeDB.Package{Type: "apk", Name: "xen"}, BlobValue: &grypeDB.AffectedPackageBlob{ Ranges: []grypeDB.AffectedRange{ @@ -1102,6 +1115,128 @@ func TestTransform(t *testing.T) { } } +func TestGetOperatingSystem(t *testing.T) { + tests := []struct { + name string + osName string + osID string + osVersion string + expected *grypeDB.OperatingSystem + }{ + { + name: "works with given args", + osName: "alpine", + osID: "alpine", + osVersion: "3.10", + expected: &grypeDB.OperatingSystem{ + Name: "alpine", + ReleaseID: "alpine", + MajorVersion: "3", + MinorVersion: "10", + LabelVersion: "", + Codename: "", + }, + }, + { + name: "does codename lookup (debian)", + osName: "debian", + osID: "debian", + osVersion: "11", + expected: &grypeDB.OperatingSystem{ + Name: "debian", + ReleaseID: "debian", + MajorVersion: "11", + MinorVersion: "", + LabelVersion: "", + Codename: "bullseye", + }, + }, + { + name: "does codename lookup (ubuntu)", + osName: "ubuntu", + osID: "ubuntu", + osVersion: "22.04", + expected: &grypeDB.OperatingSystem{ + Name: "ubuntu", + ReleaseID: "ubuntu", + MajorVersion: "22", + MinorVersion: "04", + LabelVersion: "", + Codename: "jammy", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getOperatingSystem(tt.osName, tt.osID, tt.osVersion) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestGetOSInfo(t *testing.T) { + tests := []struct { + name string + group string + expectedOS string + expectedID string + expectedV string + }{ + { + name: "alpine 3.10", + group: "alpine:3.10", + expectedOS: "alpine", + expectedID: "alpine", + expectedV: "3.10", + }, + { + name: "debian bullseye", + group: "debian:11", + expectedOS: "debian", + expectedID: "debian", + expectedV: "11", + }, + { + name: "mariner version 1", + group: "mariner:1.0", + expectedOS: "mariner", + expectedID: "mariner", + expectedV: "1.0", + }, + { + name: "mariner version 3 (azurelinux conversion)", + group: "mariner:3.0", + expectedOS: "azurelinux", + expectedID: "azurelinux", + expectedV: "3.0", + }, + { + name: "ubuntu focal", + group: "ubuntu:20.04", + expectedOS: "ubuntu", + expectedID: "ubuntu", + expectedV: "20.04", + }, + { + name: "oracle linux", + group: "ol:8", + expectedOS: "oracle", // normalize name + expectedID: "ol", // keep original ID + expectedV: "8", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osName, id, version := getOSInfo(tt.group) + require.Equal(t, tt.expectedOS, osName) + require.Equal(t, tt.expectedID, id) + require.Equal(t, tt.expectedV, version) + }) + } +} + func affectedPkgSlice(a ...grypeDB.AffectedPackageHandle) []any { var r []any for _, v := range a {