diff --git a/grype/db/v1/id.go b/grype/db/v1/id.go deleted file mode 100644 index 297d73c1303..00000000000 --- a/grype/db/v1/id.go +++ /dev/null @@ -1,28 +0,0 @@ -package v1 - -import ( - "time" -) - -// ID represents identifying information for a DB and the data it contains. -type ID struct { - // BuildTimestamp is the timestamp used to define the age of the DB, ideally including the age of the data - // contained in the DB, not just when the DB file was created. - BuildTimestamp time.Time - SchemaVersion int -} - -type IDReader interface { - GetID() (*ID, error) -} - -type IDWriter interface { - SetID(ID) error -} - -func NewID(age time.Time) ID { - return ID{ - BuildTimestamp: age.UTC(), - SchemaVersion: SchemaVersion, - } -} diff --git a/grype/db/v1/namespace.go b/grype/db/v1/namespace.go deleted file mode 100644 index 780cbcfdf50..00000000000 --- a/grype/db/v1/namespace.go +++ /dev/null @@ -1,30 +0,0 @@ -package v1 - -import ( - "fmt" -) - -const ( - NVDNamespace = "nvd" -) - -func RecordSource(feed, group string) string { - switch feed { - case "github", "nvdv2": - return group - default: - return fmt.Sprintf("%s:%s", feed, group) - } -} - -func NamespaceForFeedGroup(feed, group string) (string, error) { - switch { - case feed == "vulnerabilities": - return group, nil - case feed == "github": - return group, nil - case feed == "nvdv2" && group == "nvdv2:cves": - return NVDNamespace, nil - } - return "", fmt.Errorf("feed=%q group=%q has no namespace mappings", feed, group) -} diff --git a/grype/db/v1/namespace_test.go b/grype/db/v1/namespace_test.go deleted file mode 100644 index 10dc9845437..00000000000 --- a/grype/db/v1/namespace_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package v1 - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNamespaceFromRecordSource(t *testing.T) { - tests := []struct { - Feed, Group string - Namespace string - }{ - { - Feed: "vulnerabilities", - Group: "ubuntu:20.04", - Namespace: "ubuntu:20.04", - }, - { - Feed: "vulnerabilities", - Group: "alpine:3.9", - Namespace: "alpine:3.9", - }, - { - Feed: "vulnerabilities", - Group: "sles:12.5", - Namespace: "sles:12.5", - }, - { - Feed: "nvdv2", - Group: "nvdv2:cves", - Namespace: "nvd", - }, - { - Feed: "github", - Group: "github:python", - Namespace: "github:python", - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("feed=%q group=%q namespace=%q", test.Feed, test.Group, test.Namespace), func(t *testing.T) { - actual, err := NamespaceForFeedGroup(test.Feed, test.Group) - assert.NoError(t, err) - assert.Equal(t, test.Namespace, actual) - }) - } -} diff --git a/grype/db/v1/schema_version.go b/grype/db/v1/schema_version.go deleted file mode 100644 index f72f10ceae8..00000000000 --- a/grype/db/v1/schema_version.go +++ /dev/null @@ -1,3 +0,0 @@ -package v1 - -const SchemaVersion = 1 diff --git a/grype/db/v1/store.go b/grype/db/v1/store.go deleted file mode 100644 index 97b6f8bf683..00000000000 --- a/grype/db/v1/store.go +++ /dev/null @@ -1,19 +0,0 @@ -package v1 - -type Store interface { - StoreReader - StoreWriter -} - -type StoreReader interface { - IDReader - VulnerabilityStoreReader - VulnerabilityMetadataStoreReader -} - -type StoreWriter interface { - IDWriter - VulnerabilityStoreWriter - VulnerabilityMetadataStoreWriter - Close() -} diff --git a/grype/db/v1/store/model/id.go b/grype/db/v1/store/model/id.go deleted file mode 100644 index 51befc235c9..00000000000 --- a/grype/db/v1/store/model/id.go +++ /dev/null @@ -1,40 +0,0 @@ -package model - -import ( - "fmt" - "time" - - v1 "github.com/anchore/grype/grype/db/v1" -) - -const ( - IDTableName = "id" -) - -type IDModel struct { - BuildTimestamp string `gorm:"column:build_timestamp"` - SchemaVersion int `gorm:"column:schema_version"` -} - -func NewIDModel(id v1.ID) IDModel { - return IDModel{ - BuildTimestamp: id.BuildTimestamp.Format(time.RFC3339Nano), - SchemaVersion: id.SchemaVersion, - } -} - -func (IDModel) TableName() string { - return IDTableName -} - -func (m *IDModel) Inflate() (v1.ID, error) { - buildTime, err := time.Parse(time.RFC3339Nano, m.BuildTimestamp) - if err != nil { - return v1.ID{}, fmt.Errorf("unable to parse build timestamp (%+v): %w", m.BuildTimestamp, err) - } - - return v1.ID{ - BuildTimestamp: buildTime, - SchemaVersion: m.SchemaVersion, - }, nil -} diff --git a/grype/db/v1/store/model/vulnerability.go b/grype/db/v1/store/model/vulnerability.go deleted file mode 100644 index c52e4c274d3..00000000000 --- a/grype/db/v1/store/model/vulnerability.go +++ /dev/null @@ -1,86 +0,0 @@ -package model - -import ( - "encoding/json" - "fmt" - - v1 "github.com/anchore/grype/grype/db/v1" -) - -const ( - VulnerabilityTableName = "vulnerability" - GetVulnerabilityIndexName = "get_vulnerability_index" -) - -// VulnerabilityModel is a struct used to serialize db.Vulnerability information into a sqlite3 DB. -type VulnerabilityModel struct { - PK uint64 `gorm:"primary_key;auto_increment;"` - ID string `gorm:"column:id"` - RecordSource string `gorm:"column:record_source"` - PackageName string `gorm:"column:package_name; index:get_vulnerability_index"` - Namespace string `gorm:"column:namespace; index:get_vulnerability_index"` - VersionConstraint string `gorm:"column:version_constraint"` - VersionFormat string `gorm:"column:version_format"` - CPEs string `gorm:"column:cpes"` - ProxyVulnerabilities string `gorm:"column:proxy_vulnerabilities"` - FixedInVersion string `gorm:"column:fixed_in_version"` -} - -// NewVulnerabilityModel generates a new model from a db.Vulnerability struct. -func NewVulnerabilityModel(vulnerability v1.Vulnerability) VulnerabilityModel { - cpes, err := json.Marshal(vulnerability.CPEs) - if err != nil { - // TODO: just no - panic(err) - } - - proxy, err := json.Marshal(vulnerability.ProxyVulnerabilities) - if err != nil { - // TODO: just no - panic(err) - } - - return VulnerabilityModel{ - ID: vulnerability.ID, - PackageName: vulnerability.PackageName, - RecordSource: vulnerability.RecordSource, - Namespace: vulnerability.Namespace, - VersionConstraint: vulnerability.VersionConstraint, - VersionFormat: vulnerability.VersionFormat, - FixedInVersion: vulnerability.FixedInVersion, - CPEs: string(cpes), - ProxyVulnerabilities: string(proxy), - } -} - -// TableName returns the table which all db.Vulnerability model instances are stored into. -func (VulnerabilityModel) TableName() string { - return VulnerabilityTableName -} - -// Inflate generates a db.Vulnerability object from the serialized model instance. -func (m *VulnerabilityModel) Inflate() (v1.Vulnerability, error) { - var cpes []string - err := json.Unmarshal([]byte(m.CPEs), &cpes) - if err != nil { - return v1.Vulnerability{}, fmt.Errorf("unable to unmarshal CPEs (%+v): %w", m.CPEs, err) - } - - var proxy []string - err = json.Unmarshal([]byte(m.ProxyVulnerabilities), &proxy) - if err != nil { - return v1.Vulnerability{}, fmt.Errorf("unable to unmarshal proxy vulnerabilities (%+v): %w", m.ProxyVulnerabilities, err) - } - - return v1.Vulnerability{ - ID: m.ID, - RecordSource: m.RecordSource, - PackageName: m.PackageName, - Namespace: m.Namespace, - VersionConstraint: m.VersionConstraint, - VersionFormat: m.VersionFormat, - CPEs: cpes, - ProxyVulnerabilities: proxy, - FixedInVersion: m.FixedInVersion, - }, nil -} diff --git a/grype/db/v1/store/model/vulnerability_metadata.go b/grype/db/v1/store/model/vulnerability_metadata.go deleted file mode 100644 index 4c2bbba0bb9..00000000000 --- a/grype/db/v1/store/model/vulnerability_metadata.go +++ /dev/null @@ -1,104 +0,0 @@ -package model - -import ( - "database/sql" - "encoding/json" - "fmt" - - v1 "github.com/anchore/grype/grype/db/v1" -) - -const ( - VulnerabilityMetadataTableName = "vulnerability_metadata" -) - -// VulnerabilityMetadataModel is a struct used to serialize db.VulnerabilityMetadata information into a sqlite3 DB. -type VulnerabilityMetadataModel struct { - ID string `gorm:"primary_key; column:id;"` - RecordSource string `gorm:"primary_key; column:record_source;"` - Severity string `gorm:"column:severity"` - Links string `gorm:"column:links"` - Description string `gorm:"column:description"` - CvssV2 sql.NullString `gorm:"column:cvss_v2"` - CvssV3 sql.NullString `gorm:"column:cvss_v3"` -} - -// NewVulnerabilityMetadataModel generates a new model from a db.VulnerabilityMetadata struct. -func NewVulnerabilityMetadataModel(metadata v1.VulnerabilityMetadata) VulnerabilityMetadataModel { - links, err := json.Marshal(metadata.Links) - if err != nil { - // TODO: just no - panic(err) - } - - var cvssV2Str sql.NullString - if metadata.CvssV2 != nil { - cvssV2, err := json.Marshal(*metadata.CvssV2) - if err != nil { - // TODO: just no - panic(err) - } - cvssV2Str.String = string(cvssV2) - cvssV2Str.Valid = true - } - - var cvssV3Str sql.NullString - if metadata.CvssV3 != nil { - cvssV3, err := json.Marshal(*metadata.CvssV3) - if err != nil { - // TODO: just no - panic(err) - } - cvssV3Str.String = string(cvssV3) - cvssV3Str.Valid = true - } - - return VulnerabilityMetadataModel{ - ID: metadata.ID, - RecordSource: metadata.RecordSource, - Severity: metadata.Severity, - Links: string(links), - Description: metadata.Description, - CvssV2: cvssV2Str, - CvssV3: cvssV3Str, - } -} - -// TableName returns the table which all db.VulnerabilityMetadata model instances are stored into. -func (VulnerabilityMetadataModel) TableName() string { - return VulnerabilityMetadataTableName -} - -// Inflate generates a db.VulnerabilityMetadataModel object from the serialized model instance. -func (m *VulnerabilityMetadataModel) Inflate() (v1.VulnerabilityMetadata, error) { - var links []string - var cvssV2, cvssV3 *v1.Cvss - - if err := json.Unmarshal([]byte(m.Links), &links); err != nil { - return v1.VulnerabilityMetadata{}, fmt.Errorf("unable to unmarshal links (%+v): %w", m.Links, err) - } - - if m.CvssV2.Valid { - err := json.Unmarshal([]byte(m.CvssV2.String), &cvssV2) - if err != nil { - return v1.VulnerabilityMetadata{}, fmt.Errorf("unable to unmarshal cvssV2 data (%+v): %w", m.CvssV2, err) - } - } - - if m.CvssV3.Valid { - err := json.Unmarshal([]byte(m.CvssV3.String), &cvssV3) - if err != nil { - return v1.VulnerabilityMetadata{}, fmt.Errorf("unable to unmarshal cvssV3 data (%+v): %w", m.CvssV3, err) - } - } - - return v1.VulnerabilityMetadata{ - ID: m.ID, - RecordSource: m.RecordSource, - Severity: m.Severity, - Links: links, - Description: m.Description, - CvssV2: cvssV2, - CvssV3: cvssV3, - }, nil -} diff --git a/grype/db/v1/store/store.go b/grype/db/v1/store/store.go deleted file mode 100644 index 949f60b6c4f..00000000000 --- a/grype/db/v1/store/store.go +++ /dev/null @@ -1,211 +0,0 @@ -package store - -import ( - "fmt" - "sort" - - _ "github.com/glebarez/sqlite" // provide the sqlite dialect to gorm via import - "github.com/go-test/deep" - "gorm.io/gorm" - - "github.com/anchore/grype/grype/db/internal/gormadapter" - v1 "github.com/anchore/grype/grype/db/v1" - "github.com/anchore/grype/grype/db/v1/store/model" - "github.com/anchore/grype/internal/stringutil" -) - -// store holds an instance of the database connection -type store struct { - db *gorm.DB -} - -// New creates a new instance of the store. -func New(dbFilePath string, overwrite bool) (v1.Store, error) { - db, err := gormadapter.Open(dbFilePath, gormadapter.WithTruncate(overwrite)) - if err != nil { - return nil, err - } - - if overwrite { - // TODO: automigrate could write to the database, - // we should be validating the database is the correct database based on the version in the ID table before - // automigrating - if err := db.AutoMigrate(&model.IDModel{}); err != nil { - return nil, fmt.Errorf("unable to migrate ID model: %w", err) - } - if err := db.AutoMigrate(&model.VulnerabilityModel{}); err != nil { - return nil, fmt.Errorf("unable to migrate Vulnerability model: %w", err) - } - if err := db.AutoMigrate(&model.VulnerabilityMetadataModel{}); err != nil { - return nil, fmt.Errorf("unable to migrate Vulnerability Metadata model: %w", err) - } - } - - return &store{ - db: db, - }, nil -} - -// GetID fetches the metadata about the databases schema version and build time. -func (s *store) GetID() (*v1.ID, error) { - var models []model.IDModel - result := s.db.Find(&models) - if result.Error != nil { - return nil, result.Error - } - - switch { - case len(models) > 1: - return nil, fmt.Errorf("found multiple DB IDs") - case len(models) == 1: - id, err := models[0].Inflate() - if err != nil { - return nil, err - } - return &id, nil - } - - return nil, nil -} - -// SetID stores the databases schema version and build time. -func (s *store) SetID(id v1.ID) error { - var ids []model.IDModel - - // replace the existing ID with the given one - s.db.Find(&ids).Delete(&ids) - - m := model.NewIDModel(id) - result := s.db.Create(&m) - - if result.RowsAffected != 1 { - return fmt.Errorf("unable to add id (%d rows affected)", result.RowsAffected) - } - - return result.Error -} - -// GetVulnerability retrieves one or more vulnerabilities given a namespace and package name. -func (s *store) GetVulnerability(namespace, packageName string) ([]v1.Vulnerability, error) { - var models []model.VulnerabilityModel - - result := s.db.Where("namespace = ? AND package_name = ?", namespace, packageName).Find(&models) - - var vulnerabilities = make([]v1.Vulnerability, len(models)) - for idx, m := range models { - vulnerability, err := m.Inflate() - if err != nil { - return nil, err - } - vulnerabilities[idx] = vulnerability - } - - return vulnerabilities, result.Error -} - -// AddVulnerability saves one or more vulnerabilities into the sqlite3 store. -func (s *store) AddVulnerability(vulnerabilities ...v1.Vulnerability) error { - for _, vulnerability := range vulnerabilities { - m := model.NewVulnerabilityModel(vulnerability) - - result := s.db.Create(&m) - if result.Error != nil { - return result.Error - } - - if result.RowsAffected != 1 { - return fmt.Errorf("unable to add vulnerability (%d rows affected)", result.RowsAffected) - } - } - return nil -} - -// GetVulnerabilityMetadata retrieves metadata for the given vulnerability ID relative to a specific record source. -func (s *store) GetVulnerabilityMetadata(id, recordSource string) (*v1.VulnerabilityMetadata, error) { - var models []model.VulnerabilityMetadataModel - - result := s.db.Where(&model.VulnerabilityMetadataModel{ID: id, RecordSource: recordSource}).Find(&models) - if result.Error != nil { - return nil, result.Error - } - - switch { - case len(models) > 1: - return nil, fmt.Errorf("found multiple metadatas for single ID=%q RecordSource=%q", id, recordSource) - case len(models) == 1: - metadata, err := models[0].Inflate() - if err != nil { - return nil, err - } - - return &metadata, nil - } - - return nil, nil -} - -// AddVulnerabilityMetadata stores one or more vulnerability metadata models into the sqlite DB. -func (s *store) AddVulnerabilityMetadata(metadata ...v1.VulnerabilityMetadata) error { - for _, m := range metadata { - existing, err := s.GetVulnerabilityMetadata(m.ID, m.RecordSource) - if err != nil { - return fmt.Errorf("failed to verify existing entry: %w", err) - } - - if existing != nil { - // merge with the existing entry - - cvssV3Diffs := deep.Equal(existing.CvssV3, m.CvssV3) - cvssV2Diffs := deep.Equal(existing.CvssV2, m.CvssV2) - - switch { - case existing.Severity != m.Severity: - return fmt.Errorf("existing metadata has mismatched severity (%q!=%q)", existing.Severity, m.Severity) - case existing.Description != m.Description: - return fmt.Errorf("existing metadata has mismatched description (%q!=%q)", existing.Description, m.Description) - case existing.CvssV2 != nil && len(cvssV2Diffs) > 0: - return fmt.Errorf("existing metadata has mismatched cvss-v2: %+v", cvssV2Diffs) - case existing.CvssV3 != nil && len(cvssV3Diffs) > 0: - return fmt.Errorf("existing metadata has mismatched cvss-v3: %+v", cvssV3Diffs) - default: - existing.CvssV2 = m.CvssV2 - existing.CvssV3 = m.CvssV3 - } - - links := stringutil.NewStringSetFromSlice(existing.Links) - for _, l := range m.Links { - links.Add(l) - } - - existing.Links = links.ToSlice() - sort.Strings(existing.Links) - - newModel := model.NewVulnerabilityMetadataModel(*existing) - result := s.db.Save(&newModel) - - if result.RowsAffected != 1 { - return fmt.Errorf("unable to merge vulnerability metadata (%d rows affected)", result.RowsAffected) - } - - if result.Error != nil { - return result.Error - } - } else { - // this is a new entry - newModel := model.NewVulnerabilityMetadataModel(m) - result := s.db.Create(&newModel) - if result.Error != nil { - return result.Error - } - - if result.RowsAffected != 1 { - return fmt.Errorf("unable to add vulnerability metadata (%d rows affected)", result.RowsAffected) - } - } - } - return nil -} - -func (s *store) Close() { - s.db.Exec("VACUUM;") -} diff --git a/grype/db/v1/store/store_test.go b/grype/db/v1/store/store_test.go deleted file mode 100644 index 99019a7a0d8..00000000000 --- a/grype/db/v1/store/store_test.go +++ /dev/null @@ -1,494 +0,0 @@ -package store - -import ( - "testing" - "time" - - "github.com/go-test/deep" - - v1 "github.com/anchore/grype/grype/db/v1" - "github.com/anchore/grype/grype/db/v1/store/model" -) - -func assertIDReader(t *testing.T, reader v1.IDReader, expected v1.ID) { - t.Helper() - if actual, err := reader.GetID(); err != nil { - t.Fatalf("failed to get ID: %+v", err) - } else { - diffs := deep.Equal(&expected, actual) - if len(diffs) > 0 { - for _, d := range diffs { - t.Errorf("Diff: %+v", d) - } - } - } -} - -func TestStore_GetID_SetID(t *testing.T) { - dbTempFile := t.TempDir() - - s, err := New(dbTempFile, true) - if err != nil { - t.Fatalf("could not create store: %+v", err) - } - - expected := v1.ID{ - BuildTimestamp: time.Now().UTC(), - SchemaVersion: 2, - } - - if err = s.SetID(expected); err != nil { - t.Fatalf("failed to set ID: %+v", err) - } - - assertIDReader(t, s, expected) - -} - -func assertVulnerabilityReader(t *testing.T, reader v1.VulnerabilityStoreReader, namespace, name string, expected []v1.Vulnerability) { - if actual, err := reader.GetVulnerability(namespace, name); err != nil { - t.Fatalf("failed to get Vulnerability: %+v", err) - } else { - if len(actual) != len(expected) { - t.Fatalf("unexpected number of vulns: %d", len(actual)) - } - - for idx := range actual { - diffs := deep.Equal(expected[idx], actual[idx]) - if len(diffs) > 0 { - for _, d := range diffs { - t.Errorf("Diff: %+v", d) - } - } - } - } -} - -func TestStore_GetVulnerability_SetVulnerability(t *testing.T) { - dbTempFile := t.TempDir() - s, err := New(dbTempFile, true) - if err != nil { - t.Fatalf("could not create store: %+v", err) - } - - extra := []v1.Vulnerability{ - { - ID: "my-cve-33333", - RecordSource: "record-source", - PackageName: "package-name-2", - Namespace: "my-namespace", - VersionConstraint: "< 1.0", - VersionFormat: "semver", - CPEs: []string{"a-cool-cpe"}, - ProxyVulnerabilities: []string{"another-cve", "an-other-cve"}, - FixedInVersion: "2.0.1", - }, - { - ID: "my-other-cve-33333", - RecordSource: "record-source", - PackageName: "package-name-3", - Namespace: "my-namespace", - VersionConstraint: "< 509.2.2", - VersionFormat: "semver", - CPEs: []string{"a-cool-cpe"}, - ProxyVulnerabilities: []string{"another-cve", "an-other-cve"}, - }, - } - - expected := []v1.Vulnerability{ - { - ID: "my-cve", - RecordSource: "record-source", - PackageName: "package-name", - Namespace: "my-namespace", - VersionConstraint: "< 1.0", - VersionFormat: "semver", - CPEs: []string{"a-cool-cpe"}, - ProxyVulnerabilities: []string{"another-cve", "an-other-cve"}, - FixedInVersion: "1.0.1", - }, - { - ID: "my-other-cve", - RecordSource: "record-source", - PackageName: "package-name", - Namespace: "my-namespace", - VersionConstraint: "< 509.2.2", - VersionFormat: "semver", - CPEs: []string{"a-cool-cpe"}, - ProxyVulnerabilities: []string{"another-cve", "an-other-cve"}, - FixedInVersion: "4.0.5", - }, - } - - total := append(expected, extra...) - - if err = s.AddVulnerability(total...); err != nil { - t.Fatalf("failed to set Vulnerability: %+v", err) - } - - var allEntries []model.VulnerabilityModel - s.(*store).db.Find(&allEntries) - if len(allEntries) != len(total) { - t.Fatalf("unexpected number of entries: %d", len(allEntries)) - } - - assertVulnerabilityReader(t, s, expected[0].Namespace, expected[0].PackageName, expected) - -} - -func assertVulnerabilityMetadataReader(t *testing.T, reader v1.VulnerabilityMetadataStoreReader, id, recordSource string, expected v1.VulnerabilityMetadata) { - if actual, err := reader.GetVulnerabilityMetadata(id, recordSource); err != nil { - t.Fatalf("failed to get metadata: %+v", err) - } else { - - diffs := deep.Equal(&expected, actual) - if len(diffs) > 0 { - for _, d := range diffs { - t.Errorf("Diff: %+v", d) - } - } - } - -} - -func TestStore_GetVulnerabilityMetadata_SetVulnerabilityMetadata(t *testing.T) { - dbTempFile := t.TempDir() - - s, err := New(dbTempFile, true) - if err != nil { - t.Fatalf("could not create store: %+v", err) - } - - total := []v1.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v1.Cvss{ - BaseScore: 1.1, - ExploitabilityScore: 2.2, - ImpactScore: 3.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--NOT", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.3, - ExploitabilityScore: 2.1, - ImpactScore: 3.2, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--NICE", - }, - }, - { - ID: "my-other-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "worst description ever", - CvssV2: &v1.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - } - - if err = s.AddVulnerabilityMetadata(total...); err != nil { - t.Fatalf("failed to set metadata: %+v", err) - } - - var allEntries []model.VulnerabilityMetadataModel - s.(*store).db.Find(&allEntries) - if len(allEntries) != len(total) { - t.Fatalf("unexpected number of entries: %d", len(allEntries)) - } - -} - -func TestStore_MergeVulnerabilityMetadata(t *testing.T) { - tests := []struct { - name string - add []v1.VulnerabilityMetadata - expected v1.VulnerabilityMetadata - err bool - }{ - { - name: "go-case", - add: []v1.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "worst description ever", - CvssV2: &v1.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - expected: v1.VulnerabilityMetadata{ - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "worst description ever", - CvssV2: &v1.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - { - name: "merge-links", - add: []v1.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://google.com"}, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://yahoo.com"}, - }, - }, - expected: v1.VulnerabilityMetadata{ - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re", "https://google.com", "https://yahoo.com"}, - }, - }, - { - name: "bad-severity", - add: []v1.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "meh, push that for next tuesday...", - Links: []string{"https://redhat.com"}, - }, - }, - err: true, - }, - { - name: "mismatch-description", - err: true, - add: []v1.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v1.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "worst description ever", - CvssV2: &v1.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - }, - { - name: "mismatch-cvss2", - err: true, - add: []v1.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v1.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v1.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:P--VERY", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - }, - { - name: "mismatch-cvss3", - err: true, - add: []v1.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v1.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v1.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v1.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 0, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - dbTempDir := t.TempDir() - - s, err := New(dbTempDir, true) - if err != nil { - t.Fatalf("could not create store: %+v", err) - } - - // add each metadata in order - var theErr error - for _, metadata := range test.add { - err = s.AddVulnerabilityMetadata(metadata) - if err != nil { - theErr = err - break - } - } - - if test.err && theErr == nil { - t.Fatalf("expected error but did not get one") - } else if !test.err && theErr != nil { - t.Fatalf("expected no error but got one: %+v", theErr) - } else if test.err && theErr != nil { - // test pass... - return - } - - // ensure there is exactly one entry - var allEntries []model.VulnerabilityMetadataModel - s.(*store).db.Find(&allEntries) - if len(allEntries) != 1 { - t.Fatalf("unexpected number of entries: %d", len(allEntries)) - } - - // get the resulting metadata object - if actual, err := s.GetVulnerabilityMetadata(test.expected.ID, test.expected.RecordSource); err != nil { - t.Fatalf("failed to get metadata: %+v", err) - } else { - diffs := deep.Equal(&test.expected, actual) - if len(diffs) > 0 { - for _, d := range diffs { - t.Errorf("Diff: %+v", d) - } - } - } - }) - } -} diff --git a/grype/db/v1/vulnerability.go b/grype/db/v1/vulnerability.go deleted file mode 100644 index c0fa5912ea8..00000000000 --- a/grype/db/v1/vulnerability.go +++ /dev/null @@ -1,31 +0,0 @@ -package v1 - -const VulnerabilityStoreFileName = "vulnerability.db" - -// Vulnerability represents the minimum data fields necessary to perform package-to-vulnerability matching. This can represent a CVE, 3rd party advisory, or any source that relates back to a CVE. -type Vulnerability struct { - ID string // The identifier of the vulnerability or advisory - RecordSource string // The source of the vulnerability information - PackageName string // The name of the package that is vulnerable - Namespace string // The ecosystem where the package resides - VersionConstraint string // The version range which the given package is vulnerable - VersionFormat string // The format which all version fields should be interpreted as - CPEs []string // The CPEs which are considered vulnerable - ProxyVulnerabilities []string // IDs of other Vulnerabilities that are related to this one (this is how advisories relate to CVEs) - FixedInVersion string // The version which this particular vulnerability was fixed in -} - -type VulnerabilityStore interface { - VulnerabilityStoreReader - VulnerabilityStoreWriter -} - -type VulnerabilityStoreReader interface { - // GetVulnerability retrieves vulnerabilities associated with a namespace and a package name - GetVulnerability(namespace, name string) ([]Vulnerability, error) -} - -type VulnerabilityStoreWriter interface { - // AddVulnerability inserts a new record of a vulnerability into the store - AddVulnerability(vulnerabilities ...Vulnerability) error -} diff --git a/grype/db/v1/vulnerability_metadata.go b/grype/db/v1/vulnerability_metadata.go deleted file mode 100644 index 218c0ff5d9a..00000000000 --- a/grype/db/v1/vulnerability_metadata.go +++ /dev/null @@ -1,33 +0,0 @@ -package v1 - -// VulnerabilityMetadata represents all vulnerability data that is not necessary to perform package-to-vulnerability matching. -type VulnerabilityMetadata struct { - ID string // The identifier of the vulnerability or advisory - RecordSource string // The source of the vulnerability information - Severity string // How severe the vulnerability is (valid values are defined by upstream sources currently) - Links []string // URLs to get more information about the vulnerability or advisory - Description string // Description of the vulnerability - CvssV2 *Cvss // Common Vulnerability Scoring System V2 values - CvssV3 *Cvss // Common Vulnerability Scoring System V3 values -} - -// Cvss contains select Common Vulnerability Scoring System fields for a vulnerability. -type Cvss struct { - BaseScore float64 // Ranges from 0 - 10 and defines for qualities intrinsic to a vulnerability - ExploitabilityScore float64 // Indicator of how easy it may be for an attacker to exploit a vulnerability - ImpactScore float64 // Representation of the effects of an exploited vulnerability relative to compromise in confidentiality, integrity, and availability - Vector string // A textual representation of the metric values used to determine the score -} - -type VulnerabilityMetadataStore interface { - VulnerabilityMetadataStoreReader - VulnerabilityMetadataStoreWriter -} - -type VulnerabilityMetadataStoreReader interface { - GetVulnerabilityMetadata(id, recordSource string) (*VulnerabilityMetadata, error) -} - -type VulnerabilityMetadataStoreWriter interface { - AddVulnerabilityMetadata(metadata ...VulnerabilityMetadata) error -} diff --git a/grype/db/v2/id.go b/grype/db/v2/id.go deleted file mode 100644 index f162ea08d48..00000000000 --- a/grype/db/v2/id.go +++ /dev/null @@ -1,28 +0,0 @@ -package v2 - -import ( - "time" -) - -// ID represents identifying information for a DB and the data it contains. -type ID struct { - // BuildTimestamp is the timestamp used to define the age of the DB, ideally including the age of the data - // contained in the DB, not just when the DB file was created. - BuildTimestamp time.Time - SchemaVersion int -} - -type IDReader interface { - GetID() (*ID, error) -} - -type IDWriter interface { - SetID(ID) error -} - -func NewID(age time.Time) ID { - return ID{ - BuildTimestamp: age.UTC(), - SchemaVersion: SchemaVersion, - } -} diff --git a/grype/db/v2/namespace.go b/grype/db/v2/namespace.go deleted file mode 100644 index 70ac1030d4c..00000000000 --- a/grype/db/v2/namespace.go +++ /dev/null @@ -1,30 +0,0 @@ -package v2 - -import ( - "fmt" -) - -const ( - NVDNamespace = "nvd" -) - -func RecordSource(feed, group string) string { - switch feed { - case "github", "nvdv2": - return group - default: - return fmt.Sprintf("%s:%s", feed, group) - } -} - -func NamespaceForFeedGroup(feed, group string) (string, error) { - switch { - case feed == "vulnerabilities": - return group, nil - case feed == "github": - return group, nil - case feed == "nvdv2" && group == "nvdv2:cves": - return NVDNamespace, nil - } - return "", fmt.Errorf("feed=%q group=%q has no namespace mappings", feed, group) -} diff --git a/grype/db/v2/namespace_test.go b/grype/db/v2/namespace_test.go deleted file mode 100644 index f4f4a2bb223..00000000000 --- a/grype/db/v2/namespace_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package v2 - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNamespaceFromRecordSource(t *testing.T) { - tests := []struct { - Feed, Group string - Namespace string - }{ - { - Feed: "vulnerabilities", - Group: "ubuntu:20.04", - Namespace: "ubuntu:20.04", - }, - { - Feed: "vulnerabilities", - Group: "alpine:3.9", - Namespace: "alpine:3.9", - }, - { - Feed: "vulnerabilities", - Group: "sles:12.5", - Namespace: "sles:12.5", - }, - { - Feed: "nvdv2", - Group: "nvdv2:cves", - Namespace: "nvd", - }, - { - Feed: "github", - Group: "github:python", - Namespace: "github:python", - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("feed=%q group=%q namespace=%q", test.Feed, test.Group, test.Namespace), func(t *testing.T) { - actual, err := NamespaceForFeedGroup(test.Feed, test.Group) - assert.NoError(t, err) - assert.Equal(t, test.Namespace, actual) - }) - } -} diff --git a/grype/db/v2/schema_version.go b/grype/db/v2/schema_version.go deleted file mode 100644 index 86d7d191d8b..00000000000 --- a/grype/db/v2/schema_version.go +++ /dev/null @@ -1,3 +0,0 @@ -package v2 - -const SchemaVersion = 2 diff --git a/grype/db/v2/store.go b/grype/db/v2/store.go deleted file mode 100644 index ad5cb06484b..00000000000 --- a/grype/db/v2/store.go +++ /dev/null @@ -1,19 +0,0 @@ -package v2 - -type Store interface { - StoreReader - StoreWriter -} - -type StoreReader interface { - IDReader - VulnerabilityStoreReader - VulnerabilityMetadataStoreReader -} - -type StoreWriter interface { - IDWriter - VulnerabilityStoreWriter - VulnerabilityMetadataStoreWriter - Close() -} diff --git a/grype/db/v2/store/model/id.go b/grype/db/v2/store/model/id.go deleted file mode 100644 index 03185af0ee4..00000000000 --- a/grype/db/v2/store/model/id.go +++ /dev/null @@ -1,40 +0,0 @@ -package model - -import ( - "fmt" - "time" - - v2 "github.com/anchore/grype/grype/db/v2" -) - -const ( - IDTableName = "id" -) - -type IDModel struct { - BuildTimestamp string `gorm:"column:build_timestamp"` - SchemaVersion int `gorm:"column:schema_version"` -} - -func NewIDModel(id v2.ID) IDModel { - return IDModel{ - BuildTimestamp: id.BuildTimestamp.Format(time.RFC3339Nano), - SchemaVersion: id.SchemaVersion, - } -} - -func (IDModel) TableName() string { - return IDTableName -} - -func (m *IDModel) Inflate() (v2.ID, error) { - buildTime, err := time.Parse(time.RFC3339Nano, m.BuildTimestamp) - if err != nil { - return v2.ID{}, fmt.Errorf("unable to parse build timestamp (%+v): %w", m.BuildTimestamp, err) - } - - return v2.ID{ - BuildTimestamp: buildTime, - SchemaVersion: m.SchemaVersion, - }, nil -} diff --git a/grype/db/v2/store/model/vulnerability.go b/grype/db/v2/store/model/vulnerability.go deleted file mode 100644 index 25f8c18d27f..00000000000 --- a/grype/db/v2/store/model/vulnerability.go +++ /dev/null @@ -1,86 +0,0 @@ -package model - -import ( - "encoding/json" - "fmt" - - v2 "github.com/anchore/grype/grype/db/v2" -) - -const ( - VulnerabilityTableName = "vulnerability" - GetVulnerabilityIndexName = "get_vulnerability_index" -) - -// VulnerabilityModel is a struct used to serialize db.Vulnerability information into a sqlite3 DB. -type VulnerabilityModel struct { - PK uint64 `gorm:"primary_key;auto_increment;"` - ID string `gorm:"column:id"` - RecordSource string `gorm:"column:record_source"` - PackageName string `gorm:"column:package_name; index:get_vulnerability_index"` - Namespace string `gorm:"column:namespace; index:get_vulnerability_index"` - VersionConstraint string `gorm:"column:version_constraint"` - VersionFormat string `gorm:"column:version_format"` - CPEs string `gorm:"column:cpes"` - ProxyVulnerabilities string `gorm:"column:proxy_vulnerabilities"` - FixedInVersion string `gorm:"column:fixed_in_version"` -} - -// NewVulnerabilityModel generates a new model from a db.Vulnerability struct. -func NewVulnerabilityModel(vulnerability v2.Vulnerability) VulnerabilityModel { - cpes, err := json.Marshal(vulnerability.CPEs) - if err != nil { - // TODO: just no - panic(err) - } - - proxy, err := json.Marshal(vulnerability.ProxyVulnerabilities) - if err != nil { - // TODO: just no - panic(err) - } - - return VulnerabilityModel{ - ID: vulnerability.ID, - PackageName: vulnerability.PackageName, - RecordSource: vulnerability.RecordSource, - Namespace: vulnerability.Namespace, - VersionConstraint: vulnerability.VersionConstraint, - VersionFormat: vulnerability.VersionFormat, - FixedInVersion: vulnerability.FixedInVersion, - CPEs: string(cpes), - ProxyVulnerabilities: string(proxy), - } -} - -// TableName returns the table which all db.Vulnerability model instances are stored into. -func (VulnerabilityModel) TableName() string { - return VulnerabilityTableName -} - -// Inflate generates a db.Vulnerability object from the serialized model instance. -func (m *VulnerabilityModel) Inflate() (v2.Vulnerability, error) { - var cpes []string - err := json.Unmarshal([]byte(m.CPEs), &cpes) - if err != nil { - return v2.Vulnerability{}, fmt.Errorf("unable to unmarshal CPEs (%+v): %w", m.CPEs, err) - } - - var proxy []string - err = json.Unmarshal([]byte(m.ProxyVulnerabilities), &proxy) - if err != nil { - return v2.Vulnerability{}, fmt.Errorf("unable to unmarshal proxy vulnerabilities (%+v): %w", m.ProxyVulnerabilities, err) - } - - return v2.Vulnerability{ - ID: m.ID, - RecordSource: m.RecordSource, - PackageName: m.PackageName, - Namespace: m.Namespace, - VersionConstraint: m.VersionConstraint, - VersionFormat: m.VersionFormat, - CPEs: cpes, - ProxyVulnerabilities: proxy, - FixedInVersion: m.FixedInVersion, - }, nil -} diff --git a/grype/db/v2/store/model/vulnerability_metadata.go b/grype/db/v2/store/model/vulnerability_metadata.go deleted file mode 100644 index 47c47d9660e..00000000000 --- a/grype/db/v2/store/model/vulnerability_metadata.go +++ /dev/null @@ -1,104 +0,0 @@ -package model - -import ( - "database/sql" - "encoding/json" - "fmt" - - v2 "github.com/anchore/grype/grype/db/v2" -) - -const ( - VulnerabilityMetadataTableName = "vulnerability_metadata" -) - -// VulnerabilityMetadataModel is a struct used to serialize db.VulnerabilityMetadata information into a sqlite3 DB. -type VulnerabilityMetadataModel struct { - ID string `gorm:"primary_key; column:id;"` - RecordSource string `gorm:"primary_key; column:record_source;"` - Severity string `gorm:"column:severity"` - Links string `gorm:"column:links"` - Description string `gorm:"column:description"` - CvssV2 sql.NullString `gorm:"column:cvss_v2"` - CvssV3 sql.NullString `gorm:"column:cvss_v3"` -} - -// NewVulnerabilityMetadataModel generates a new model from a db.VulnerabilityMetadata struct. -func NewVulnerabilityMetadataModel(metadata v2.VulnerabilityMetadata) VulnerabilityMetadataModel { - links, err := json.Marshal(metadata.Links) - if err != nil { - // TODO: just no - panic(err) - } - - var cvssV2Str sql.NullString - if metadata.CvssV2 != nil { - cvssV2, err := json.Marshal(*metadata.CvssV2) - if err != nil { - // TODO: just no - panic(err) - } - cvssV2Str.String = string(cvssV2) - cvssV2Str.Valid = true - } - - var cvssV3Str sql.NullString - if metadata.CvssV3 != nil { - cvssV3, err := json.Marshal(*metadata.CvssV3) - if err != nil { - // TODO: just no - panic(err) - } - cvssV3Str.String = string(cvssV3) - cvssV3Str.Valid = true - } - - return VulnerabilityMetadataModel{ - ID: metadata.ID, - RecordSource: metadata.RecordSource, - Severity: metadata.Severity, - Links: string(links), - Description: metadata.Description, - CvssV2: cvssV2Str, - CvssV3: cvssV3Str, - } -} - -// TableName returns the table which all db.VulnerabilityMetadata model instances are stored into. -func (VulnerabilityMetadataModel) TableName() string { - return VulnerabilityMetadataTableName -} - -// Inflate generates a db.VulnerabilityMetadataModel object from the serialized model instance. -func (m *VulnerabilityMetadataModel) Inflate() (v2.VulnerabilityMetadata, error) { - var links []string - var cvssV2, cvssV3 *v2.Cvss - - if err := json.Unmarshal([]byte(m.Links), &links); err != nil { - return v2.VulnerabilityMetadata{}, fmt.Errorf("unable to unmarshal links (%+v): %w", m.Links, err) - } - - if m.CvssV2.Valid { - err := json.Unmarshal([]byte(m.CvssV2.String), &cvssV2) - if err != nil { - return v2.VulnerabilityMetadata{}, fmt.Errorf("unable to unmarshal cvssV2 data (%+v): %w", m.CvssV2, err) - } - } - - if m.CvssV3.Valid { - err := json.Unmarshal([]byte(m.CvssV3.String), &cvssV3) - if err != nil { - return v2.VulnerabilityMetadata{}, fmt.Errorf("unable to unmarshal cvssV3 data (%+v): %w", m.CvssV3, err) - } - } - - return v2.VulnerabilityMetadata{ - ID: m.ID, - RecordSource: m.RecordSource, - Severity: m.Severity, - Links: links, - Description: m.Description, - CvssV2: cvssV2, - CvssV3: cvssV3, - }, nil -} diff --git a/grype/db/v2/store/store.go b/grype/db/v2/store/store.go deleted file mode 100644 index 818f100de44..00000000000 --- a/grype/db/v2/store/store.go +++ /dev/null @@ -1,210 +0,0 @@ -package store - -import ( - "fmt" - "sort" - - _ "github.com/glebarez/sqlite" // provide the sqlite dialect to gorm via import - "github.com/go-test/deep" - "gorm.io/gorm" - - "github.com/anchore/grype/grype/db/internal/gormadapter" - v2 "github.com/anchore/grype/grype/db/v2" - "github.com/anchore/grype/grype/db/v2/store/model" - "github.com/anchore/grype/internal/stringutil" -) - -// store holds an instance of the database connection -type store struct { - db *gorm.DB -} - -// New creates a new instance of the store. -func New(dbFilePath string, overwrite bool) (v2.Store, error) { - db, err := gormadapter.Open(dbFilePath, gormadapter.WithTruncate(overwrite)) - if err != nil { - return nil, err - } - if overwrite { - // TODO: automigrate could write to the database, - // we should be validating the database is the correct database based on the version in the ID table before - // automigrating - if err := db.AutoMigrate(&model.IDModel{}); err != nil { - return nil, fmt.Errorf("unable to migrate ID model: %w", err) - } - if err := db.AutoMigrate(&model.VulnerabilityModel{}); err != nil { - return nil, fmt.Errorf("unable to migrate Vulnerability model: %w", err) - } - if err := db.AutoMigrate(&model.VulnerabilityMetadataModel{}); err != nil { - return nil, fmt.Errorf("unable to migrate Vulnerability Metadata model: %w", err) - } - } - - return &store{ - db: db, - }, nil -} - -// GetID fetches the metadata about the databases schema version and build time. -func (s *store) GetID() (*v2.ID, error) { - var models []model.IDModel - result := s.db.Find(&models) - if result.Error != nil { - return nil, result.Error - } - - switch { - case len(models) > 1: - return nil, fmt.Errorf("found multiple DB IDs") - case len(models) == 1: - id, err := models[0].Inflate() - if err != nil { - return nil, err - } - return &id, nil - } - - return nil, nil -} - -// SetID stores the databases schema version and build time. -func (s *store) SetID(id v2.ID) error { - var ids []model.IDModel - - // replace the existing ID with the given one - s.db.Find(&ids).Delete(&ids) - - m := model.NewIDModel(id) - result := s.db.Create(&m) - - if result.RowsAffected != 1 { - return fmt.Errorf("unable to add id (%d rows affected)", result.RowsAffected) - } - - return result.Error -} - -// GetVulnerability retrieves one or more vulnerabilities given a namespace and package name. -func (s *store) GetVulnerability(namespace, packageName string) ([]v2.Vulnerability, error) { - var models []model.VulnerabilityModel - - result := s.db.Where("namespace = ? AND package_name = ?", namespace, packageName).Find(&models) - - var vulnerabilities = make([]v2.Vulnerability, len(models)) - for idx, m := range models { - vulnerability, err := m.Inflate() - if err != nil { - return nil, err - } - vulnerabilities[idx] = vulnerability - } - - return vulnerabilities, result.Error -} - -// AddVulnerability saves one or more vulnerabilities into the sqlite3 store. -func (s *store) AddVulnerability(vulnerabilities ...v2.Vulnerability) error { - for _, vulnerability := range vulnerabilities { - m := model.NewVulnerabilityModel(vulnerability) - - result := s.db.Create(&m) - if result.Error != nil { - return result.Error - } - - if result.RowsAffected != 1 { - return fmt.Errorf("unable to add vulnerability (%d rows affected)", result.RowsAffected) - } - } - return nil -} - -// GetVulnerabilityMetadata retrieves metadata for the given vulnerability ID relative to a specific record source. -func (s *store) GetVulnerabilityMetadata(id, recordSource string) (*v2.VulnerabilityMetadata, error) { - var models []model.VulnerabilityMetadataModel - - result := s.db.Where(&model.VulnerabilityMetadataModel{ID: id, RecordSource: recordSource}).Find(&models) - if result.Error != nil { - return nil, result.Error - } - - switch { - case len(models) > 1: - return nil, fmt.Errorf("found multiple metadatas for single ID=%q RecordSource=%q", id, recordSource) - case len(models) == 1: - metadata, err := models[0].Inflate() - if err != nil { - return nil, err - } - - return &metadata, nil - } - - return nil, nil -} - -// AddVulnerabilityMetadata stores one or more vulnerability metadata models into the sqlite DB. -func (s *store) AddVulnerabilityMetadata(metadata ...v2.VulnerabilityMetadata) error { - for _, m := range metadata { - existing, err := s.GetVulnerabilityMetadata(m.ID, m.RecordSource) - if err != nil { - return fmt.Errorf("failed to verify existing entry: %w", err) - } - - if existing != nil { - // merge with the existing entry - - cvssV3Diffs := deep.Equal(existing.CvssV3, m.CvssV3) - cvssV2Diffs := deep.Equal(existing.CvssV2, m.CvssV2) - - switch { - case existing.Severity != m.Severity: - return fmt.Errorf("existing metadata has mismatched severity (%q!=%q)", existing.Severity, m.Severity) - case existing.Description != m.Description: - return fmt.Errorf("existing metadata has mismatched description (%q!=%q)", existing.Description, m.Description) - case existing.CvssV2 != nil && len(cvssV2Diffs) > 0: - return fmt.Errorf("existing metadata has mismatched cvss-v2: %+v", cvssV2Diffs) - case existing.CvssV3 != nil && len(cvssV3Diffs) > 0: - return fmt.Errorf("existing metadata has mismatched cvss-v3: %+v", cvssV3Diffs) - default: - existing.CvssV2 = m.CvssV2 - existing.CvssV3 = m.CvssV3 - } - - links := stringutil.NewStringSetFromSlice(existing.Links) - for _, l := range m.Links { - links.Add(l) - } - - existing.Links = links.ToSlice() - sort.Strings(existing.Links) - - newModel := model.NewVulnerabilityMetadataModel(*existing) - result := s.db.Save(&newModel) - - if result.RowsAffected != 1 { - return fmt.Errorf("unable to merge vulnerability metadata (%d rows affected)", result.RowsAffected) - } - - if result.Error != nil { - return result.Error - } - } else { - // this is a new entry - newModel := model.NewVulnerabilityMetadataModel(m) - result := s.db.Create(&newModel) - if result.Error != nil { - return result.Error - } - - if result.RowsAffected != 1 { - return fmt.Errorf("unable to add vulnerability metadata (%d rows affected)", result.RowsAffected) - } - } - } - return nil -} - -func (s *store) Close() { - s.db.Exec("VACUUM;") -} diff --git a/grype/db/v2/store/store_test.go b/grype/db/v2/store/store_test.go deleted file mode 100644 index 43537be52a7..00000000000 --- a/grype/db/v2/store/store_test.go +++ /dev/null @@ -1,494 +0,0 @@ -package store - -import ( - "testing" - "time" - - "github.com/go-test/deep" - - v2 "github.com/anchore/grype/grype/db/v2" - "github.com/anchore/grype/grype/db/v2/store/model" -) - -func assertIDReader(t *testing.T, reader v2.IDReader, expected v2.ID) { - t.Helper() - if actual, err := reader.GetID(); err != nil { - t.Fatalf("failed to get ID: %+v", err) - } else { - diffs := deep.Equal(&expected, actual) - if len(diffs) > 0 { - for _, d := range diffs { - t.Errorf("Diff: %+v", d) - } - } - } -} - -func TestStore_GetID_SetID(t *testing.T) { - dbTempFile := t.TempDir() - - s, err := New(dbTempFile, true) - if err != nil { - t.Fatalf("could not create store: %+v", err) - } - - expected := v2.ID{ - BuildTimestamp: time.Now().UTC(), - SchemaVersion: 2, - } - - if err = s.SetID(expected); err != nil { - t.Fatalf("failed to set ID: %+v", err) - } - - assertIDReader(t, s, expected) - -} - -func assertVulnerabilityReader(t *testing.T, reader v2.VulnerabilityStoreReader, namespace, name string, expected []v2.Vulnerability) { - if actual, err := reader.GetVulnerability(namespace, name); err != nil { - t.Fatalf("failed to get Vulnerability: %+v", err) - } else { - if len(actual) != len(expected) { - t.Fatalf("unexpected number of vulns: %d", len(actual)) - } - - for idx := range actual { - diffs := deep.Equal(expected[idx], actual[idx]) - if len(diffs) > 0 { - for _, d := range diffs { - t.Errorf("Diff: %+v", d) - } - } - } - } -} - -func TestStore_GetVulnerability_SetVulnerability(t *testing.T) { - dbTempFile := t.TempDir() - s, err := New(dbTempFile, true) - if err != nil { - t.Fatalf("could not create store: %+v", err) - } - - extra := []v2.Vulnerability{ - { - ID: "my-cve-33333", - RecordSource: "record-source", - PackageName: "package-name-2", - Namespace: "my-namespace", - VersionConstraint: "< 1.0", - VersionFormat: "semver", - CPEs: []string{"a-cool-cpe"}, - ProxyVulnerabilities: []string{"another-cve", "an-other-cve"}, - FixedInVersion: "2.0.1", - }, - { - ID: "my-other-cve-33333", - RecordSource: "record-source", - PackageName: "package-name-3", - Namespace: "my-namespace", - VersionConstraint: "< 509.2.2", - VersionFormat: "semver", - CPEs: []string{"a-cool-cpe"}, - ProxyVulnerabilities: []string{"another-cve", "an-other-cve"}, - }, - } - - expected := []v2.Vulnerability{ - { - ID: "my-cve", - RecordSource: "record-source", - PackageName: "package-name", - Namespace: "my-namespace", - VersionConstraint: "< 1.0", - VersionFormat: "semver", - CPEs: []string{"a-cool-cpe"}, - ProxyVulnerabilities: []string{"another-cve", "an-other-cve"}, - FixedInVersion: "1.0.1", - }, - { - ID: "my-other-cve", - RecordSource: "record-source", - PackageName: "package-name", - Namespace: "my-namespace", - VersionConstraint: "< 509.2.2", - VersionFormat: "semver", - CPEs: []string{"a-cool-cpe"}, - ProxyVulnerabilities: []string{"another-cve", "an-other-cve"}, - FixedInVersion: "4.0.5", - }, - } - - total := append(expected, extra...) - - if err = s.AddVulnerability(total...); err != nil { - t.Fatalf("failed to set Vulnerability: %+v", err) - } - - var allEntries []model.VulnerabilityModel - s.(*store).db.Find(&allEntries) - if len(allEntries) != len(total) { - t.Fatalf("unexpected number of entries: %d", len(allEntries)) - } - - assertVulnerabilityReader(t, s, expected[0].Namespace, expected[0].PackageName, expected) - -} - -func assertVulnerabilityMetadataReader(t *testing.T, reader v2.VulnerabilityMetadataStoreReader, id, recordSource string, expected v2.VulnerabilityMetadata) { - if actual, err := reader.GetVulnerabilityMetadata(id, recordSource); err != nil { - t.Fatalf("failed to get metadata: %+v", err) - } else { - - diffs := deep.Equal(&expected, actual) - if len(diffs) > 0 { - for _, d := range diffs { - t.Errorf("Diff: %+v", d) - } - } - } - -} - -func TestStore_GetVulnerabilityMetadata_SetVulnerabilityMetadata(t *testing.T) { - dbTempFile := t.TempDir() - - s, err := New(dbTempFile, true) - if err != nil { - t.Fatalf("could not create store: %+v", err) - } - - total := []v2.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v2.Cvss{ - BaseScore: 1.1, - ExploitabilityScore: 2.2, - ImpactScore: 3.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--NOT", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.3, - ExploitabilityScore: 2.1, - ImpactScore: 3.2, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--NICE", - }, - }, - { - ID: "my-other-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "worst description ever", - CvssV2: &v2.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - } - - if err = s.AddVulnerabilityMetadata(total...); err != nil { - t.Fatalf("failed to set metadata: %+v", err) - } - - var allEntries []model.VulnerabilityMetadataModel - s.(*store).db.Find(&allEntries) - if len(allEntries) != len(total) { - t.Fatalf("unexpected number of entries: %d", len(allEntries)) - } - -} - -func TestStore_MergeVulnerabilityMetadata(t *testing.T) { - tests := []struct { - name string - add []v2.VulnerabilityMetadata - expected v2.VulnerabilityMetadata - err bool - }{ - { - name: "go-case", - add: []v2.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "worst description ever", - CvssV2: &v2.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - expected: v2.VulnerabilityMetadata{ - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "worst description ever", - CvssV2: &v2.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - { - name: "merge-links", - add: []v2.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://google.com"}, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://yahoo.com"}, - }, - }, - expected: v2.VulnerabilityMetadata{ - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re", "https://google.com", "https://yahoo.com"}, - }, - }, - { - name: "bad-severity", - add: []v2.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "meh, push that for next tuesday...", - Links: []string{"https://redhat.com"}, - }, - }, - err: true, - }, - { - name: "mismatch-description", - err: true, - add: []v2.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v2.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "worst description ever", - CvssV2: &v2.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - }, - { - name: "mismatch-cvss2", - err: true, - add: []v2.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v2.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v2.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:P--VERY", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - }, - { - name: "mismatch-cvss3", - err: true, - add: []v2.VulnerabilityMetadata{ - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v2.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 2.5, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - { - ID: "my-cve", - RecordSource: "record-source", - Severity: "pretty bad", - Links: []string{"https://ancho.re"}, - Description: "best description ever", - CvssV2: &v2.Cvss{ - BaseScore: 4.1, - ExploitabilityScore: 5.2, - ImpactScore: 6.3, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--VERY", - }, - CvssV3: &v2.Cvss{ - BaseScore: 1.4, - ExploitabilityScore: 0, - ImpactScore: 3.6, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P--GOOD", - }, - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - dbTempDir := t.TempDir() - - s, err := New(dbTempDir, true) - if err != nil { - t.Fatalf("could not create store: %+v", err) - } - - // add each metadata in order - var theErr error - for _, metadata := range test.add { - err = s.AddVulnerabilityMetadata(metadata) - if err != nil { - theErr = err - break - } - } - - if test.err && theErr == nil { - t.Fatalf("expected error but did not get one") - } else if !test.err && theErr != nil { - t.Fatalf("expected no error but got one: %+v", theErr) - } else if test.err && theErr != nil { - // test pass... - return - } - - // ensure there is exactly one entry - var allEntries []model.VulnerabilityMetadataModel - s.(*store).db.Find(&allEntries) - if len(allEntries) != 1 { - t.Fatalf("unexpected number of entries: %d", len(allEntries)) - } - - // get the resulting metadata object - if actual, err := s.GetVulnerabilityMetadata(test.expected.ID, test.expected.RecordSource); err != nil { - t.Fatalf("failed to get metadata: %+v", err) - } else { - diffs := deep.Equal(&test.expected, actual) - if len(diffs) > 0 { - for _, d := range diffs { - t.Errorf("Diff: %+v", d) - } - } - } - }) - } -} diff --git a/grype/db/v2/vulnerability.go b/grype/db/v2/vulnerability.go deleted file mode 100644 index f76d76f70f5..00000000000 --- a/grype/db/v2/vulnerability.go +++ /dev/null @@ -1,14 +0,0 @@ -package v2 - -// Vulnerability represents the minimum data fields necessary to perform package-to-vulnerability matching. This can represent a CVE, 3rd party advisory, or any source that relates back to a CVE. -type Vulnerability struct { - ID string // The identifier of the vulnerability or advisory - RecordSource string // The source of the vulnerability information - PackageName string // The name of the package that is vulnerable - Namespace string // The ecosystem where the package resides - VersionConstraint string // The version range which the given package is vulnerable - VersionFormat string // The format which all version fields should be interpreted as - CPEs []string // The CPEs which are considered vulnerable - ProxyVulnerabilities []string // IDs of other Vulnerabilities that are related to this one (this is how advisories relate to CVEs) - FixedInVersion string // The version which this particular vulnerability was fixed in -} diff --git a/grype/db/v2/vulnerability_metadata.go b/grype/db/v2/vulnerability_metadata.go deleted file mode 100644 index d92395b5e67..00000000000 --- a/grype/db/v2/vulnerability_metadata.go +++ /dev/null @@ -1,20 +0,0 @@ -package v2 - -// VulnerabilityMetadata represents all vulnerability data that is not necessary to perform package-to-vulnerability matching. -type VulnerabilityMetadata struct { - ID string // The identifier of the vulnerability or advisory - RecordSource string // The source of the vulnerability information - Severity string // How severe the vulnerability is (valid values are defined by upstream sources currently) - Links []string // URLs to get more information about the vulnerability or advisory - Description string // Description of the vulnerability - CvssV2 *Cvss // Common Vulnerability Scoring System V2 values - CvssV3 *Cvss // Common Vulnerability Scoring System V3 values -} - -// Cvss contains select Common Vulnerability Scoring System fields for a vulnerability. -type Cvss struct { - BaseScore float64 // Ranges from 0 - 10 and defines for qualities intrinsic to a vulnerability - ExploitabilityScore float64 // Indicator of how easy it may be for an attacker to exploit a vulnerability - ImpactScore float64 // Representation of the effects of an exploited vulnerability relative to compromise in confidentiality, integrity, and availability - Vector string // A textual representation of the metric values used to determine the score -} diff --git a/grype/db/v2/vulnerability_metadata_store.go b/grype/db/v2/vulnerability_metadata_store.go deleted file mode 100644 index 65b726e03d9..00000000000 --- a/grype/db/v2/vulnerability_metadata_store.go +++ /dev/null @@ -1,14 +0,0 @@ -package v2 - -type VulnerabilityMetadataStore interface { - VulnerabilityMetadataStoreReader - VulnerabilityMetadataStoreWriter -} - -type VulnerabilityMetadataStoreReader interface { - GetVulnerabilityMetadata(id, recordSource string) (*VulnerabilityMetadata, error) -} - -type VulnerabilityMetadataStoreWriter interface { - AddVulnerabilityMetadata(metadata ...VulnerabilityMetadata) error -} diff --git a/grype/db/v2/vulnerability_store.go b/grype/db/v2/vulnerability_store.go deleted file mode 100644 index d3c18e64a35..00000000000 --- a/grype/db/v2/vulnerability_store.go +++ /dev/null @@ -1,18 +0,0 @@ -package v2 - -const VulnerabilityStoreFileName = "vulnerability.db" - -type VulnerabilityStore interface { - VulnerabilityStoreReader - VulnerabilityStoreWriter -} - -type VulnerabilityStoreReader interface { - // GetVulnerability retrieves vulnerabilities associated with a namespace and a package name - GetVulnerability(namespace, name string) ([]Vulnerability, error) -} - -type VulnerabilityStoreWriter interface { - // AddVulnerability inserts a new record of a vulnerability into the store - AddVulnerability(vulnerabilities ...Vulnerability) error -}