Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions pkg/data/v6/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package v6

import (
"strings"

grypeDB "github.com/anchore/grype/grype/db/v6"
"github.com/anchore/syft/syft/pkg"
)

// KnownOperatingSystemSpecifierOverrides returns the default set of OS alias rules
// used during vulnerability database construction and querying.
func KnownOperatingSystemSpecifierOverrides() []grypeDB.OperatingSystemSpecifierOverride {
strRef := func(s string) *string {
return &s
}
return []grypeDB.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")},

// 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: "raspbian", ReplacementName: strRef("debian")}, // raspbian shares debian vulnerability data
{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
}
}

// KnownPackageSpecifierOverrides returns the default set of package ecosystem alias rules
// used during vulnerability database construction and querying.
func KnownPackageSpecifierOverrides() []grypeDB.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 := []grypeDB.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, grypeDB.PackageSpecifierOverride{
Ecosystem: purlType,
ReplacementEcosystem: ptr(string(t)),
})
}
return ret
}

// ptr returns a pointer to the given value
func ptr[T any](v T) *T {
return &v
}
94 changes: 94 additions & 0 deletions pkg/data/v6/data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package v6

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

grypeDB "github.com/anchore/grype/grype/db/v6"
)

func TestKnownOperatingSystemSpecifierOverrides(t *testing.T) {
overrides := KnownOperatingSystemSpecifierOverrides()
require.NotEmpty(t, overrides, "should have at least some OS overrides")

// Test that we have some expected aliases
aliasMap := make(map[string]grypeDB.OperatingSystemSpecifierOverride)
for _, override := range overrides {
aliasMap[override.Alias] = override
}

// Test RHEL aliases
centos, exists := aliasMap["centos"]
require.True(t, exists, "should have centos alias")
require.NotNil(t, centos.ReplacementName)
assert.Equal(t, "rhel", *centos.ReplacementName)

alma, exists := aliasMap["alma"]
require.True(t, exists, "should have alma alias")
require.NotNil(t, alma.ReplacementName)
assert.Equal(t, "rhel", *alma.ReplacementName)

almalinux, exists := aliasMap["almalinux"]
require.True(t, exists, "should have almalinux alias")
require.NotNil(t, almalinux.ReplacementName)
assert.Equal(t, "rhel", *almalinux.ReplacementName)

// Test rolling releases
arch, exists := aliasMap["arch"]
require.True(t, exists, "should have arch alias")
assert.True(t, arch.Rolling, "arch should be marked as rolling")

wolfi, exists := aliasMap["wolfi"]
require.True(t, exists, "should have wolfi alias")
assert.True(t, wolfi.Rolling, "wolfi should be marked as rolling")

// Test that version and version_pattern are mutually exclusive
for _, override := range overrides {
assert.False(t, override.Version != "" && override.VersionPattern != "",
"override %s should not have both version and version_pattern set", override.Alias)
}
}

func TestKnownPackageSpecifierOverrides(t *testing.T) {
overrides := KnownPackageSpecifierOverrides()
require.NotEmpty(t, overrides, "should have at least some package overrides")

// Test that we have some expected ecosystems
ecosystemMap := make(map[string]grypeDB.PackageSpecifierOverride)
for _, override := range overrides {
ecosystemMap[override.Ecosystem] = override
}

// Test some expected language ecosystems
python, exists := ecosystemMap["python"]
require.True(t, exists, "should have python ecosystem")
require.NotNil(t, python.ReplacementEcosystem)

java, exists := ecosystemMap["java"]
require.True(t, exists, "should have java ecosystem")
require.NotNil(t, java.ReplacementEcosystem)

// Test legacy cases
dpkg, exists := ecosystemMap["dpkg"]
require.True(t, exists, "should have dpkg legacy ecosystem")
require.NotNil(t, dpkg.ReplacementEcosystem)

apkg, exists := ecosystemMap["apkg"]
require.True(t, exists, "should have apkg legacy ecosystem")
require.NotNil(t, apkg.ReplacementEcosystem)
}

func TestPtr(t *testing.T) {
// Test that ptr function works correctly
s := "test"
p := ptr(s)
require.NotNil(t, p)
assert.Equal(t, s, *p)

i := 42
pi := ptr(i)
require.NotNil(t, pi)
assert.Equal(t, i, *pi)
}
13 changes: 10 additions & 3 deletions pkg/process/v6/transformers/os/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,18 @@ import (
)

func Transform(vulnerability unmarshal.OSVulnerability, state provider.State) ([]data.Entry, error) {
providerID := state.Provider
providerModel := internal.ProviderModel(state)
if state.Provider == "rhel" && vulnerability.Vulnerability.NamespaceName != "" && strings.Contains(vulnerability.Vulnerability.NamespaceName, ":") {
// the RHEL provider can emit AlmaLinux vulnerabilities
providerID = strings.Split(vulnerability.Vulnerability.NamespaceName, ":")[0]
providerModel.ID = providerID
}
in := []any{
grypeDB.VulnerabilityHandle{
Name: vulnerability.Vulnerability.Name,
ProviderID: state.Provider,
Provider: internal.ProviderModel(state),
ProviderID: providerID,
Provider: providerModel,
Status: grypeDB.VulnerabilityActive,
ModifiedDate: internal.ParseTime(vulnerability.Vulnerability.Metadata.Updated),
PublishedDate: internal.ParseTime(vulnerability.Vulnerability.Metadata.Issued),
Expand Down Expand Up @@ -232,7 +239,7 @@ func groupFixedIns(vuln unmarshal.OSVulnerability) map[groupIndex][]unmarshal.OS

func getPackageType(osName string) pkg.Type {
switch osName {
case "redhat", "amazonlinux", "oraclelinux", "sles", "mariner", "azurelinux":
case "redhat", "amazonlinux", "oraclelinux", "sles", "mariner", "azurelinux", "almalinux":
return pkg.RpmPkg
case "ubuntu", "debian", "echo":
return pkg.DebPkg
Expand Down
Loading