Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(rocky): add multi-arch support #321

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8a63aed
add support of fixedVersions struct for Rocky Linux
DmitriyLewen Jun 21, 2023
d500e5f
remove Flavers. Will be added in PR for OracleLinux
DmitriyLewen Jun 21, 2023
14b1e69
refactor
DmitriyLewen Jun 22, 2023
f7e1d98
test: rename test files name to fix import errors
DmitriyLewen Jun 22, 2023
d18d630
refactor: reuse types.Advisory
knqyf263 Jun 22, 2023
d6514cb
refactor: remove empty advisories
knqyf263 Jun 22, 2023
3451105
test: add Get tests
knqyf263 Jun 22, 2023
7f754b8
tidy
knqyf263 Jun 22, 2023
3a813b8
fix: sort arch and vendor ids
knqyf263 Jun 22, 2023
f85ee28
refactor: move rocky.Advisory to types.Advisories
DmitriyLewen Jun 27, 2023
b0cc897
test: add tests
DmitriyLewen Jun 27, 2023
b0b1de8
Merge branch 'main' of github.com:DmitriyLewen/trivy-db into feat/fix…
DmitriyLewen Jun 27, 2023
1d3a6c6
fix: keep backward compatibility
knqyf263 Jun 29, 2023
6915173
test: replace ":" with "-"
knqyf263 Jun 29, 2023
b7cd082
fix: add data source
knqyf263 Jun 29, 2023
686353b
fix tests
DmitriyLewen Jun 30, 2023
35ef1fe
remove datasource for redhat-oval. Will be added in another PR
DmitriyLewen Jun 30, 2023
6cb4d22
refactor testdata fixtures
DmitriyLewen Jun 30, 2023
b3e77c1
Merge branch 'main' of github.com:DmitriyLewen/trivy-db into feat/fix…
DmitriyLewen Jul 3, 2023
26d271a
use `0.0.0` version for non-`x86_64`/`noarch` arch in advisories.fixe…
DmitriyLewen Jul 3, 2023
84f0bd3
refactor: add comment
DmitriyLewen Jul 3, 2023
9ed16fa
refactor: change dir for noarch test
DmitriyLewen Jul 3, 2023
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/hashicorp/go-version v1.6.0
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
github.com/samber/lo v1.38.1
github.com/stretchr/testify v1.8.4
github.com/urfave/cli v1.22.14
go.etcd.io/bbolt v1.3.7
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
Expand Down
3 changes: 3 additions & 0 deletions pkg/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ func (dbc Config) forEach(bktNames []string) (map[string]Value, error) {
}

err = bkt.ForEach(func(k, v []byte) error {
if len(v) == 0 {
return nil
}
// Copy the byte slice so it can be used outside of the current transaction
copiedContent := make([]byte, len(v))
copy(copiedContent, v)
Expand Down
11 changes: 8 additions & 3 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,7 @@ type Advisory struct {
VulnerabilityID string `json:",omitempty"` // CVE-ID or vendor ID
VendorIDs []string `json:",omitempty"` // e.g. RHSA-ID and DSA-ID

// Rpm packages have advisories for different architectures with same package name
// This field is required to separate these packages.
Arches []string `json:"-"`
Arches []string `json:",omitempty"`

// It is filled only when FixedVersion is empty since it is obvious the state is "Fixed" when FixedVersion is not empty.
// e.g. Will not fix and Affected
Expand Down Expand Up @@ -129,6 +127,13 @@ type Advisory struct {
Custom interface{} `json:",omitempty"`
}

// Advisories saves fixed versions for each arches/vendorIDs
// e.g. this is required when CVE has different fixed versions for different arches
type Advisories struct {
FixedVersion string `json:",omitempty"` // For backward compatibility
Entries []Advisory `json:",omitempty"`
}

type Vulnerability struct {
Title string `json:",omitempty"`
Description string `json:",omitempty"`
Expand Down
4 changes: 0 additions & 4 deletions pkg/vulnsrc/redhat-oval/redhat-oval.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,6 @@ func (vs VulnSrc) Get(pkgName string, repositories, nvrs []string) ([]types.Advi

var advisories []types.Advisory
for vulnID, v := range rawAdvisories {
if len(v.Content) == 0 {
continue
}

var adv Advisory
if err = json.Unmarshal(v.Content, &adv); err != nil {
return nil, xerrors.Errorf("failed to unmarshal advisory JSON: %w", err)
Expand Down
142 changes: 115 additions & 27 deletions pkg/vulnsrc/rocky/rocky.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import (
"io"
"log"
"path/filepath"
"sort"
"strings"

"github.com/samber/lo"
bolt "go.etcd.io/bbolt"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"

"github.com/aquasecurity/trivy-db/pkg/db"
Expand All @@ -24,9 +27,16 @@ const (
)

var (
targetRepos = []string{"BaseOS", "AppStream", "extras"}
targetArches = []string{"x86_64"}
source = types.DataSource{
targetRepos = []string{
"BaseOS",
"AppStream",
"extras",
}
targetArches = []string{
"x86_64",
"aarch64",
}
source = types.DataSource{
ID: vulnerability.Rocky,
Name: "Rocky Linux updateinfo",
URL: "https://download.rockylinux.org/pub/rocky/",
Expand All @@ -37,14 +47,14 @@ type PutInput struct {
PlatformName string
CveID string
Vuln types.VulnerabilityDetail
Advisories map[string]types.Advisory // pkg name => advisory
Erratum RLSA // for extensibility, not used in trivy-db
Advisories map[string]types.Advisories // pkg name => advisory
Erratum RLSA // for extensibility, not used in trivy-db
}

type DB interface {
db.Operation
Put(*bolt.Tx, PutInput) error
Get(release, pkgName string) ([]types.Advisory, error)
Get(release, pkgName, arch string) ([]types.Advisory, error)
}

type VulnSrc struct {
Expand Down Expand Up @@ -106,11 +116,7 @@ func (vs *VulnSrc) parse(rootDir string) (map[string][]RLSA, error) {
}

if !ustrings.InSlice(arch, targetArches) {
switch arch {
case "aarch64":
default:
log.Printf("Unsupported Rocky arch: %s", arch)
}
log.Printf("Unsupported Rocky arch: %s", arch)
return nil
}

Expand Down Expand Up @@ -143,22 +149,61 @@ func (vs *VulnSrc) put(errataVer map[string][]RLSA) error {
}

func (vs *VulnSrc) commit(tx *bolt.Tx, platformName string, errata []RLSA) error {
savedInputs := map[string]PutInput{}
for _, erratum := range errata {
for _, cveID := range erratum.CveIDs {
advisories := map[string]types.Advisory{}
input := PutInput{
Advisories: map[string]types.Advisories{},
}
if in, ok := savedInputs[cveID]; ok {
input = in
}
for _, pkg := range erratum.Packages {
// Skip the modular packages until the following bug is fixed.
// https://forums.rockylinux.org/t/some-errata-missing-in-comparison-with-rhel-and-almalinux/3843/8
if strings.Contains(pkg.Release, ".module+el") {
continue
}

advisories[pkg.Name] = types.Advisory{
entry := types.Advisory{
FixedVersion: utils.ConstructVersion(pkg.Epoch, pkg.Version, pkg.Release),
Arches: []string{pkg.Arch},
VendorIDs: []string{erratum.ID},
}

// if the advisory for this package and CVE have been kept - just add the new architecture
if adv, ok := input.Advisories[pkg.Name]; ok {
// update `fixedVersion` if `fixedVersion` for `x86_64` was not previously saved
adv.FixedVersion = fixedVersion(adv.FixedVersion, entry.FixedVersion, pkg.Arch)

old, i, found := lo.FindIndexOf(adv.Entries, func(adv types.Advisory) bool {
return adv.FixedVersion == entry.FixedVersion
})

// If the advisory with the same fixed version and RLSA-ID is present - just add the new architecture
if found {
if !slices.Contains(old.Arches, pkg.Arch) {
adv.Entries[i].Arches = append(old.Arches, pkg.Arch)
}
if !slices.Contains(old.VendorIDs, erratum.ID) {
adv.Entries[i].VendorIDs = append(old.VendorIDs, erratum.ID)
}
input.Advisories[pkg.Name] = adv
} else if !found {
adv.Entries = append(adv.Entries, entry)
input.Advisories[pkg.Name] = adv
}
} else {
input.Advisories[pkg.Name] = types.Advisories{
// will save `0.0.0` version for non-`x86_64` arch
// to avoid false positives when using old Trivy with new database
FixedVersion: fixedVersion("0.0.0", entry.FixedVersion, pkg.Arch), // For backward compatibility
Entries: []types.Advisory{entry},
}
}
}

if len(advisories) == 0 {
if len(input.Advisories) == 0 {
continue
}

Expand All @@ -174,16 +219,18 @@ func (vs *VulnSrc) commit(tx *bolt.Tx, platformName string, errata []RLSA) error
Description: erratum.Description,
}

err := vs.Put(tx, PutInput{
PlatformName: platformName,
CveID: cveID,
Vuln: vuln,
Advisories: advisories,
Erratum: erratum,
})
if err != nil {
return xerrors.Errorf("db put error: %w", err)
}
input.PlatformName = platformName
input.CveID = cveID
input.Vuln = vuln

savedInputs[cveID] = input
}
}

for _, input := range savedInputs {
err := vs.Put(tx, input)
if err != nil {
return xerrors.Errorf("db put error: %w", err)
}
}
return nil
Expand All @@ -200,19 +247,51 @@ func (r *Rocky) Put(tx *bolt.Tx, input PutInput) error {
}

for pkgName, advisory := range input.Advisories {
for _, entry := range advisory.Entries {
sort.Strings(entry.Arches)
sort.Strings(entry.VendorIDs)
}
if err := r.PutAdvisoryDetail(tx, input.CveID, pkgName, []string{input.PlatformName}, advisory); err != nil {
return xerrors.Errorf("failed to save Rocky advisory: %w", err)
}
}
return nil
}

func (r *Rocky) Get(release, pkgName string) ([]types.Advisory, error) {
func (r *Rocky) Get(release, pkgName, arch string) ([]types.Advisory, error) {
bucket := fmt.Sprintf(platformFormat, release)
advisories, err := r.GetAdvisories(bucket, pkgName)
rawAdvisories, err := r.ForEachAdvisory([]string{bucket}, pkgName)
if err != nil {
return nil, xerrors.Errorf("failed to get Rocky advisories: %w", err)
return nil, xerrors.Errorf("unable to iterate advisories: %w", err)
}
var advisories []types.Advisory
for vulnID, v := range rawAdvisories {
var adv types.Advisories
if err = json.Unmarshal(v.Content, &adv); err != nil {
return nil, xerrors.Errorf("failed to unmarshal advisory JSON: %w", err)
}

// For backward compatibility
// The old trivy-db has no entries, but has fixed versions only.
if len(adv.Entries) == 0 {
advisories = append(advisories, types.Advisory{
VulnerabilityID: vulnID,
FixedVersion: adv.FixedVersion,
DataSource: &v.Source,
})
continue
}

for _, entry := range adv.Entries {
if !slices.Contains(entry.Arches, arch) {
continue
}
entry.VulnerabilityID = vulnID
entry.DataSource = &v.Source
advisories = append(advisories, entry)
}
}

return advisories, nil
}

Expand All @@ -229,3 +308,12 @@ func generalizeSeverity(severity string) types.Severity {
}
return types.SeverityUnknown
}

// fixedVersion checks for the arch and only updates version for `x86_64`
// only used for types.Advisories.FixedVersion for backward compatibility
func fixedVersion(prevVersion, newVersion, arch string) string {
if arch == "x86_64" || arch == "noarch" {
return newVersion
}
return prevVersion
}
Loading