diff --git a/grype/db/v6/data.go b/grype/db/v6/data.go deleted file mode 100644 index bf56b636b6c..00000000000 --- a/grype/db/v6/data.go +++ /dev/null @@ -1,129 +0,0 @@ -package v6 - -import ( - "strings" - - "github.com/anchore/syft/syft/pkg" -) - -// TODO: in a future iteration these should be raised up more explicitly by the vunnel providers -func KnownOperatingSystemSpecifierOverrides() []OperatingSystemSpecifierOverride { - strRef := func(s string) *string { - return &s - } - return []OperatingSystemSpecifierOverride{ - // redhat clones or otherwise shared vulnerability data - {Alias: "centos", ReplacementName: strRef("rhel")}, - {Alias: "rocky", ReplacementName: strRef("rhel")}, - {Alias: "rockylinux", ReplacementName: strRef("rhel")}, // non-standard, but common (dockerhub uses "rockylinux") - {Alias: "alma", ReplacementName: strRef("rhel")}, - {Alias: "almalinux", ReplacementName: strRef("rhel")}, // non-standard, but common (dockerhub uses "almalinux") - {Alias: "gentoo", ReplacementName: strRef("rhel")}, - - // Alternaitve distros that should match against the debian vulnerability data - {Alias: "raspbian", ReplacementName: strRef("debian")}, - - // to remain backwards compatible, we need to keep old clients from ignoring EUS data. - // we do this by diverting any requests for a specific major.minor version of rhel to only - // use the major version. But, this only applies to clients before v6.0.3 DB schema version. - // Why 6.0.3? This is when OS channel was introduced, which grype-db will leverage, and add additional - // rhel rows to the DB, all which have major.minor versions. This means that any old client (which wont - // see the new channel column) will assume during OS resolution that there is major.minor vuln data - // that should be used (which is incorrect). - {Alias: "rhel", VersionPattern: `^\d+\.\d+`, ReplacementMinorVersion: strRef(""), ApplicableClientDBSchemas: "< 6.0.3"}, - // we pass in the distro.Type into the search specifier, not a raw release-id - {Alias: "redhat", VersionPattern: `^\d+\.\d+`, ReplacementMinorVersion: strRef(""), ReplacementName: strRef("rhel"), ApplicableClientDBSchemas: "< 6.0.3"}, - - // alpine family - {Alias: "alpine", VersionPattern: `.*_alpha.*`, ReplacementLabelVersion: strRef("edge"), Rolling: true}, - {Alias: "wolfi", Rolling: true}, - {Alias: "chainguard", Rolling: true}, - - // others - {Alias: "arch", Rolling: true}, - {Alias: "minimos", Rolling: true}, - {Alias: "archlinux", ReplacementName: strRef("arch"), Rolling: true}, // non-standard, but common (dockerhub uses "archlinux") - {Alias: "oracle", ReplacementName: strRef("ol")}, // non-standard, but common - {Alias: "oraclelinux", ReplacementName: strRef("ol")}, // non-standard, but common (dockerhub uses "oraclelinux") - {Alias: "amazon", ReplacementName: strRef("amzn")}, // non-standard, but common - {Alias: "amazonlinux", ReplacementName: strRef("amzn")}, // non-standard, but common (dockerhub uses "amazonlinux") - {Alias: "echo", Rolling: true}, - // TODO: forky is a placeholder for now, but should be updated to sid when the time comes - // this needs to be automated, but isn't clear how to do so since you'll see things like this: - // - // ❯ docker run --rm debian:sid cat /etc/os-release | grep VERSION_CODENAME - // VERSION_CODENAME=forky - // ❯ docker run --rm debian:testing cat /etc/os-release | grep VERSION_CODENAME - // VERSION_CODENAME=forky - // - // ❯ curl -s http://deb.debian.org/debian/dists/testing/Release | grep '^Codename:' - // Codename: forky - // ❯ curl -s http://deb.debian.org/debian/dists/sid/Release | grep '^Codename:' - // Codename: sid - // - // depending where the team is during the development cycle you will see different behavior, making automating - // this a little challenging. - {Alias: "debian", Codename: "forky", Rolling: true, ReplacementLabelVersion: strRef("unstable")}, // is currently sid, which is considered rolling - } -} - -func KnownPackageSpecifierOverrides() []PackageSpecifierOverride { - // when matching packages, grype will always attempt to do so based off of the package type which means - // that any request must be in terms of the package type (relative to syft). - - ret := []PackageSpecifierOverride{ - // map all known language ecosystems to their respective syft package types - {Ecosystem: pkg.Dart.String(), ReplacementEcosystem: ptr(string(pkg.DartPubPkg))}, - {Ecosystem: pkg.Dotnet.String(), ReplacementEcosystem: ptr(string(pkg.DotnetPkg))}, - {Ecosystem: pkg.Elixir.String(), ReplacementEcosystem: ptr(string(pkg.HexPkg))}, - {Ecosystem: pkg.Erlang.String(), ReplacementEcosystem: ptr(string(pkg.ErlangOTPPkg))}, - {Ecosystem: pkg.Go.String(), ReplacementEcosystem: ptr(string(pkg.GoModulePkg))}, - {Ecosystem: pkg.Haskell.String(), ReplacementEcosystem: ptr(string(pkg.HackagePkg))}, - {Ecosystem: pkg.Java.String(), ReplacementEcosystem: ptr(string(pkg.JavaPkg))}, - {Ecosystem: pkg.JavaScript.String(), ReplacementEcosystem: ptr(string(pkg.NpmPkg))}, - {Ecosystem: pkg.Lua.String(), ReplacementEcosystem: ptr(string(pkg.LuaRocksPkg))}, - {Ecosystem: pkg.OCaml.String(), ReplacementEcosystem: ptr(string(pkg.OpamPkg))}, - {Ecosystem: pkg.PHP.String(), ReplacementEcosystem: ptr(string(pkg.PhpComposerPkg))}, - {Ecosystem: pkg.Python.String(), ReplacementEcosystem: ptr(string(pkg.PythonPkg))}, - {Ecosystem: pkg.R.String(), ReplacementEcosystem: ptr(string(pkg.Rpkg))}, - {Ecosystem: pkg.Ruby.String(), ReplacementEcosystem: ptr(string(pkg.GemPkg))}, - {Ecosystem: pkg.Rust.String(), ReplacementEcosystem: ptr(string(pkg.RustPkg))}, - {Ecosystem: pkg.Swift.String(), ReplacementEcosystem: ptr(string(pkg.SwiftPkg))}, - {Ecosystem: pkg.Swipl.String(), ReplacementEcosystem: ptr(string(pkg.SwiplPackPkg))}, - - // jenkins plugins are a special case since they are always considered to be within the java ecosystem - {Ecosystem: string(pkg.JenkinsPluginPkg), ReplacementEcosystem: ptr(string(pkg.JavaPkg))}, - - // legacy cases - {Ecosystem: "pecl", ReplacementEcosystem: ptr(string(pkg.PhpPeclPkg))}, - {Ecosystem: "kb", ReplacementEcosystem: ptr(string(pkg.KbPkg))}, - {Ecosystem: "dpkg", ReplacementEcosystem: ptr(string(pkg.DebPkg))}, - {Ecosystem: "apkg", ReplacementEcosystem: ptr(string(pkg.ApkPkg))}, - } - - // remap package URL types to syft package types - for _, t := range pkg.AllPkgs { - // these types should never be mapped to - // jenkins plugin: java-archive supersedes this - // github action workflow: github-action supersedes this - switch t { - case pkg.JenkinsPluginPkg, pkg.GithubActionWorkflowPkg: - continue - } - - purlType := t.PackageURLType() - if purlType == "" || purlType == string(t) || strings.HasPrefix(purlType, "generic") { - continue - } - - ret = append(ret, PackageSpecifierOverride{ - Ecosystem: purlType, - ReplacementEcosystem: ptr(string(t)), - }) - } - return ret -} - -func ptr[T any](v T) *T { - return &v -} diff --git a/grype/db/v6/db.go b/grype/db/v6/db.go index 844769b3ce6..87d719b98f6 100644 --- a/grype/db/v6/db.go +++ b/grype/db/v6/db.go @@ -125,7 +125,7 @@ func NewLowLevelDB(dbFilePath string, empty, writable, debug bool) (*gorm.DB, er if empty { opts = append(opts, - gormadapter.WithTruncate(true, Models(), InitialData()), + gormadapter.WithTruncate(true, Models(), nil), ) } else if writable { opts = append(opts, gormadapter.WithWritable(true, Models())) diff --git a/grype/db/v6/db_metadata_store_test.go b/grype/db/v6/db_metadata_store_test.go index 048f007b26b..fe82277e922 100644 --- a/grype/db/v6/db_metadata_store_test.go +++ b/grype/db/v6/db_metadata_store_test.go @@ -1,11 +1,14 @@ package v6 import ( + "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" + + "github.com/anchore/syft/syft/pkg" ) func TestDbMetadataStore_empty(t *testing.T) { @@ -74,9 +77,162 @@ func setupTestStore(t testing.TB, d ...string) *store { require.NoError(t, s.SetDBMetadata()) + // Populate test data for grype's matching behavior tests + // In production, this data is managed by grype-db during database construction + require.NoError(t, populateTestOverridesForMatchingTests(s)) + return s } +// populateTestOverridesForMatchingTests populates OS and package aliases solely for testing +// grype's override matching behavior. In production, this data is managed by grype-db. +func populateTestOverridesForMatchingTests(s *store) error { + // Populate OS specifier overrides for testing grype's matching logic + osOverrides := testOperatingSystemSpecifierOverrides() + for i := range osOverrides { + override := &osOverrides[i] + if err := s.db.Create(override).Error; err != nil { + return err + } + } + + // Populate package specifier overrides for testing grype's matching logic + packageOverrides := testPackageSpecifierOverrides() + for i := range packageOverrides { + override := &packageOverrides[i] + if err := s.db.Create(override).Error; err != nil { + return err + } + } + + return nil +} + +// testOperatingSystemSpecifierOverrides returns OS aliases for testing grype's matching behavior. +// NOTE: In production, this data is managed by grype-db during database construction. +func testOperatingSystemSpecifierOverrides() []OperatingSystemSpecifierOverride { + strRef := func(s string) *string { + return &s + } + return []OperatingSystemSpecifierOverride{ + // redhat clones or otherwise shared vulnerability data + {Alias: "centos", ReplacementName: strRef("rhel")}, + {Alias: "rocky", ReplacementName: strRef("rhel")}, + {Alias: "rockylinux", ReplacementName: strRef("rhel")}, // non-standard, but common (dockerhub uses "rockylinux") + {Alias: "alma", ReplacementName: strRef("rhel")}, + {Alias: "almalinux", ReplacementName: strRef("rhel")}, // non-standard, but common (dockerhub uses "almalinux") + {Alias: "gentoo", ReplacementName: strRef("rhel")}, + + // Alternative distros that should match against the debian vulnerability data + {Alias: "raspbian", ReplacementName: strRef("debian")}, + + // to remain backwards compatible, we need to keep old clients from ignoring EUS data. + // we do this by diverting any requests for a specific major.minor version of rhel to only + // use the major version. But, this only applies to clients before v6.0.3 DB schema version. + // Why 6.0.3? This is when OS channel was introduced, which grype-db will leverage, and add additional + // rhel rows to the DB, all which have major.minor versions. This means that any old client (which wont + // see the new channel column) will assume during OS resolution that there is major.minor vuln data + // that should be used (which is incorrect). + {Alias: "rhel", VersionPattern: `^\d+\.\d+`, ReplacementMinorVersion: strRef(""), ApplicableClientDBSchemas: "< 6.0.3"}, + // we pass in the distro.Type into the search specifier, not a raw release-id + {Alias: "redhat", VersionPattern: `^\d+\.\d+`, ReplacementMinorVersion: strRef(""), ReplacementName: strRef("rhel"), ApplicableClientDBSchemas: "< 6.0.3"}, + + // alpine family + {Alias: "alpine", VersionPattern: `.*_alpha.*`, ReplacementLabelVersion: strRef("edge"), Rolling: true}, + {Alias: "wolfi", Rolling: true}, + {Alias: "chainguard", Rolling: true}, + + // others + {Alias: "arch", Rolling: true}, + {Alias: "minimos", Rolling: true}, + {Alias: "archlinux", ReplacementName: strRef("arch"), Rolling: true}, // non-standard, but common (dockerhub uses "archlinux") + {Alias: "oracle", ReplacementName: strRef("ol")}, // non-standard, but common + {Alias: "oraclelinux", ReplacementName: strRef("ol")}, // non-standard, but common (dockerhub uses "oraclelinux") + {Alias: "amazon", ReplacementName: strRef("amzn")}, // non-standard, but common + {Alias: "amazonlinux", ReplacementName: strRef("amzn")}, // non-standard, but common (dockerhub uses "amazonlinux") + {Alias: "echo", Rolling: true}, + // TODO: forky is a placeholder for now, but should be updated to sid when the time comes + // this needs to be automated, but isn't clear how to do so since you'll see things like this: + // + // ❯ docker run --rm debian:sid cat /etc/os-release | grep VERSION_CODENAME + // VERSION_CODENAME=forky + // ❯ docker run --rm debian:testing cat /etc/os-release | grep VERSION_CODENAME + // VERSION_CODENAME=forky + // + // ❯ curl -s http://deb.debian.org/debian/dists/testing/Release | grep '^Codename:' + // Codename: forky + // ❯ curl -s http://deb.debian.org/debian/dists/sid/Release | grep '^Codename:' + // Codename: sid + // + // depending where the team is during the development cycle you will see different behavior, making automating + // this a little challenging. + {Alias: "debian", Codename: "forky", Rolling: true, ReplacementLabelVersion: strRef("unstable")}, // is currently sid, which is considered rolling + } +} + +// testPackageSpecifierOverrides returns package ecosystem aliases for testing grype's matching behavior. +// NOTE: In production, this data is managed by grype-db during database construction. +func testPackageSpecifierOverrides() []PackageSpecifierOverride { + // when matching packages, grype will always attempt to do so based off of the package type which means + // that any request must be in terms of the package type (relative to syft). + + ret := []PackageSpecifierOverride{ + // map all known language ecosystems to their respective syft package types + {Ecosystem: pkg.Dart.String(), ReplacementEcosystem: ptr(string(pkg.DartPubPkg))}, + {Ecosystem: pkg.Dotnet.String(), ReplacementEcosystem: ptr(string(pkg.DotnetPkg))}, + {Ecosystem: pkg.Elixir.String(), ReplacementEcosystem: ptr(string(pkg.HexPkg))}, + {Ecosystem: pkg.Erlang.String(), ReplacementEcosystem: ptr(string(pkg.ErlangOTPPkg))}, + {Ecosystem: pkg.Go.String(), ReplacementEcosystem: ptr(string(pkg.GoModulePkg))}, + {Ecosystem: pkg.Haskell.String(), ReplacementEcosystem: ptr(string(pkg.HackagePkg))}, + {Ecosystem: pkg.Java.String(), ReplacementEcosystem: ptr(string(pkg.JavaPkg))}, + {Ecosystem: pkg.JavaScript.String(), ReplacementEcosystem: ptr(string(pkg.NpmPkg))}, + {Ecosystem: pkg.Lua.String(), ReplacementEcosystem: ptr(string(pkg.LuaRocksPkg))}, + {Ecosystem: pkg.OCaml.String(), ReplacementEcosystem: ptr(string(pkg.OpamPkg))}, + {Ecosystem: pkg.PHP.String(), ReplacementEcosystem: ptr(string(pkg.PhpComposerPkg))}, + {Ecosystem: pkg.Python.String(), ReplacementEcosystem: ptr(string(pkg.PythonPkg))}, + {Ecosystem: pkg.R.String(), ReplacementEcosystem: ptr(string(pkg.Rpkg))}, + {Ecosystem: pkg.Ruby.String(), ReplacementEcosystem: ptr(string(pkg.GemPkg))}, + {Ecosystem: pkg.Rust.String(), ReplacementEcosystem: ptr(string(pkg.RustPkg))}, + {Ecosystem: pkg.Swift.String(), ReplacementEcosystem: ptr(string(pkg.SwiftPkg))}, + {Ecosystem: pkg.Swipl.String(), ReplacementEcosystem: ptr(string(pkg.SwiplPackPkg))}, + + // jenkins plugins are a special case since they are always considered to be within the java ecosystem + {Ecosystem: string(pkg.JenkinsPluginPkg), ReplacementEcosystem: ptr(string(pkg.JavaPkg))}, + + // legacy cases + {Ecosystem: "pecl", ReplacementEcosystem: ptr(string(pkg.PhpPeclPkg))}, + {Ecosystem: "kb", ReplacementEcosystem: ptr(string(pkg.KbPkg))}, + {Ecosystem: "dpkg", ReplacementEcosystem: ptr(string(pkg.DebPkg))}, + {Ecosystem: "apkg", ReplacementEcosystem: ptr(string(pkg.ApkPkg))}, + } + + // remap package URL types to syft package types + for _, t := range pkg.AllPkgs { + // these types should never be mapped to + // jenkins plugin: java-archive supersedes this + // github action workflow: github-action supersedes this + switch t { + case pkg.JenkinsPluginPkg, pkg.GithubActionWorkflowPkg: + continue + } + + purlType := t.PackageURLType() + if purlType == "" || purlType == string(t) || strings.HasPrefix(purlType, "generic") { + continue + } + + ret = append(ret, PackageSpecifierOverride{ + Ecosystem: purlType, + ReplacementEcosystem: ptr(string(t)), + }) + } + return ret +} + +func ptr[T any](v T) *T { + return &v +} + func setupReadOnlyTestStore(t testing.TB, dir string) *store { s, err := newStore(Config{ DBDirPath: dir, diff --git a/grype/db/v6/store.go b/grype/db/v6/store.go index b48ee4ab319..b3786c5db11 100644 --- a/grype/db/v6/store.go +++ b/grype/db/v6/store.go @@ -32,20 +32,6 @@ func (s *store) attachBlobValue(values ...blobable) error { return s.blobStore.attachBlobValue(values...) } -func InitialData() []any { - var data []any - os := KnownOperatingSystemSpecifierOverrides() - for i := range os { - data = append(data, &os[i]) - } - - p := KnownPackageSpecifierOverrides() - for i := range p { - data = append(data, &p[i]) - } - return data -} - func newStore(cfg Config, empty, writable bool) (*store, error) { var path string if cfg.DBDirPath != "" {