use package identifiers not a package obj

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman 2024-06-21 12:04:48 -04:00
parent 27b4268022
commit 0f7c19811e
2 changed files with 156 additions and 59 deletions

View file

@ -19,12 +19,12 @@ func All() []any {
&DbSpecificNvd{}, &DbSpecificNvd{},
&Epss{}, &Epss{},
&KnownExploitedVulnerability{}, &KnownExploitedVulnerability{},
&LogicalPackage{}, //&LogicalPackage{},
&AffectedExcludeVersion{}, &AffectedExcludeVersion{},
&OperatingSystem{}, &OperatingSystem{},
&PackageQualifierPlatformCpe{}, &PackageQualifierPlatformCpe{},
&PackageQualifierRpmModularity{}, &PackageQualifierRpmModularity{},
&Package{}, //&Package{},
&Provider{}, &Provider{},
&RangeEvent{}, &RangeEvent{},
//&RangeEventMetadata{}, //&RangeEventMetadata{},
@ -177,16 +177,33 @@ type Affected struct {
ID int64 `gorm:"column:id;primaryKey"` ID int64 `gorm:"column:id;primaryKey"`
VulnerabilityID int64 `gorm:"column:vulnerability_id,not null"` VulnerabilityID int64 `gorm:"column:vulnerability_id,not null"`
PackageID *int64 `gorm:"column:package_id"` //PackageID *int64 `gorm:"column:package_id"`
Package *Package //Package *Package
OperatingSystemID *int64 `gorm:"column:operating_system_id"`
OperatingSystem *OperatingSystem `gorm:"foreignKey:OperatingSystemID"`
Versions *[]AffectedVersion `gorm:"foreignKey:AffectedID"` Versions *[]AffectedVersion `gorm:"foreignKey:AffectedID"`
ExcludeVersions *[]AffectedExcludeVersion `gorm:"foreignKey:AffectedID"` ExcludeVersions *[]AffectedExcludeVersion `gorm:"foreignKey:AffectedID"`
Severities *[]AffectedSeverity `gorm:"foreignKey:AffectedID"` Severities *[]AffectedSeverity `gorm:"foreignKey:AffectedID"`
Range *[]Range `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 that are known to correspond to this vulnerability, but cannot be closely associated with a package
Digests *[]Digest `gorm:"many2many:affected_digests"` 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 // TODO: add later and reuse existing similar tables with many2many
@ -320,44 +337,49 @@ type Package struct {
ID int64 `gorm:"column:id;primaryKey"` ID int64 `gorm:"column:id;primaryKey"`
// TODO: break purl out into fields here // TODO: break purl out into fields here
Ecosystem string `gorm:"column:ecosystem"` // TODO: NVD doesn't have this, should this be nullable? 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:package_name"` PackageName string `gorm:"column:package_name;index:idx_package,unique"`
OperatingSystemID *int64 `gorm:"column:operating_system_id"` //OperatingSystemID *int64 `gorm:"column:operating_system_id"`
OperatingSystem *OperatingSystem `gorm:"foreignKey:OperatingSystemID"` //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 { func (c *Package) BeforeCreate(tx *gorm.DB) (err error) {
ID int64 `gorm:"column:id;primaryKey"` // if the name, major version, and minor version already exist in the table then we should not insert a new record
var existing Package
Scheme string `gorm:"column:scheme"` result := tx.Where("package_name = ? AND ecosystem = ?", c.PackageName, c.Ecosystem).First(&existing)
Type string `gorm:"column:type"` if result.Error == nil {
Namespace *string `gorm:"column:namespace"` // if the record already exists, then we should use the existing record
Name string `gorm:"column:name"` *c = existing
Version string `gorm:"column:version"` }
SubPath *string `gorm:"column:subpath"` return nil
Qualifiers *[]Qualifier `gorm:"many2many:purl_qualifiers"`
} }
//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 // secondary package identifier information
//
type Qualifier struct { //type Qualifier struct {
ID int64 `gorm:"column:id;primaryKey"` // ID int64 `gorm:"column:id;primaryKey"`
//
Key string `gorm:"column:key"` // Key string `gorm:"column:key"`
Value string `gorm:"column:value"` // Value string `gorm:"column:value"`
} //}
type OperatingSystem struct { type OperatingSystem struct {
ID int64 `gorm:"column:id;primaryKey"` ID int64 `gorm:"column:id;primaryKey"`
@ -395,11 +417,11 @@ type PackageQualifierRpmModularity struct {
// logical package info // logical package info
type LogicalPackage struct { //type LogicalPackage struct {
ID int64 `gorm:"column:id;primaryKey"` // ID int64 `gorm:"column:id;primaryKey"`
//
Packages []Package `gorm:"many2many:logical_package_packages"` // Packages []Package `gorm:"many2many:logical_package_packages"`
} //}
// aux // aux

View file

@ -45,6 +45,7 @@ func (s *vulnerabilityStore) AddVulnerabilities(vulnerabilities ...*Vulnerabilit
s.handleOSs, s.handleOSs,
s.handleRangeEvents, s.handleRangeEvents,
s.handleCPEs, s.handleCPEs,
s.handlePackages,
} { } {
if err := h(vulnerabilities); err != nil { if err := h(vulnerabilities); err != nil {
return err return err
@ -55,20 +56,94 @@ func (s *vulnerabilityStore) AddVulnerabilities(vulnerabilities ...*Vulnerabilit
return s.db.CreateInBatches(vulnerabilities, s.BatchSize).Error 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 { func (s *vulnerabilityStore) handleCPEs(vulns []*Vulnerability) error {
// ensure unique operating systems // ensure unique cpes
unique, err := ensureUniqueCPEs(s.db, vulns) unique, err := ensureUniqueCPEs(s.db, vulns)
if err != nil { if err != nil {
return err return err
} }
// update vulnerabilities with operating system IDs // update vulnerabilities with cpes IDs
updateVulnerabilitiesWithCPEs(vulns, unique) updateAffectedsWithCPEs(vulns, unique)
return nil return nil
} }
func updateVulnerabilitiesWithCPEs(vulns []*Vulnerability, uniqueCPEs []*Cpe) { func updateAffectedsWithCPEs(vulns []*Vulnerability, uniqueCPEs []*Cpe) {
cpeMap := make(map[string]int64) cpeMap := make(map[string]int64)
for _, c := range uniqueCPEs { 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)) 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 continue
} }
for j, a := range *v.Affected { for j, a := range *v.Affected {
if a.Package.Cpes == nil { if a.Cpes == nil {
continue 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)) 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] 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 continue
} }
for _, a := range *v.Affected { for _, a := range *v.Affected {
if a.Package.Cpes == nil { if a.Cpes == nil {
continue 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)) 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 cpeMap[cpeKey] = c
} }
@ -138,7 +213,7 @@ func (s *vulnerabilityStore) handleRangeEvents(vulns []*Vulnerability) error {
} }
// update vulnerabilities with operating system IDs // update vulnerabilities with operating system IDs
updateVulnerabilitiesWithRanges(vulns, unique) updateAffectedsWithRanges(vulns, unique)
return nil return nil
} }
@ -184,7 +259,7 @@ func ensureUniqueRangeEvents(db *gorm.DB, vulns []*Vulnerability) ([]*RangeEvent
return uniqueRangeEvents, nil return uniqueRangeEvents, nil
} }
func updateVulnerabilitiesWithRanges(vulns []*Vulnerability, uniqueRangeEvents []*RangeEvent) { func updateAffectedsWithRanges(vulns []*Vulnerability, uniqueRangeEvents []*RangeEvent) {
reMap := make(map[string]int64) reMap := make(map[string]int64)
for _, re := range uniqueRangeEvents { 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) 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 // update vulnerabilities with operating system IDs
updateVulnerabilitiesWithOperatingSystems(vulns, uniqueOSList) updateAffectedsWithOperatingSystems(vulns, uniqueOSList)
return nil return nil
} }
@ -235,11 +310,11 @@ func ensureUniqueOperatingSystems(db *gorm.DB, vulns []*Vulnerability) ([]*Opera
osMap := make(map[string]*OperatingSystem) osMap := make(map[string]*OperatingSystem)
for _, v := range vulns { for _, v := range vulns {
for _, a := range *v.Affected { for _, a := range *v.Affected {
if a.Package.OperatingSystem == nil { if a.OperatingSystem == nil {
continue 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)
osMap[osKey] = a.Package.OperatingSystem osMap[osKey] = a.OperatingSystem
} }
} }
@ -263,7 +338,7 @@ func ensureUniqueOperatingSystems(db *gorm.DB, vulns []*Vulnerability) ([]*Opera
return uniqueOSList, nil return uniqueOSList, nil
} }
func updateVulnerabilitiesWithOperatingSystems(vulns []*Vulnerability, uniqueOSList []*OperatingSystem) { func updateAffectedsWithOperatingSystems(vulns []*Vulnerability, uniqueOSList []*OperatingSystem) {
osMap := make(map[string]int64) osMap := make(map[string]int64)
for _, os := range uniqueOSList { for _, os := range uniqueOSList {
osKey := fmt.Sprintf("%s:%s:%s", os.Name, os.MajorVersion, os.MinorVersion) 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 i, v := range vulns {
for j, a := range *v.Affected { for j, a := range *v.Affected {
if a.Package == nil || a.Package.OperatingSystem == nil { if a.OperatingSystem == nil {
continue 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] val := osMap[osKey]
(*vulns[i].Affected)[j].Package.OperatingSystemID = &val (*vulns[i].Affected)[j].OperatingSystemID = &val
} }
} }
} }