deduplicate range events

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman 2024-06-21 09:11:06 -04:00
parent 6933716613
commit 27b4268022
6 changed files with 360 additions and 80 deletions

12
grype/db/v6/blob.go Normal file
View file

@ -0,0 +1,12 @@
package v6
import (
"fmt"
"github.com/OneOfOne/xxhash"
)
func BlobDigest(content string) string {
h := xxhash.New64()
h.Write([]byte(content)) // TODO: handle error?
return fmt.Sprintf("xx64:%x", h.Sum(nil))
}

View file

@ -6,6 +6,10 @@ import (
"time"
)
func sp(s string) *string {
return &s
}
func populateFixture1(store v6.Store) error {
// Define providers
ghsaProvider := &v6.Provider{
@ -36,15 +40,15 @@ func populateFixture1(store v6.Store) error {
{
ProviderID: ghsaProvider.ID,
Name: "GHSA-xxxx-xxxx-xxxx1",
Modified: time.Now().UTC().Format(time.RFC3339),
Published: time.Now().UTC().Format(time.RFC3339),
SummaryDigest: "sha256:exampledigest1",
DetailDigest: "sha256:exampledetaildigest1",
Modified: sp(time.Now().UTC().Format(time.RFC3339)),
Published: sp(time.Now().UTC().Format(time.RFC3339)),
SummaryDigest: sp("sha256:exampledigest1"),
DetailDigest: sp("sha256:exampledetaildigest1"),
Aliases: &[]v6.Alias{
{Alias: "CVE-2024-12341"},
},
Severities: &[]v6.Severity{
{Type: "CVSS_V3", Score: "5.0", Source: "nvd@nist.gov", Priority: "secondary"},
{Type: "CVSS_V3", Score: "5.0", Source: sp("nvd@nist.gov"), Priority: sp("secondary")},
},
References: &[]v6.Reference{
{Type: "ADVISORY", URL: "https://example.com/GHSA-xxxx-xxxx-xxxx1"},
@ -72,15 +76,15 @@ func populateFixture1(store v6.Store) error {
{
ProviderID: ghsaProvider.ID,
Name: "GHSA-xxxx-xxxx-xxxx2",
Modified: time.Now().UTC().Format(time.RFC3339),
Published: time.Now().UTC().Format(time.RFC3339),
SummaryDigest: "sha256:exampledigest2",
DetailDigest: "sha256:exampledetaildigest2",
Modified: sp(time.Now().UTC().Format(time.RFC3339)),
Published: sp(time.Now().UTC().Format(time.RFC3339)),
SummaryDigest: sp("sha256:exampledigest2"),
DetailDigest: sp("sha256:exampledetaildigest2"),
Aliases: &[]v6.Alias{
{Alias: "CVE-2024-12342"},
},
Severities: &[]v6.Severity{
{Type: "CVSS_V3", Score: "7.5", Source: "nvd@nist.gov", Priority: "primary"},
{Type: "CVSS_V3", Score: "7.5", Source: sp("nvd@nist.gov"), Priority: sp("primary")},
},
References: &[]v6.Reference{
{Type: "ADVISORY", URL: "https://example.com/GHSA-xxxx-xxxx-xxxx2"},
@ -113,15 +117,15 @@ func populateFixture1(store v6.Store) error {
{
ProviderID: ubuntuProvider.ID,
Name: "CVE-2024-12343",
Modified: time.Now().UTC().Format(time.RFC3339),
Published: time.Now().UTC().Format(time.RFC3339),
SummaryDigest: "sha256:exampledigest3",
DetailDigest: "sha256:exampledetaildigest3",
Modified: sp(time.Now().UTC().Format(time.RFC3339)),
Published: sp(time.Now().UTC().Format(time.RFC3339)),
SummaryDigest: sp("sha256:exampledigest3"),
DetailDigest: sp("sha256:exampledetaildigest3"),
Aliases: &[]v6.Alias{
{Alias: "GHSA-xxxx-xxxx-xxxx3"},
},
Severities: &[]v6.Severity{
{Type: "CVSS_V3", Score: "6.0", Source: "nvd@nist.gov", Priority: "primary"},
{Type: "CVSS_V3", Score: "6.0", Source: sp("nvd@nist.gov"), Priority: sp("primary")},
},
References: &[]v6.Reference{
{Type: "ADVISORY", URL: "https://example.com/CVE-2024-12343"},
@ -157,15 +161,15 @@ func populateFixture1(store v6.Store) error {
vulnerabilities = append(vulnerabilities, &v6.Vulnerability{
ProviderID: ghsaProvider.ID,
Name: fmt.Sprintf("GHSA-xxxx-xxxx-xxxx%d", i+3),
Modified: time.Now().UTC().Format(time.RFC3339),
Published: time.Now().UTC().Format(time.RFC3339),
SummaryDigest: fmt.Sprintf("sha256:exampledigest%d", i+3),
DetailDigest: fmt.Sprintf("sha256:exampledetaildigest%d", i+3),
Modified: sp(time.Now().UTC().Format(time.RFC3339)),
Published: sp(time.Now().UTC().Format(time.RFC3339)),
SummaryDigest: sp(fmt.Sprintf("sha256:exampledigest%d", i+3)),
DetailDigest: sp(fmt.Sprintf("sha256:exampledetaildigest%d", i+3)),
Aliases: &[]v6.Alias{
{Alias: fmt.Sprintf("CVE-2024-1234%d", i+3)},
},
Severities: &[]v6.Severity{
{Type: "CVSS_V3", Score: "4.0", Source: "nvd@nist.gov", Priority: "primary"},
{Type: "CVSS_V3", Score: "4.0", Source: sp("nvd@nist.gov"), Priority: sp("primary")},
},
References: &[]v6.Reference{
{Type: "ADVISORY", URL: fmt.Sprintf("https://example.com/GHSA-xxxx-xxxx-xxxx%d", i+3)},

View file

@ -27,7 +27,7 @@ func All() []any {
&Package{},
&Provider{},
&RangeEvent{},
&RangeEventMetadata{},
//&RangeEventMetadata{},
&Range{},
&Reference{},
&Related{},
@ -53,19 +53,19 @@ type Vulnerability struct {
Name string `gorm:"column:name;not null;index;index:idx_vulnerability_provider"`
// Modified is the time the entry was last modified, as an RFC3339-formatted timestamp in UTC (ending in “Z”) (mirrors the OSV field)
Modified string `gorm:"column:modified"`
Modified *string `gorm:"column:modified"`
// Published is the time the entry should be considered to have been published, as an RFC3339-formatted time stamp in UTC (ending in “Z”) (mirrors the OSV field)
Published string `gorm:"column:published"`
Published *string `gorm:"column:published"`
// Withdrawn is the time the entry should be considered to have been withdrawn, as an RFC3339-formatted timestamp in UTC (ending in “Z”) (mirrors the OSV field)
Withdrawn string `gorm:"column:withdrawn"`
Withdrawn *string `gorm:"column:withdrawn"`
// SummaryDigest is a self describing hash (e.g. sha256:123... not 123...) of the summary field from the OSV summary field. This digest is searched against the Blob table/DB.
SummaryDigest string `gorm:"column:summary_digest"`
SummaryDigest *string `gorm:"column:summary_digest"`
// DetailDigest is a self describing hash (e.g. sha256:123... not 123...) of the detail field from the OSV summary field. This digest is searched against the Blob table/DB.
DetailDigest string `gorm:"column:detail_digest"`
DetailDigest *string `gorm:"column:detail_digest"`
// References are URLs to external resources that provide more information about the vulnerability (mirrors the OSV field)
References *[]Reference `gorm:"foreignKey:VulnerabilityID"`
@ -117,7 +117,7 @@ type Alias struct {
ID int64 `gorm:"column:id;primaryKey"`
//VulnerabilityID int64 `gorm:"column:vulnerability_id;not null"`
Alias string `gorm:"column:alias;not null,index:idx_alias"`
Alias string `gorm:"column:alias;not null,index:idx_alias,unique"`
}
// Related represents a single related vulnerability name
@ -126,7 +126,7 @@ type Related struct {
//VulnerabilityID int64 `gorm:"column:vulnerability_id;not null"`
// Name of the related vulnerability (e.g. CVE-2024-34102 or GHSA-85rg-8m6h-825p)
Name string `gorm:"column:name;not null,index:idx_related"`
Name string `gorm:"column:name;not null,index:idx_related,unique"`
//// Reason is a free-form text field that describes the relationship between the two vulnerabilities ("CVE-2022-12345 might be related to CVE-2022-54321 because both affect the same software library but are distinct issues")
//Reason string `gorm:"column:reason"`
@ -143,10 +143,10 @@ type Severity struct {
Score string `gorm:"column:score;not null"`
// Source is the name of the source of the severity score (e.g. "nvd@nist.gov" or "security-advisories@github.com")
Source string `gorm:"column:source"`
Source *string `gorm:"column:source"`
// Priority is a free-form organizational field to convey priority over other severities (e.g. primary vs secondary or authoritative vs unverified)
Priority string `gorm:"column:priority"` // TODO: naming is hard...
Priority *string `gorm:"column:priority"` // TODO: naming is hard...
}
type Reference struct {
@ -210,10 +210,10 @@ type AffectedSeverity struct {
ID int64 `gorm:"column:id;primaryKey"`
AffectedID int64 `gorm:"column:affected_id;not null"`
Type string `gorm:"column:type;not null"`
Score string `gorm:"column:score;not null"`
Source string `gorm:"column:source"`
Tag string `gorm:"column:tag"`
Type string `gorm:"column:type;not null"`
Score string `gorm:"column:score;not null"`
Source *string `gorm:"column:source"`
Priority *string `gorm:"column:priority"` // TODO: naming is hard...
}
type AffectedVersion struct {
@ -235,35 +235,48 @@ type Range struct {
AffectedID int64 `gorm:"column:affected_id;not null"`
Type string `gorm:"column:type;not null"`
Repo string `gorm:"column:repo"`
Events *[]RangeEvent `gorm:"foreignKey:RangeID"`
Repo *string `gorm:"column:repo"`
Events *[]RangeEvent `gorm:"many2many:range_range_events"`
}
type RangeEvent struct {
ID int64 `gorm:"primaryKey"`
RangeID int64 `gorm:"column:range_id;not null"`
ID int64 `gorm:"primaryKey"`
Introduced string `gorm:"column:introduced"`
Fixed string `gorm:"column:fixed"`
LastAffected string `gorm:"column:last_affected"`
Limit string `gorm:"column:limit"`
Introduced *string `gorm:"column:introduced;index:idx_range_event,unique"`
Fixed *string `gorm:"column:fixed;index:idx_range_event,unique"`
LastAffected *string `gorm:"column:last_affected;index:idx_range_event,unique"`
Limit *string `gorm:"column:range_limit;index:idx_range_event,unique"` // limit is a keyword in sql, so it's easier to just use range_limit instead
// non OSV...
State string `gorm:"column:state"` // TODO: this could be db specific since there will be multiple ways to represent/interpret this
State string `gorm:"column:state;index:idx_range_event,unique"` // TODO: this could be db specific since there will be multiple ways to represent/interpret this
RangeEventMetadata []RangeEventMetadata `gorm:"foreignKey:RangeEventID"`
// if deduplicating these, then this can't be associated
//RangeEventMetadata *[]RangeEventMetadata `gorm:"foreignKey:RangeEventID"`
}
type RangeEventMetadata struct {
ID int64 `gorm:"primaryKey"`
RangeEventID int64 `gorm:"column:range_event_id;not null"`
func (re *RangeEvent) BeforeCreate(tx *gorm.DB) (err error) {
//tx = tx.Session(&gorm.Session{Logger: loggerIgnoreRecordNotFound{tx.Logger}})
GitCommit string `gorm:"column:git_commit"`
PullRequestURL string `gorm:"column:pull_request_url"`
Timestamp string `gorm:"column:timestamp"`
// TODO: what else here?
// if the event already exist in the table then we should not insert a new record
var existing RangeEvent
result := tx.Where("introduced = ? AND fixed = ? AND last_affected = ? AND range_limit = ? AND state = ?", re.Introduced, re.Fixed, re.LastAffected, re.Limit, re.State).First(&existing)
if result.Error == nil {
// if the record already exists, then we should use the existing record
*re = existing
}
return nil
}
//type RangeEventMetadata struct {
// ID int64 `gorm:"primaryKey"`
// RangeEventID int64 `gorm:"column:range_event_id;not null"`
//
// GitCommit *string `gorm:"column:git_commit"`
// PullRequestURL *string `gorm:"column:pull_request_url"`
// Timestamp *string `gorm:"column:timestamp"`
// // TODO: what else here?
//}
// primary package identifiers (search entrypoints)
type Cpe struct {
@ -271,17 +284,28 @@ type Cpe struct {
ID int64 `gorm:"column:id;primaryKey"`
Schema string `gorm:"column:schema;not null"` // effectively the CPE version
Type string `gorm:"column:type;not null"`
Vendor string `gorm:"column:vendor"`
Product string `gorm:"column:product;not null"`
Version string `gorm:"column:version"`
Update string `gorm:"column:update"`
TargetSoftware string `gorm:"column:target_software"`
Schema string `gorm:"column:schema;not null;index:idx_cpe"` // effectively the CPE version
Type string `gorm:"column:type;not null;index:idx_cpe"`
Vendor *string `gorm:"column:vendor;index:idx_cpe"`
Product string `gorm:"column:product;not null;index:idx_cpe"`
Version *string `gorm:"column:version;index:idx_cpe"`
Update *string `gorm:"column:version_update;index:idx_cpe"` // update is a SQL keyword
TargetSoftware *string `gorm:"column:target_software;index:idx_cpe"`
// TODO: should we also have the remaining CPE fields here?
}
func (c *Cpe) 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 Cpe
result := tx.Where("schema = ? AND type = ? AND vendor = ? AND product = ? AND version = ? AND version_update = ? AND target_software = ?", c.Schema, c.Type, c.Vendor, c.Product, c.Version, c.Update, c.TargetSoftware).First(&existing)
if result.Error == nil {
// if the record already exists, then we should use the existing record
*c = existing
}
return nil
}
// Digest represents arbitrary digests that can be associated with a vulnerability such that if found the material can be considered to be affected by this vulnerability
type Digest struct {
ID int64 `gorm:"column:id;primaryKey"`
@ -296,7 +320,7 @@ type Package struct {
ID int64 `gorm:"column:id;primaryKey"`
// TODO: break purl out into fields here
Ecosystem string `gorm:"column:ecosystem"`
Ecosystem string `gorm:"column:ecosystem"` // TODO: NVD doesn't have this, should this be nullable?
PackageName string `gorm:"column:package_name;index:package_name"`
OperatingSystemID *int64 `gorm:"column:operating_system_id"`
@ -316,12 +340,12 @@ type Package struct {
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"`
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"`
}
@ -346,11 +370,11 @@ type OperatingSystem struct {
func (os *OperatingSystem) 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 existingOs OperatingSystem
result := tx.Where("name = ? AND major_version = ? AND minor_version = ?", os.Name, os.MajorVersion, os.MinorVersion).First(&existingOs)
var existing OperatingSystem
result := tx.Where("name = ? AND major_version = ? AND minor_version = ?", os.Name, os.MajorVersion, os.MinorVersion).First(&existing)
if result.Error == nil {
// if the record already exists, then we should use the existing record
*os = existingOs
*os = existing
}
return nil
}

View file

@ -4,22 +4,64 @@ import (
"fmt"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"reflect"
"sync"
"time"
)
type state struct {
db *gorm.DB
preloadCache *preloadCache
destination string
write bool
useMem bool
}
//type loggerIgnoreRecordNotFound struct {
// logger.Interface
//}
//
//func (c loggerIgnoreRecordNotFound) Error(ctx context.Context, msg string, data ...interface{}) {
// if msg == "record not found" {
// // Suppress "record not found" messages
// return
// }
// c.Interface.Error(ctx, msg, data...)
//}
//
//func (c loggerIgnoreRecordNotFound) Warn(ctx context.Context, msg string, data ...interface{}) {
// if msg == "record not found" {
// // Suppress "record not found" messages
// return
// }
// c.Interface.Warn(ctx, msg, data...)
//}
func newState(dbFilePath string, overwrite bool) (*state, error) {
//db, err := gormadapter.Open(dbFilePath, overwrite)
//if err != nil {
// return nil, err
//}
db, err := gorm.Open(sqlite.Open(dbFilePath), &gorm.Config{})
lgr := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Warn,
IgnoreRecordNotFoundError: true,
Colorful: true,
})
useMem := true
location := dbFilePath
if useMem {
location = "file::memory:?cache=shared"
}
db, err := gorm.Open(sqlite.Open(location), &gorm.Config{
Logger: lgr,
})
if err != nil {
fmt.Println("Failed to connect to database:", err)
return nil, err
@ -31,21 +73,34 @@ func newState(dbFilePath string, overwrite bool) (*state, error) {
if err := db.AutoMigrate(All()...); err != nil {
return nil, fmt.Errorf("unable to migrate: %w", err)
}
} else {
// optimize read-only access
db.Exec("PRAGMA synchronous = OFF")
db.Exec("PRAGMA journal_mode = OFF")
db.Exec("PRAGMA temp_store = MEMORY")
db.Exec("PRAGMA cache_size = 100000")
db.Exec("PRAGMA mmap_size = 268435456") // 256 MB
}
db.Exec("PRAGMA synchronous = OFF")
db.Exec("PRAGMA journal_mode = OFF")
db.Exec("PRAGMA temp_store = MEMORY")
db.Exec("PRAGMA cache_size = 100000")
db.Exec("PRAGMA mmap_size = 268435456") // 256 MB
db.Exec("PRAGMA auto_vacuum = NONE")
return &state{
db: db,
preloadCache: newPreloadCache(),
destination: dbFilePath,
write: overwrite,
useMem: useMem,
}, nil
}
func (s *state) close() error {
if s.write {
if s.useMem {
return s.db.Exec(fmt.Sprintf("VACUUM main into %q", s.destination)).Error
}
return s.db.Exec("VACUUM").Error
}
return nil
}
func (s *state) getPreloadableFields(model interface{}) []string {
t := reflect.TypeOf(model).Elem()

View file

@ -1,6 +1,8 @@
package v6
import (
"fmt"
"github.com/anchore/grype/internal/log"
"io"
"path/filepath"
)
@ -55,6 +57,7 @@ func (c *StoreConfig) DBFilePath() string {
// New creates a new instance of the Store.
func New(cfg StoreConfig) (Store, error) {
return &store{
cfg: &cfg,
ProviderStore: NewProviderStore(&cfg),
OperatingSystemStore: NewOperatingSystemStore(&cfg),
AffectedStore: NewAffectedStore(&cfg),
@ -64,5 +67,7 @@ func New(cfg StoreConfig) (Store, error) {
}
func (s store) Close() error {
return nil
log.Debug("closing store")
st := s.cfg.state()
return st.db.Exec(fmt.Sprintf("VACUUM main into %q", st.destination)).Error
}

View file

@ -41,16 +41,193 @@ func (s *vulnerabilityStore) GetVulnerability(id string, loadAuxInfo bool) ([]Vu
}
func (s *vulnerabilityStore) AddVulnerabilities(vulnerabilities ...*Vulnerability) error {
for _, h := range []func([]*Vulnerability) error{
s.handleOSs,
s.handleRangeEvents,
s.handleCPEs,
} {
if err := h(vulnerabilities); err != nil {
return err
}
}
return s.db.CreateInBatches(vulnerabilities, s.BatchSize).Error
}
func (s *vulnerabilityStore) handleCPEs(vulns []*Vulnerability) error {
// ensure unique operating systems
uniqueOSList, err := ensureUniqueOperatingSystems(s.db, vulnerabilities)
unique, err := ensureUniqueCPEs(s.db, vulns)
if err != nil {
return err
}
// update vulnerabilities with operating system IDs
updateVulnerabilitiesWithOperatingSystems(vulnerabilities, uniqueOSList)
updateVulnerabilitiesWithCPEs(vulns, unique)
return s.db.CreateInBatches(vulnerabilities, 100).Error
return nil
}
func updateVulnerabilitiesWithCPEs(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))
cpeMap[cpeKey] = c.ID
}
for i, v := range vulns {
if v.Affected == nil {
continue
}
for j, a := range *v.Affected {
if a.Package.Cpes == nil {
continue
}
for k, c := range *a.Package.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
}
}
}
}
func ensureUniqueCPEs(db *gorm.DB, vulns []*Vulnerability) ([]*Cpe, error) {
cpeMap := make(map[string]Cpe)
for _, v := range vulns {
if v.Affected == nil {
continue
}
for _, a := range *v.Affected {
if a.Package.Cpes == nil {
continue
}
for _, c := range *a.Package.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
}
}
}
// extract unique CPEs
var uniqueCPEs []*Cpe
for i := range cpeMap {
c := cpeMap[i]
uniqueCPEs = append(uniqueCPEs, &c)
}
// insert unique CPEs into the database or fetch existing ones
for i, c := range uniqueCPEs {
var existing Cpe
err := db.Where("schema = ? AND type = ? AND vendor = ? AND product = ? AND version = ? AND version_update = ? AND target_software = ?", c.Schema, c.Type, c.Vendor, c.Product, c.Version, c.Update, c.TargetSoftware).
FirstOrCreate(&existing, c).Error
if err != nil {
return nil, err
}
uniqueCPEs[i] = &existing
}
return uniqueCPEs, nil
}
func (s *vulnerabilityStore) handleRangeEvents(vulns []*Vulnerability) error {
// ensure unique operating systems
unique, err := ensureUniqueRangeEvents(s.db, vulns)
if err != nil {
return err
}
// update vulnerabilities with operating system IDs
updateVulnerabilitiesWithRanges(vulns, unique)
return nil
}
func ensureUniqueRangeEvents(db *gorm.DB, vulns []*Vulnerability) ([]*RangeEvent, error) {
reMap := make(map[string]RangeEvent)
for _, v := range vulns {
for _, a := range *v.Affected {
if a.Range == nil {
continue
}
for _, r := range *a.Range {
if r.Events == nil {
continue
}
for _, re := range *r.Events {
reKey := fmt.Sprintf("%s:%s:%s:%s:%s", strVal(re.Introduced), strVal(re.Fixed), strVal(re.LastAffected), strVal(re.Limit), re.State)
reMap[reKey] = re
}
}
}
}
// extract unique range events
var uniqueRangeEvents []*RangeEvent
for i := range reMap {
re := reMap[i]
uniqueRangeEvents = append(uniqueRangeEvents, &re)
}
// insert unique range events into the database or fetch existing ones
for i, re := range uniqueRangeEvents {
var existing RangeEvent
err := db.Where("introduced = ? AND fixed = ? AND last_affected = ? AND range_limit = ? AND state = ?", re.Introduced, re.Fixed, re.LastAffected, re.Limit, re.State).
FirstOrCreate(&existing, re).Error
if err != nil {
return nil, err
}
uniqueRangeEvents[i] = &existing
}
return uniqueRangeEvents, nil
}
func updateVulnerabilitiesWithRanges(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)
reMap[reKey] = re.ID
}
for i, v := range vulns {
for j, a := range *v.Affected {
if a.Range == nil {
continue
}
for k, r := range *a.Range {
if r.Events == nil {
continue
}
for l, re := range *r.Events {
reKey := fmt.Sprintf("%s:%s:%s:%s:%s", strVal(re.Introduced), strVal(re.Fixed), strVal(re.LastAffected), strVal(re.Limit), re.State)
val := reMap[reKey]
(*(*(*vulns[i].Affected)[j].Range)[k].Events)[l].ID = val
}
}
}
}
}
func strVal(s *string) string {
if s == nil {
return ""
}
return *s
}
func (s *vulnerabilityStore) handleOSs(vulns []*Vulnerability) error {
// ensure unique operating systems
uniqueOSList, err := ensureUniqueOperatingSystems(s.db, vulns)
if err != nil {
return err
}
// update vulnerabilities with operating system IDs
updateVulnerabilitiesWithOperatingSystems(vulns, uniqueOSList)
return nil
}
func ensureUniqueOperatingSystems(db *gorm.DB, vulns []*Vulnerability) ([]*OperatingSystem, error) {
@ -95,6 +272,9 @@ func updateVulnerabilitiesWithOperatingSystems(vulns []*Vulnerability, uniqueOSL
for i, v := range vulns {
for j, a := range *v.Affected {
if a.Package == nil || a.Package.OperatingSystem == nil {
continue
}
osKey := fmt.Sprintf("%s:%s:%s", a.Package.OperatingSystem.Name, a.Package.OperatingSystem.MajorVersion, a.Package.OperatingSystem.MinorVersion)
val := osMap[osKey]
(*vulns[i].Affected)[j].Package.OperatingSystemID = &val