diff --git a/grype/db/v6/affected_package_store.go b/grype/db/v6/affected_package_store.go index 67d54e58322..56a5888d11d 100644 --- a/grype/db/v6/affected_package_store.go +++ b/grype/db/v6/affected_package_store.go @@ -91,7 +91,7 @@ type OSSpecifiers []*OSSpecifier // OSSpecifier is a struct that represents a distro in a way that can be used to query the affected package store. type OSSpecifier struct { - // Name of the distro as identified by the ID field in /etc/os-release + // Name of the distro as identified by the ID field in /etc/os-release (or similar normalized name, e.g. "oracle" instead of "ol") Name string // MajorVersion is the first field in the VERSION_ID field in /etc/os-release (e.g. 7 in "7.0.1406") @@ -100,13 +100,9 @@ type OSSpecifier struct { // MinorVersion is the second field in the VERSION_ID field in /etc/os-release (e.g. 0 in "7.0.1406") MinorVersion string - // LabelVersion is mutually exclusive to MajorVersion and MinorVersion and tends to represent the - // VERSION_ID when it is not a version number (e.g. "edge" or "unstable") + // LabelVersion is a string that represents a floating version (e.g. "edge" or "unstable") or is the CODENAME field in /etc/os-release (e.g. "wheezy" for debian 7) LabelVersion string - // Codename is the CODENAME field in /etc/os-release (e.g. "wheezy" for debian 7) - Codename string - // AllowMultiple specifies whether we intend to allow for multiple distro identities to be matched. AllowMultiple bool } @@ -127,15 +123,15 @@ func (d *OSSpecifier) String() string { version += "." + d.MinorVersion } } else { - version = d.Codename + version = d.LabelVersion } distroDisplayName := d.Name if version != "" { distroDisplayName += "@" + version } - if version == d.MajorVersion && d.Codename != "" { - distroDisplayName += " (" + d.Codename + ")" + if version == d.MajorVersion && d.LabelVersion != "" { + distroDisplayName += " (" + d.LabelVersion + ")" } return distroDisplayName @@ -154,10 +150,6 @@ func (d OSSpecifier) version() string { return d.LabelVersion } - if d.Codename != "" { - return d.Codename - } - return "" } @@ -397,7 +389,7 @@ func (s *affectedPackageStore) handleOSOptions(query *gorm.DB, configs []*OSSpec } func (s *affectedPackageStore) resolveDistro(d OSSpecifier) ([]OperatingSystem, error) { - if d.Name == "" && d.Codename == "" { + if d.Name == "" && d.LabelVersion == "" { return nil, ErrMissingDistroIdentification } @@ -411,15 +403,11 @@ func (s *affectedPackageStore) resolveDistro(d OSSpecifier) ([]OperatingSystem, query := s.db.Model(&OperatingSystem{}) if d.Name != "" { - query = query.Where("name = ?", d.Name) - } - - if d.Codename != "" { - query = query.Where("codename = ?", d.Codename) + query = query.Where("name = ? OR release_id = ?", d.Name, d.Name) } if d.LabelVersion != "" { - query = query.Where("label_version = ?", d.LabelVersion) + query = query.Where("codename = ? OR label_version = ?", d.LabelVersion, d.LabelVersion) } return s.searchForDistroVersionVariants(query, d) @@ -496,7 +484,7 @@ func (s *affectedPackageStore) applyAlias(d *OSSpecifier) error { var alias *OperatingSystemAlias for _, a := range aliases { - if a.Codename != "" && a.Codename != d.Codename { + if a.Codename != "" && a.Codename != d.LabelVersion { continue } diff --git a/grype/db/v6/affected_package_store_test.go b/grype/db/v6/affected_package_store_test.go index ccd36a3f0c6..123768d9e3c 100644 --- a/grype/db/v6/affected_package_store_test.go +++ b/grype/db/v6/affected_package_store_test.go @@ -633,8 +633,8 @@ func TestAffectedPackageStore_GetAffectedPackages(t *testing.T) { pkg: pkgFromName(pkg2d1.Package.Name), options: &GetAffectedPackageOptions{ OSs: []*OSSpecifier{{ - Name: "ubuntu", - Codename: "groovy", + Name: "ubuntu", + LabelVersion: "groovy", }}, }, expected: []AffectedPackageHandle{*pkg2d2}, @@ -747,17 +747,19 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) { bs := newBlobStore(db) s := newAffectedPackageStore(db, bs) - ubuntu2004 := &OperatingSystem{Name: "ubuntu", MajorVersion: "20", MinorVersion: "04", Codename: "focal"} - ubuntu2010 := &OperatingSystem{Name: "ubuntu", MajorVersion: "20", MinorVersion: "10", Codename: "groovy"} - rhel8 := &OperatingSystem{Name: "rhel", MajorVersion: "8"} - rhel81 := &OperatingSystem{Name: "rhel", MajorVersion: "8", MinorVersion: "1"} - debian10 := &OperatingSystem{Name: "debian", MajorVersion: "10"} - alpine318 := &OperatingSystem{Name: "alpine", MajorVersion: "3", MinorVersion: "18"} - alpineEdge := &OperatingSystem{Name: "alpine", LabelVersion: "edge"} - debianTrixie := &OperatingSystem{Name: "debian", Codename: "trixie"} - debian7 := &OperatingSystem{Name: "debian", MajorVersion: "7", Codename: "wheezy"} - wolfi := &OperatingSystem{Name: "wolfi", MajorVersion: "20230201"} - arch := &OperatingSystem{Name: "arch", MajorVersion: "20241110", MinorVersion: "0"} + ubuntu2004 := &OperatingSystem{Name: "ubuntu", ReleaseID: "ubuntu", MajorVersion: "20", MinorVersion: "04", LabelVersion: "focal"} + ubuntu2010 := &OperatingSystem{Name: "ubuntu", MajorVersion: "20", MinorVersion: "10", LabelVersion: "groovy"} + rhel8 := &OperatingSystem{Name: "rhel", ReleaseID: "rhel", MajorVersion: "8"} + rhel81 := &OperatingSystem{Name: "rhel", ReleaseID: "rhel", MajorVersion: "8", MinorVersion: "1"} + debian10 := &OperatingSystem{Name: "debian", ReleaseID: "debian", MajorVersion: "10"} + alpine318 := &OperatingSystem{Name: "alpine", ReleaseID: "alpine", MajorVersion: "3", MinorVersion: "18"} + alpineEdge := &OperatingSystem{Name: "alpine", ReleaseID: "alpine", LabelVersion: "edge"} + debianTrixie := &OperatingSystem{Name: "debian", ReleaseID: "debian", LabelVersion: "trixie"} + debian7 := &OperatingSystem{Name: "debian", ReleaseID: "debian", MajorVersion: "7", LabelVersion: "wheezy"} + wolfi := &OperatingSystem{Name: "wolfi", ReleaseID: "wolfi", MajorVersion: "20230201"} + arch := &OperatingSystem{Name: "arch", ReleaseID: "arch", MajorVersion: "20241110", MinorVersion: "0"} + oracle5 := &OperatingSystem{Name: "oracle", ReleaseID: "ol", MajorVersion: "5"} + oracle6 := &OperatingSystem{Name: "oracle", ReleaseID: "ol", MajorVersion: "6"} operatingSystems := []*OperatingSystem{ ubuntu2004, @@ -771,6 +773,8 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) { debian7, wolfi, arch, + oracle5, + oracle6, } require.NoError(t, db.Create(&operatingSystems).Error) @@ -817,8 +821,8 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) { { name: "codename resolution", distro: OSSpecifier{ - Name: "ubuntu", - Codename: "focal", + Name: "ubuntu", + LabelVersion: "focal", }, expected: []OperatingSystem{*ubuntu2004}, }, @@ -828,7 +832,7 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) { Name: "ubuntu", MajorVersion: "20", MinorVersion: "04", - Codename: "focal", + LabelVersion: "focal", }, expected: []OperatingSystem{*ubuntu2004}, }, @@ -838,7 +842,7 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) { Name: "ubuntu", MajorVersion: "20", MinorVersion: "04", - Codename: "fake", + LabelVersion: "fake", }, }, { @@ -871,15 +875,15 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) { distro: OSSpecifier{ Name: "debian", MajorVersion: "13", - Codename: "trixie", // TODO: what about sid status indication from pretty-name or /etc/debian_version? + LabelVersion: "trixie", }, expected: []OperatingSystem{*debianTrixie}, }, { name: "debian by codename", distro: OSSpecifier{ - Name: "debian", - Codename: "wheezy", + Name: "debian", + LabelVersion: "wheezy", }, expected: []OperatingSystem{*debian7}, }, @@ -909,6 +913,14 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) { }, expected: []OperatingSystem{*alpine318}, }, + { + name: "lookup by release ID (not name)", + distro: OSSpecifier{ + Name: "ol", + MajorVersion: "5", + }, + expected: []OperatingSystem{*oracle5}, + }, { name: "missing distro name", distro: OSSpecifier{ @@ -988,15 +1000,15 @@ func TestDistroSpecifier_String(t *testing.T) { distro: &OSSpecifier{ Name: "ubuntu", MajorVersion: "20", - Codename: "focal", + LabelVersion: "focal", }, expected: "ubuntu@20 (focal)", }, { name: "name and codename specified", distro: &OSSpecifier{ - Name: "ubuntu", - Codename: "focal", + Name: "ubuntu", + LabelVersion: "focal", }, expected: "ubuntu@focal", }, @@ -1006,7 +1018,7 @@ func TestDistroSpecifier_String(t *testing.T) { Name: "ubuntu", MajorVersion: "20", MinorVersion: "04", - Codename: "focal", + LabelVersion: "focal", }, expected: "ubuntu@20.04", }, @@ -1041,7 +1053,7 @@ func testDistro1AffectedPackage2Handle() *AffectedPackageHandle { Name: "ubuntu", MajorVersion: "20", MinorVersion: "04", - Codename: "focal", + LabelVersion: "focal", }, BlobValue: &AffectedPackageBlob{ CVEs: []string{"CVE-2023-1234"}, @@ -1069,7 +1081,7 @@ func testDistro2AffectedPackage2Handle() *AffectedPackageHandle { Name: "ubuntu", MajorVersion: "20", MinorVersion: "10", - Codename: "groovy", + LabelVersion: "groovy", }, BlobValue: &AffectedPackageBlob{ CVEs: []string{"CVE-2023-4567"}, diff --git a/grype/db/v6/models.go b/grype/db/v6/models.go index f3bd357d806..485e1a9dfe8 100644 --- a/grype/db/v6/models.go +++ b/grype/db/v6/models.go @@ -278,17 +278,15 @@ func (p *Package) BeforeCreate(tx *gorm.DB) (err error) { type OperatingSystem struct { ID ID `gorm:"column:id;primaryKey"` - Name string `gorm:"column:name;index:os_idx,unique"` - MajorVersion string `gorm:"column:major_version;index:os_idx,unique"` - MinorVersion string `gorm:"column:minor_version;index:os_idx,unique"` - LabelVersion string `gorm:"column:label_version;index:os_idx,unique"` - Codename string `gorm:"column:codename"` // TODO: should this be removed and use label-version instead? + Name string `gorm:"column:name;index:os_idx,unique;index"` + ReleaseID string `gorm:"column:release_id;index:os_idx,unique;index"` + MajorVersion string `gorm:"column:major_version;index:os_idx,unique;index"` + MinorVersion string `gorm:"column:minor_version;index:os_idx,unique;index"` + LabelVersion string `gorm:"column:label_version;index:os_idx,unique;index"` + Codename string `gorm:"column:codename;index"` } func (os *OperatingSystem) BeforeCreate(tx *gorm.DB) (err error) { - if (os.MajorVersion != "" || os.MinorVersion != "") && os.LabelVersion != "" { - return fmt.Errorf("cannot have both label_version and major_version/minor_version set") - } // if the name, major version, and minor version already exist in the table then we should not insert a new record var existing OperatingSystem result := tx.Where("name = ? AND major_version = ? AND minor_version = ?", os.Name, os.MajorVersion, os.MinorVersion).First(&existing) @@ -303,10 +301,17 @@ type OperatingSystemAlias struct { // Name is the matching name as found in the ID field if the /etc/os-release file Name string `gorm:"column:name;primaryKey;index:os_alias_idx"` - // Version is the matching version as found in the ID field if the /etc/os-release file - Version string `gorm:"column:version;primaryKey"` - VersionPattern string `gorm:"column:version_pattern;primaryKey"` - Codename string `gorm:"column:codename"` + // Version is the matching version as found in the VERSION_ID field if the /etc/os-release file + Version string `gorm:"column:version;primaryKey"` + + // VersionPattern is a regex pattern to match against the VERSION_ID field if the /etc/os-release file + VersionPattern string `gorm:"column:version_pattern;primaryKey"` + + // Codename is the matching codename as found in the VERSION_CODENAME field if the /etc/os-release file + Codename string `gorm:"column:codename"` + + // below are the fields that should be used as replacement for fields in the OperatingSystem table + ReplacementName *string `gorm:"column:replacement;primaryKey"` ReplacementMajorVersion *string `gorm:"column:replacement_major_version;primaryKey"` ReplacementMinorVersion *string `gorm:"column:replacement_minor_version;primaryKey"` diff --git a/grype/db/v6/models_test.go b/grype/db/v6/models_test.go index 112f6c24d0b..0264911be38 100644 --- a/grype/db/v6/models_test.go +++ b/grype/db/v6/models_test.go @@ -7,73 +7,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestOperatingSystem_LabelVersionMutualExclusivity(t *testing.T) { - msg := "cannot have both label_version and major_version/minor_version set" - db := setupTestStore(t).db - - tests := []struct { - name string - input *OperatingSystem - errMsg string - }{ - { - name: "label version and major version are mutually exclusive", - input: &OperatingSystem{ - Name: "ubuntu", - MajorVersion: "20", - LabelVersion: "something", - }, - errMsg: msg, - }, - { - name: "label version and major.minor version are mutually exclusive", - input: &OperatingSystem{ - Name: "ubuntu", - MajorVersion: "20", - MinorVersion: "04", - LabelVersion: "something", - }, - errMsg: msg, - }, - { - name: "label version and minor version are mutually exclusive", - input: &OperatingSystem{ - Name: "ubuntu", - MinorVersion: "04", - LabelVersion: "something", - }, - errMsg: msg, - }, - { - name: "label version set", - input: &OperatingSystem{ - Name: "ubuntu", - LabelVersion: "something", - }, - }, - { - name: "major/minor version set", - input: &OperatingSystem{ - Name: "ubuntu", - MajorVersion: "20", - MinorVersion: "04", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := db.Create(tt.input).Error - if tt.errMsg == "" { - assert.NoError(t, err) - return - } - require.Error(t, err) - assert.Contains(t, err.Error(), tt.errMsg) - }) - } -} - func TestOperatingSystemAlias_VersionMutualExclusivity(t *testing.T) { db := setupTestStore(t).db