diff --git a/grype/db/v6/models.go b/grype/db/v6/models.go index f6d24f24..7630e9d2 100644 --- a/grype/db/v6/models.go +++ b/grype/db/v6/models.go @@ -19,12 +19,12 @@ func All() []any { &DbSpecificNvd{}, &Epss{}, &KnownExploitedVulnerability{}, - &LogicalPackage{}, + //&LogicalPackage{}, &AffectedExcludeVersion{}, &OperatingSystem{}, &PackageQualifierPlatformCpe{}, &PackageQualifierRpmModularity{}, - &Package{}, + //&Package{}, &Provider{}, &RangeEvent{}, //&RangeEventMetadata{}, @@ -177,16 +177,33 @@ type Affected struct { ID int64 `gorm:"column:id;primaryKey"` VulnerabilityID int64 `gorm:"column:vulnerability_id,not null"` - PackageID *int64 `gorm:"column:package_id"` - Package *Package + //PackageID *int64 `gorm:"column:package_id"` + //Package *Package + + OperatingSystemID *int64 `gorm:"column:operating_system_id"` + OperatingSystem *OperatingSystem `gorm:"foreignKey:OperatingSystemID"` Versions *[]AffectedVersion `gorm:"foreignKey:AffectedID"` ExcludeVersions *[]AffectedExcludeVersion `gorm:"foreignKey:AffectedID"` Severities *[]AffectedSeverity `gorm:"foreignKey:AffectedID"` Range *[]Range `gorm:"foreignKey:AffectedID"` + Packages *[]Package `gorm:"many2many:affected_packages"` + // Digests that are known to correspond to this vulnerability, but cannot be closely associated with a package Digests *[]Digest `gorm:"many2many:affected_digests"` + + //Purls *[]Purl `gorm:"many2many:package_purls"` + Cpes *[]Cpe `gorm:"many2many:affected_cpes"` + + //Purls *[]Purl `gorm:"many2many:affected_packages"` + + // Digests that are known to correspond to this package, either contained within, packaged for distribution, or normalized to a single file + //Digests *[]Digest `gorm:"many2many:package_digests"` + + // package qualifier info + PackageQualifierPlatformCpes *[]PackageQualifierPlatformCpe `gorm:"foreignKey:PackageID"` + PackageQualifierRpmModularities *[]PackageQualifierRpmModularity `gorm:"foreignKey:PackageID"` // TODO: shouldn't this be 1:1 (only a single module for a single package) } // TODO: add later and reuse existing similar tables with many2many @@ -320,44 +337,49 @@ type Package struct { ID int64 `gorm:"column:id;primaryKey"` // TODO: break purl out into fields here - Ecosystem string `gorm:"column:ecosystem"` // TODO: NVD doesn't have this, should this be nullable? - PackageName string `gorm:"column:package_name;index:package_name"` + Ecosystem *string `gorm:"column:ecosystem;index:idx_package,unique"` // TODO: NVD doesn't have this, should this be nullable? + PackageName string `gorm:"column:package_name;index:idx_package,unique"` - OperatingSystemID *int64 `gorm:"column:operating_system_id"` - OperatingSystem *OperatingSystem `gorm:"foreignKey:OperatingSystemID"` + //OperatingSystemID *int64 `gorm:"column:operating_system_id"` + //OperatingSystem *OperatingSystem `gorm:"foreignKey:OperatingSystemID"` + // + //Purls *[]Purl `gorm:"many2many:package_purls"` + //Cpes *[]Cpe `gorm:"many2many:package_cpes"` - Purls *[]Purl `gorm:"many2many:package_purls"` - Cpes *[]Cpe `gorm:"many2many:package_cpes"` - - // Digests that are known to correspond to this package, either contained within, packaged for distribution, or normalized to a single file - Digests *[]Digest `gorm:"many2many:package_digests"` - - // package qualifier info - PackageQualifierPlatformCpes *[]PackageQualifierPlatformCpe `gorm:"foreignKey:PackageID"` - PackageQualifierRpmModularities *[]PackageQualifierRpmModularity `gorm:"foreignKey:PackageID"` // TODO: shouldn't this be 1:1 (only a single module for a single package) } -type Purl struct { - ID int64 `gorm:"column:id;primaryKey"` - - Scheme string `gorm:"column:scheme"` - Type string `gorm:"column:type"` - Namespace *string `gorm:"column:namespace"` - Name string `gorm:"column:name"` - Version string `gorm:"column:version"` - SubPath *string `gorm:"column:subpath"` - - Qualifiers *[]Qualifier `gorm:"many2many:purl_qualifiers"` +func (c *Package) BeforeCreate(tx *gorm.DB) (err error) { + // if the name, major version, and minor version already exist in the table then we should not insert a new record + var existing Package + result := tx.Where("package_name = ? AND ecosystem = ?", c.PackageName, c.Ecosystem).First(&existing) + if result.Error == nil { + // if the record already exists, then we should use the existing record + *c = existing + } + return nil } +//type Purl struct { +// ID int64 `gorm:"column:id;primaryKey"` +// +// //Schema *string `gorm:"column:schema"` +// Type string `gorm:"column:type"` +// //Namespace *string `gorm:"column:namespace"` +// Name string `gorm:"column:name"` +// //Version *string `gorm:"column:version"` +// //SubPath *string `gorm:"column:subpath"` +// // +// //Qualifiers *[]Qualifier `gorm:"many2many:purl_qualifiers"` +//} + // secondary package identifier information - -type Qualifier struct { - ID int64 `gorm:"column:id;primaryKey"` - - Key string `gorm:"column:key"` - Value string `gorm:"column:value"` -} +// +//type Qualifier struct { +// ID int64 `gorm:"column:id;primaryKey"` +// +// Key string `gorm:"column:key"` +// Value string `gorm:"column:value"` +//} type OperatingSystem struct { ID int64 `gorm:"column:id;primaryKey"` @@ -395,11 +417,11 @@ type PackageQualifierRpmModularity struct { // logical package info -type LogicalPackage struct { - ID int64 `gorm:"column:id;primaryKey"` - - Packages []Package `gorm:"many2many:logical_package_packages"` -} +//type LogicalPackage struct { +// ID int64 `gorm:"column:id;primaryKey"` +// +// Packages []Package `gorm:"many2many:logical_package_packages"` +//} // aux diff --git a/grype/db/v6/vulnerability.go b/grype/db/v6/vulnerability.go index 62b3bb33..2c6e3710 100644 --- a/grype/db/v6/vulnerability.go +++ b/grype/db/v6/vulnerability.go @@ -45,6 +45,7 @@ func (s *vulnerabilityStore) AddVulnerabilities(vulnerabilities ...*Vulnerabilit s.handleOSs, s.handleRangeEvents, s.handleCPEs, + s.handlePackages, } { if err := h(vulnerabilities); err != nil { return err @@ -55,20 +56,94 @@ func (s *vulnerabilityStore) AddVulnerabilities(vulnerabilities ...*Vulnerabilit return s.db.CreateInBatches(vulnerabilities, s.BatchSize).Error } +func (s *vulnerabilityStore) handlePackages(vulns []*Vulnerability) error { + // ensure unique packages + unique, err := ensureUniquePackages(s.db, vulns) + if err != nil { + return err + } + + // update vulnerabilities with package IDs + updateAffectedsWithPackages(vulns, unique) + + return nil +} + +func ensureUniquePackages(db *gorm.DB, vulns []*Vulnerability) ([]*Package, error) { + // map to track unique packages + pkgMap := make(map[string]*Package) + for _, v := range vulns { + for _, a := range *v.Affected { + if a.Packages == nil { + continue + } + pkgs := *a.Packages + for i, p := range pkgs { + pkgKey := fmt.Sprintf("%s:%s", strVal(p.Ecosystem), p.PackageName) + val := &pkgs[i] + pkgMap[pkgKey] = val + } + } + } + + // extract unique packages + var uniquePackages []*Package + for _, pkg := range pkgMap { + uniquePackages = append(uniquePackages, pkg) + } + + // insert unique packages into the database or fetch existing ones + for i, pkg := range uniquePackages { + var existing Package + err := db.Where("package_name = ? AND ecosystem = ?", pkg.PackageName, pkg.Ecosystem). + FirstOrCreate(&existing, pkg).Error + if err != nil { + return nil, err + } + uniquePackages[i].ID = existing.ID + } + + return uniquePackages, nil + +} + +func updateAffectedsWithPackages(vulns []*Vulnerability, uniquePackages []*Package) { + pkgMap := make(map[string]int64) + for _, p := range uniquePackages { + pkgKey := fmt.Sprintf("%s:%s", strVal(p.Ecosystem), p.PackageName) + pkgMap[pkgKey] = p.ID + } + + for i, v := range vulns { + for j, a := range *v.Affected { + if a.Packages == nil { + continue + } + pkgs := *a.Packages + for k, p := range pkgs { + pkgKey := fmt.Sprintf("%s:%s", strVal(p.Ecosystem), p.PackageName) + val := pkgMap[pkgKey] + (*(*vulns[i].Affected)[j].Packages)[k].ID = val + } + } + } + +} + func (s *vulnerabilityStore) handleCPEs(vulns []*Vulnerability) error { - // ensure unique operating systems + // ensure unique cpes unique, err := ensureUniqueCPEs(s.db, vulns) if err != nil { return err } - // update vulnerabilities with operating system IDs - updateVulnerabilitiesWithCPEs(vulns, unique) + // update vulnerabilities with cpes IDs + updateAffectedsWithCPEs(vulns, unique) return nil } -func updateVulnerabilitiesWithCPEs(vulns []*Vulnerability, uniqueCPEs []*Cpe) { +func updateAffectedsWithCPEs(vulns []*Vulnerability, uniqueCPEs []*Cpe) { cpeMap := make(map[string]int64) for _, c := range uniqueCPEs { cpeKey := fmt.Sprintf("%s:%s:%s:%s:%s:%s:%s", c.Schema, c.Type, strVal(c.Vendor), c.Product, strVal(c.Version), strVal(c.Update), strVal(c.TargetSoftware)) @@ -80,13 +155,13 @@ func updateVulnerabilitiesWithCPEs(vulns []*Vulnerability, uniqueCPEs []*Cpe) { continue } for j, a := range *v.Affected { - if a.Package.Cpes == nil { + if a.Cpes == nil { continue } - for k, c := range *a.Package.Cpes { + for k, c := range *a.Cpes { cpeKey := fmt.Sprintf("%s:%s:%s:%s:%s:%s:%s", c.Schema, c.Type, strVal(c.Vendor), c.Product, strVal(c.Version), strVal(c.Update), strVal(c.TargetSoftware)) val := cpeMap[cpeKey] - (*(*vulns[i].Affected)[j].Package.Cpes)[k].ID = val + (*(*vulns[i].Affected)[j].Cpes)[k].ID = val } } } @@ -99,10 +174,10 @@ func ensureUniqueCPEs(db *gorm.DB, vulns []*Vulnerability) ([]*Cpe, error) { continue } for _, a := range *v.Affected { - if a.Package.Cpes == nil { + if a.Cpes == nil { continue } - for _, c := range *a.Package.Cpes { + for _, c := range *a.Cpes { cpeKey := fmt.Sprintf("%s:%s:%s:%s:%s:%s:%s", c.Schema, c.Type, strVal(c.Vendor), c.Product, strVal(c.Version), strVal(c.Update), strVal(c.TargetSoftware)) cpeMap[cpeKey] = c } @@ -138,7 +213,7 @@ func (s *vulnerabilityStore) handleRangeEvents(vulns []*Vulnerability) error { } // update vulnerabilities with operating system IDs - updateVulnerabilitiesWithRanges(vulns, unique) + updateAffectedsWithRanges(vulns, unique) return nil } @@ -184,7 +259,7 @@ func ensureUniqueRangeEvents(db *gorm.DB, vulns []*Vulnerability) ([]*RangeEvent return uniqueRangeEvents, nil } -func updateVulnerabilitiesWithRanges(vulns []*Vulnerability, uniqueRangeEvents []*RangeEvent) { +func updateAffectedsWithRanges(vulns []*Vulnerability, uniqueRangeEvents []*RangeEvent) { reMap := make(map[string]int64) for _, re := range uniqueRangeEvents { reKey := fmt.Sprintf("%s:%s:%s:%s:%s", strVal(re.Introduced), strVal(re.Fixed), strVal(re.LastAffected), strVal(re.Limit), re.State) @@ -225,7 +300,7 @@ func (s *vulnerabilityStore) handleOSs(vulns []*Vulnerability) error { } // update vulnerabilities with operating system IDs - updateVulnerabilitiesWithOperatingSystems(vulns, uniqueOSList) + updateAffectedsWithOperatingSystems(vulns, uniqueOSList) return nil } @@ -235,11 +310,11 @@ func ensureUniqueOperatingSystems(db *gorm.DB, vulns []*Vulnerability) ([]*Opera osMap := make(map[string]*OperatingSystem) for _, v := range vulns { for _, a := range *v.Affected { - if a.Package.OperatingSystem == nil { + if a.OperatingSystem == nil { continue } - osKey := fmt.Sprintf("%s:%s:%s", a.Package.OperatingSystem.Name, a.Package.OperatingSystem.MajorVersion, a.Package.OperatingSystem.MinorVersion) - osMap[osKey] = a.Package.OperatingSystem + osKey := fmt.Sprintf("%s:%s:%s", a.OperatingSystem.Name, a.OperatingSystem.MajorVersion, a.OperatingSystem.MinorVersion) + osMap[osKey] = a.OperatingSystem } } @@ -263,7 +338,7 @@ func ensureUniqueOperatingSystems(db *gorm.DB, vulns []*Vulnerability) ([]*Opera return uniqueOSList, nil } -func updateVulnerabilitiesWithOperatingSystems(vulns []*Vulnerability, uniqueOSList []*OperatingSystem) { +func updateAffectedsWithOperatingSystems(vulns []*Vulnerability, uniqueOSList []*OperatingSystem) { osMap := make(map[string]int64) for _, os := range uniqueOSList { osKey := fmt.Sprintf("%s:%s:%s", os.Name, os.MajorVersion, os.MinorVersion) @@ -272,12 +347,12 @@ func updateVulnerabilitiesWithOperatingSystems(vulns []*Vulnerability, uniqueOSL for i, v := range vulns { for j, a := range *v.Affected { - if a.Package == nil || a.Package.OperatingSystem == nil { + if a.OperatingSystem == nil { continue } - osKey := fmt.Sprintf("%s:%s:%s", a.Package.OperatingSystem.Name, a.Package.OperatingSystem.MajorVersion, a.Package.OperatingSystem.MinorVersion) + osKey := fmt.Sprintf("%s:%s:%s", a.OperatingSystem.Name, a.OperatingSystem.MajorVersion, a.OperatingSystem.MinorVersion) val := osMap[osKey] - (*vulns[i].Affected)[j].Package.OperatingSystemID = &val + (*vulns[i].Affected)[j].OperatingSystemID = &val } } }