From fcea559f8075f1aae0a4f34a0e0de519225fe01e Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 2 Aug 2022 11:49:11 +0600 Subject: [PATCH 1/2] add rejected vulnerability check for redhat --- pkg/vulndb/db.go | 2 +- pkg/vulnsrc/redhat-oval/redhat-oval.go | 19 ++++++++++++++----- pkg/vulnsrc/vulnerability/vulnerability.go | 2 +- .../vulnerability/vulnerability_test.go | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/pkg/vulndb/db.go b/pkg/vulndb/db.go index 30a8d569..71fb1b6e 100644 --- a/pkg/vulndb/db.go +++ b/pkg/vulndb/db.go @@ -129,7 +129,7 @@ func (t TrivyDB) optimize() error { // This bucket has only vulnerability IDs provided by vendors. They must be stored. err := t.dbc.ForEachVulnerabilityID(func(tx *bolt.Tx, cveID string) error { details := t.vulnClient.GetDetails(cveID) - if t.vulnClient.IsRejected(details) { + if vulnerability.IsRejected(details) { return nil } diff --git a/pkg/vulnsrc/redhat-oval/redhat-oval.go b/pkg/vulnsrc/redhat-oval/redhat-oval.go index db5ce778..1f0ddfa5 100644 --- a/pkg/vulnsrc/redhat-oval/redhat-oval.go +++ b/pkg/vulnsrc/redhat-oval/redhat-oval.go @@ -85,7 +85,7 @@ func (vs VulnSrc) Update(dir string) error { continue } - definitions, err := parseOVALStream(filepath.Join(versionDir, f.Name()), uniqCPEs) + definitions, err := vs.parseOVALStream(filepath.Join(versionDir, f.Name()), uniqCPEs) if err != nil { return xerrors.Errorf("failed to parse OVAL stream: %w", err) } @@ -288,7 +288,7 @@ func (vs VulnSrc) Get(pkgName string, repositories, nvrs []string) ([]types.Advi return advisories, nil } -func parseOVALStream(dir string, uniqCPEs CPEMap) (map[bucket]Definition, error) { +func (vs VulnSrc) parseOVALStream(dir string, uniqCPEs CPEMap) (map[bucket]Definition, error) { log.Printf(" Parsing %s", dir) // Parse tests @@ -316,10 +316,10 @@ func parseOVALStream(dir string, uniqCPEs CPEMap) (map[bucket]Definition, error) return nil, xerrors.Errorf("Red Hat OVAL walk error: %w", err) } - return parseDefinitions(advisories, tests, uniqCPEs), nil + return vs.parseDefinitions(advisories, tests, uniqCPEs) } -func parseDefinitions(advisories []redhatOVAL, tests map[string]rpmInfoTest, uniqCPEs CPEMap) map[bucket]Definition { +func (vs VulnSrc) parseDefinitions(advisories []redhatOVAL, tests map[string]rpmInfoTest, uniqCPEs CPEMap) (map[bucket]Definition, error) { defs := map[bucket]Definition{} for _, advisory := range advisories { @@ -342,6 +342,15 @@ func parseDefinitions(advisories []redhatOVAL, tests map[string]rpmInfoTest, uni var cveEntries []CveEntry for _, cve := range advisory.Metadata.Advisory.Cves { + // get details from NVD + details, err := vs.dbc.GetVulnerabilityDetail(cve.CveID) + if err != nil { + return nil, xerrors.Errorf("Failed to get vulnerability detail: %s", err) + } + // don't use rejected vulnerabilities + if vulnerability.IsRejected(details) { + continue + } cveEntries = append(cveEntries, CveEntry{ ID: cve.CveID, Severity: severityFromImpact(cve.Impact), @@ -386,7 +395,7 @@ func parseDefinitions(advisories []redhatOVAL, tests map[string]rpmInfoTest, uni updateCPEs(advisory.Metadata.Advisory.AffectedCpeList, uniqCPEs) } - return defs + return defs, nil } func walkCriterion(cri criteria, tests map[string]rpmInfoTest) (string, []pkg) { diff --git a/pkg/vulnsrc/vulnerability/vulnerability.go b/pkg/vulnsrc/vulnerability/vulnerability.go index dffb2e45..8205255c 100644 --- a/pkg/vulnsrc/vulnerability/vulnerability.go +++ b/pkg/vulnsrc/vulnerability/vulnerability.go @@ -38,7 +38,7 @@ func (v Vulnerability) GetDetails(vulnID string) map[types.SourceID]types.Vulner return details } -func (Vulnerability) IsRejected(details map[types.SourceID]types.VulnerabilityDetail) bool { +func IsRejected(details map[types.SourceID]types.VulnerabilityDetail) bool { return getRejectedStatus(details) } diff --git a/pkg/vulnsrc/vulnerability/vulnerability_test.go b/pkg/vulnsrc/vulnerability/vulnerability_test.go index c24eff73..fead56cd 100644 --- a/pkg/vulnsrc/vulnerability/vulnerability_test.go +++ b/pkg/vulnsrc/vulnerability/vulnerability_test.go @@ -126,7 +126,7 @@ func TestIsRejected(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - got := vulnerability.New(nil).IsRejected(tc.details) + got := vulnerability.IsRejected(tc.details) assert.Equal(t, tc.want, got) }) } From 03e5c844a77f00303a550476af124d753616575e Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 8 Aug 2022 16:18:45 +0600 Subject: [PATCH 2/2] check of rejected cves for red hat moved to optimization --- pkg/db/advisory_detail.go | 64 +++++++++++++ pkg/db/advisory_detail_test.go | 91 ++++++++++++++++++ .../fixtures/rhsa-advisory-detail.yaml | 18 ++++ pkg/types/types.go | 3 +- pkg/vulndb/db.go | 29 +++++- pkg/vulnsrc/redhat-oval/parse.go | 15 +-- pkg/vulnsrc/redhat-oval/redhat-oval.go | 92 ++++++++++--------- pkg/vulnsrc/redhat-oval/redhat-oval_test.go | 33 +++---- pkg/vulnsrc/redhat-oval/{ => types}/types.go | 92 +++++++++---------- pkg/vulnsrc/vulnerability/vulnerability.go | 2 +- .../vulnerability/vulnerability_test.go | 2 +- 11 files changed, 322 insertions(+), 119 deletions(-) create mode 100644 pkg/db/testdata/fixtures/rhsa-advisory-detail.yaml rename pkg/vulnsrc/redhat-oval/{ => types}/types.go (62%) diff --git a/pkg/db/advisory_detail.go b/pkg/db/advisory_detail.go index 410ffe02..a3f920bc 100644 --- a/pkg/db/advisory_detail.go +++ b/pkg/db/advisory_detail.go @@ -3,6 +3,8 @@ package db import ( "encoding/json" + ustrings "github.com/aquasecurity/trivy-db/pkg/utils/strings" + redhatovaltypes "github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat-oval/types" bolt "go.etcd.io/bbolt" "golang.org/x/xerrors" @@ -77,3 +79,65 @@ func (dbc Config) saveAdvisories(tx *bolt.Tx, bkt *bolt.Bucket, bktNames []strin func (dbc Config) DeleteAdvisoryDetailBucket() error { return dbc.deleteBucket(advisoryDetailBucket) } + +// SaveRhsaAdvisoryDetails Extract 'RHSA' advisories from 'advisory-detail'-'Red Hat' bucket and copy them in each +func (dbc Config) SaveRhsaAdvisoryDetails(tx *bolt.Tx, vulnID string, rejectedCve []string) error { + root := tx.Bucket([]byte(advisoryDetailBucket)) + if root == nil { + return nil + } + + cveBucket := root.Bucket([]byte(vulnID)) + if cveBucket == nil { + return nil + } + + redHatBucket := cveBucket.Bucket([]byte("Red Hat")) + if redHatBucket == nil { + return nil + } + + if err := dbc.saveRhsaAdvisories(tx, redHatBucket, []string{"Red Hat"}, vulnID, rejectedCve); err != nil { + return xerrors.Errorf("walk advisories error: %w", err) + } + + return nil +} + +// saveRhsaAdvisories walks all RHSA 'advisory-detail' bucket, removes the rejected cve's from the cve List and copy them in each Red Hat bucket. +func (dbc Config) saveRhsaAdvisories(tx *bolt.Tx, bkt *bolt.Bucket, bktNames []string, vulnID string, rejectedCve []string) error { + if bkt == nil { + return nil + } + + err := bkt.ForEach(func(k, v []byte) error { + advisory := redhatovaltypes.Advisory{} + if err := json.Unmarshal(v, &advisory); err != nil { + return xerrors.Errorf("failed to unmarshall the advisory detail: %w", err) + } + + for i, entry := range advisory.Entries { + var cves []redhatovaltypes.CveEntry + for _, cve := range entry.Cves { + // Include in the database only non-rejected Cves + if !ustrings.InSlice(cve.ID, rejectedCve) { + cves = append(cves, cve) + } + } + advisory.Entries[i].Cves = cves + } + + // Save advisory + bkts := append(bktNames, string(k)) + if err := dbc.put(tx, bkts, vulnID, advisory); err != nil { + return xerrors.Errorf("database put error: %w", err) + } + + return nil + }) + if err != nil { + return xerrors.Errorf("foreach error: %w", err) + } + + return nil +} diff --git a/pkg/db/advisory_detail_test.go b/pkg/db/advisory_detail_test.go index 70deeac7..3c2b6baa 100644 --- a/pkg/db/advisory_detail_test.go +++ b/pkg/db/advisory_detail_test.go @@ -6,6 +6,7 @@ import ( bolt "go.etcd.io/bbolt" "github.com/aquasecurity/trivy-db/pkg/types" + redhatovaltypes "github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat-oval/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -83,3 +84,93 @@ func TestConfig_SaveAdvisoryDetails(t *testing.T) { }) } } + +func TestConfig_SaveRhsaAdvisoryDetails(t *testing.T) { + type want struct { + key []string + value redhatovaltypes.Advisory + } + tests := []struct { + name string + fixtures []string + vulnID string + rejectedCves []string + want []want + wantErr string + }{ + { + name: "happy path without reject cves", + fixtures: []string{"testdata/fixtures/rhsa-advisory-detail.yaml"}, + vulnID: "RHSA-2021:4151", + want: []want{ + { + key: []string{"Red Hat", "python2", "RHSA-2021:4151"}, + value: redhatovaltypes.Advisory{ + Entries: []redhatovaltypes.Entry{ + { + Cves: []redhatovaltypes.CveEntry{ + { + ID: "CVE-2020-28493", + Severity: 1, + }, + { + ID: "CVE-2021-20095", + Severity: 2, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path with reject cves", + fixtures: []string{"testdata/fixtures/rhsa-advisory-detail.yaml"}, + vulnID: "RHSA-2021:4151", + rejectedCves: []string{"CVE-2021-20095"}, + want: []want{ + { + key: []string{"Red Hat", "python2", "RHSA-2021:4151"}, + value: redhatovaltypes.Advisory{ + Entries: []redhatovaltypes.Entry{ + { + Cves: []redhatovaltypes.CveEntry{ + { + ID: "CVE-2020-28493", + Severity: 1, + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize DB for testing + tmpDir := dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + dbc := db.Config{} + err := dbc.BatchUpdate(func(tx *bolt.Tx) error { + return dbc.SaveRhsaAdvisoryDetails(tx, tt.vulnID, tt.rejectedCves) + }) + + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + require.NoError(t, err) + require.NoError(t, db.Close()) // Need to close before dbtest.JSONEq is called + for _, w := range tt.want { + dbtest.JSONEq(t, db.Path(tmpDir), w.key, w.value) + } + }) + } +} diff --git a/pkg/db/testdata/fixtures/rhsa-advisory-detail.yaml b/pkg/db/testdata/fixtures/rhsa-advisory-detail.yaml new file mode 100644 index 00000000..4cff21de --- /dev/null +++ b/pkg/db/testdata/fixtures/rhsa-advisory-detail.yaml @@ -0,0 +1,18 @@ +- bucket: advisory-detail + pairs: + - bucket: RHSA-2021:4151 + pairs: + - bucket: Red Hat + pairs: + - key: python2 + value: + Entries: + - Cves: + - + ID: CVE-2020-28493 + Severity: 1 + - + ID: CVE-2021-20095 + Severity: 2 + + diff --git a/pkg/types/types.go b/pkg/types/types.go index 5cda81b8..d00b8e4e 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -64,7 +64,8 @@ type LastUpdated struct { Date time.Time } type VulnerabilityDetail struct { - ID string `json:",omitempty"` // e.g. CVE-2019-8331, OSVDB-104365 + ID string `json:",omitempty"` // e.g. CVE-2019-8331, OSVDB-104365, RHSA-2021:4151 + CveIDs []string `json:",omitempty"` // e.g. ["CVE-2020-27619", "CVE-2020-28493" ...] for RHSA-2021:4151 CvssScore float64 `json:",omitempty"` CvssVector string `json:",omitempty"` CvssScoreV3 float64 `json:",omitempty"` diff --git a/pkg/vulndb/db.go b/pkg/vulndb/db.go index 71fb1b6e..0b793a86 100644 --- a/pkg/vulndb/db.go +++ b/pkg/vulndb/db.go @@ -2,6 +2,7 @@ package vulndb import ( "log" + "strings" "time" bolt "go.etcd.io/bbolt" @@ -129,12 +130,32 @@ func (t TrivyDB) optimize() error { // This bucket has only vulnerability IDs provided by vendors. They must be stored. err := t.dbc.ForEachVulnerabilityID(func(tx *bolt.Tx, cveID string) error { details := t.vulnClient.GetDetails(cveID) - if vulnerability.IsRejected(details) { + cves := details[vulnerability.RedHatOVAL].CveIDs + if t.vulnClient.IsRejected(details) { return nil } - - if err := t.dbc.SaveAdvisoryDetails(tx, cveID); err != nil { - return xerrors.Errorf("failed to save advisories: %w", err) + // Red Hat RHSA advisories include several CVEs. + // Part of these CVEs can be rejected. + // Keep RHSA advisories separate from all Cves. + // Because we must remove rejected Cves first. + if strings.HasPrefix(cveID, "RHSA") && len(cves) > 0 { + var rejectedCves []string + for _, cve := range cves { + if t.vulnClient.IsRejected(t.vulnClient.GetDetails(cve)) { + rejectedCves = append(rejectedCves, cve) + } + } + // if all entry CVEs are rejected - do not save this CVE + if len(rejectedCves) == len(cves) { + return nil + } + if err := t.dbc.SaveRhsaAdvisoryDetails(tx, cveID, rejectedCves); err != nil { + return xerrors.Errorf("unable to save RHSA advisories: %w", err) + } + } else { + if err := t.dbc.SaveAdvisoryDetails(tx, cveID); err != nil { + return xerrors.Errorf("failed to save advisories: %w", err) + } } if len(details) == 0 { diff --git a/pkg/vulnsrc/redhat-oval/parse.go b/pkg/vulnsrc/redhat-oval/parse.go index 50b4d8b7..edbca479 100644 --- a/pkg/vulnsrc/redhat-oval/parse.go +++ b/pkg/vulnsrc/redhat-oval/parse.go @@ -5,12 +5,13 @@ import ( "os" "path/filepath" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat-oval/types" "golang.org/x/xerrors" ) type rpmInfoTest struct { Name string - SignatureKeyID signatureKeyID + SignatureKeyID types.SignatureKeyID FixedVersion string Arch string } @@ -29,7 +30,7 @@ func unmarshalJSONFile(v interface{}, fileName string) error { } func parseObjects(dir string) (map[string]string, error) { - var objects ovalObjects + var objects types.OvalObjects if err := unmarshalJSONFile(&objects, filepath.Join(dir, "objects", "objects.json")); err != nil { return nil, xerrors.Errorf("failed to unmarshal objects: %w", err) } @@ -40,13 +41,13 @@ func parseObjects(dir string) (map[string]string, error) { return objs, nil } -func parseStates(dir string) (map[string]rpminfoState, error) { - var ss ovalStates +func parseStates(dir string) (map[string]types.RpminfoState, error) { + var ss types.OvalStates if err := unmarshalJSONFile(&ss, filepath.Join(dir, "states", "states.json")); err != nil { return nil, xerrors.Errorf("failed to unmarshal states: %w", err) } - states := map[string]rpminfoState{} + states := map[string]types.RpminfoState{} for _, state := range ss.RpminfoState { states[state.ID] = state } @@ -64,7 +65,7 @@ func parseTests(dir string) (map[string]rpmInfoTest, error) { return nil, xerrors.Errorf("failed to parse states: %w", err) } - var tt ovalTests + var tt types.OvalTests if err := unmarshalJSONFile(&tt, filepath.Join(dir, "tests", "tests.json")); err != nil { return nil, xerrors.Errorf("failed to unmarshal states: %w", err) } @@ -85,7 +86,7 @@ func parseTests(dir string) (map[string]rpmInfoTest, error) { return tests, nil } -func followTestRefs(test rpminfoTest, objects map[string]string, states map[string]rpminfoState) (rpmInfoTest, error) { +func followTestRefs(test types.RpminfoTest, objects map[string]string, states map[string]types.RpminfoState) (rpmInfoTest, error) { var t rpmInfoTest // Follow object ref diff --git a/pkg/vulnsrc/redhat-oval/redhat-oval.go b/pkg/vulnsrc/redhat-oval/redhat-oval.go index 1f0ddfa5..10531735 100644 --- a/pkg/vulnsrc/redhat-oval/redhat-oval.go +++ b/pkg/vulnsrc/redhat-oval/redhat-oval.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/aquasecurity/trivy-db/pkg/utils/ints" + redhatovaltypes "github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat-oval/types" bolt "go.etcd.io/bbolt" "golang.org/x/xerrors" @@ -72,7 +73,7 @@ func (vs VulnSrc) Update(dir string) error { return xerrors.Errorf("unable to list directory entries (%s): %w", rootDir, err) } - advisories := map[bucket]Advisory{} + advisories := map[redhatovaltypes.Bucket]redhatovaltypes.Advisory{} for _, ver := range versions { versionDir := filepath.Join(rootDir, ver.Name()) streams, err := os.ReadDir(versionDir) @@ -85,7 +86,7 @@ func (vs VulnSrc) Update(dir string) error { continue } - definitions, err := vs.parseOVALStream(filepath.Join(versionDir, f.Name()), uniqCPEs) + definitions, err := parseOVALStream(filepath.Join(versionDir, f.Name()), uniqCPEs) if err != nil { return xerrors.Errorf("failed to parse OVAL stream: %w", err) } @@ -141,7 +142,7 @@ func (vs VulnSrc) parseNvrCpeMapping(dir string, uniqCPEs CPEMap) (map[string][] return nvrToCpe, nil } -func (vs VulnSrc) mergeAdvisories(advisories map[bucket]Advisory, defs map[bucket]Definition) map[bucket]Advisory { +func (vs VulnSrc) mergeAdvisories(advisories map[redhatovaltypes.Bucket]redhatovaltypes.Advisory, defs map[redhatovaltypes.Bucket]redhatovaltypes.Definition) map[redhatovaltypes.Bucket]redhatovaltypes.Advisory { for bkt, def := range defs { if old, ok := advisories[bkt]; ok { found := false @@ -157,8 +158,8 @@ func (vs VulnSrc) mergeAdvisories(advisories map[bucket]Advisory, defs map[bucke } advisories[bkt] = old } else { - advisories[bkt] = Advisory{ - Entries: []Entry{def.Entry}, + advisories[bkt] = redhatovaltypes.Advisory{ + Entries: []redhatovaltypes.Entry{def.Entry}, } } } @@ -166,7 +167,7 @@ func (vs VulnSrc) mergeAdvisories(advisories map[bucket]Advisory, defs map[bucke return advisories } -func (vs VulnSrc) save(repoToCpe, nvrToCpe map[string][]string, advisories map[bucket]Advisory, uniqCPEs CPEMap) error { +func (vs VulnSrc) save(repoToCpe, nvrToCpe map[string][]string, advisories map[redhatovaltypes.Bucket]redhatovaltypes.Advisory, uniqCPEs CPEMap) error { cpeList := uniqCPEs.List() err := vs.dbc.BatchUpdate(func(tx *bolt.Tx) error { if err := vs.dbc.PutDataSource(tx, rootBucket, source); err != nil { @@ -189,16 +190,30 @@ func (vs VulnSrc) save(repoToCpe, nvrToCpe map[string][]string, advisories map[b // Store advisories for bkt, advisory := range advisories { + var cveList []string for i := range advisory.Entries { // Convert CPE names to indices. advisory.Entries[i].AffectedCPEIndices = cpeList.Indices(advisory.Entries[i].AffectedCPEList) + // save all CVEs for advisory + // this is necessary to detect rejected CVEs during optimization + for _, cve := range advisory.Entries[i].Cves { + cveList = append(cveList, cve.ID) + } + } + + vulnerabilityDetails := types.VulnerabilityDetail{ + CveIDs: cveList, + } + + if err := vs.dbc.PutVulnerabilityDetail(tx, bkt.VulnID, source.ID, vulnerabilityDetails); err != nil { + return xerrors.Errorf("failed to save Red Hat OVAL advisory: %w", err) } - if err := vs.dbc.PutAdvisoryDetail(tx, bkt.vulnID, bkt.pkgName, []string{rootBucket}, advisory); err != nil { + if err := vs.dbc.PutAdvisoryDetail(tx, bkt.VulnID, bkt.PkgName, []string{rootBucket}, advisory); err != nil { return xerrors.Errorf("failed to save Red Hat OVAL advisory: %w", err) } - if err := vs.dbc.PutVulnerabilityID(tx, bkt.vulnID); err != nil { + if err := vs.dbc.PutVulnerabilityID(tx, bkt.VulnID); err != nil { return xerrors.Errorf("failed to put severity: %w", err) } } @@ -256,7 +271,7 @@ func (vs VulnSrc) Get(pkgName string, repositories, nvrs []string) ([]types.Advi continue } - var adv Advisory + var adv redhatovaltypes.Advisory if err = json.Unmarshal(v.Content, &adv); err != nil { return nil, xerrors.Errorf("failed to unmarshal advisory JSON: %w", err) } @@ -288,7 +303,7 @@ func (vs VulnSrc) Get(pkgName string, repositories, nvrs []string) ([]types.Advi return advisories, nil } -func (vs VulnSrc) parseOVALStream(dir string, uniqCPEs CPEMap) (map[bucket]Definition, error) { +func parseOVALStream(dir string, uniqCPEs CPEMap) (map[redhatovaltypes.Bucket]redhatovaltypes.Definition, error) { log.Printf(" Parsing %s", dir) // Parse tests @@ -297,14 +312,14 @@ func (vs VulnSrc) parseOVALStream(dir string, uniqCPEs CPEMap) (map[bucket]Defin return nil, xerrors.Errorf("failed to parse ovalTests: %w", err) } - var advisories []redhatOVAL + var advisories []redhatovaltypes.RedhatOVAL definitionsDir := filepath.Join(dir, "definitions") if exists, _ := utils.Exists(definitionsDir); !exists { return nil, nil } err = utils.FileWalk(definitionsDir, func(r io.Reader, path string) error { - var definition redhatOVAL + var definition redhatovaltypes.RedhatOVAL if err := json.NewDecoder(r).Decode(&definition); err != nil { return xerrors.Errorf("failed to decode %s: %w", path, err) } @@ -316,11 +331,11 @@ func (vs VulnSrc) parseOVALStream(dir string, uniqCPEs CPEMap) (map[bucket]Defin return nil, xerrors.Errorf("Red Hat OVAL walk error: %w", err) } - return vs.parseDefinitions(advisories, tests, uniqCPEs) + return parseDefinitions(advisories, tests, uniqCPEs), nil } -func (vs VulnSrc) parseDefinitions(advisories []redhatOVAL, tests map[string]rpmInfoTest, uniqCPEs CPEMap) (map[bucket]Definition, error) { - defs := map[bucket]Definition{} +func parseDefinitions(advisories []redhatovaltypes.RedhatOVAL, tests map[string]rpmInfoTest, uniqCPEs CPEMap) map[redhatovaltypes.Bucket]redhatovaltypes.Definition { + defs := map[redhatovaltypes.Bucket]redhatovaltypes.Definition{} for _, advisory := range advisories { // Skip unaffected vulnerabilities @@ -328,7 +343,7 @@ func (vs VulnSrc) parseDefinitions(advisories []redhatOVAL, tests map[string]rpm continue } - // Parse criteria + // Parse Criteria moduleName, affectedPkgs := walkCriterion(advisory.Criteria, tests) for _, affectedPkg := range affectedPkgs { pkgName := affectedPkg.Name @@ -340,30 +355,21 @@ func (vs VulnSrc) parseDefinitions(advisories []redhatOVAL, tests map[string]rpm rhsaID := vendorID(advisory.Metadata.References) - var cveEntries []CveEntry + var cveEntries []redhatovaltypes.CveEntry for _, cve := range advisory.Metadata.Advisory.Cves { - // get details from NVD - details, err := vs.dbc.GetVulnerabilityDetail(cve.CveID) - if err != nil { - return nil, xerrors.Errorf("Failed to get vulnerability detail: %s", err) - } - // don't use rejected vulnerabilities - if vulnerability.IsRejected(details) { - continue - } - cveEntries = append(cveEntries, CveEntry{ + cveEntries = append(cveEntries, redhatovaltypes.CveEntry{ ID: cve.CveID, Severity: severityFromImpact(cve.Impact), }) } if rhsaID != "" { // For patched vulnerabilities - bkt := bucket{ - pkgName: pkgName, - vulnID: rhsaID, + bkt := redhatovaltypes.Bucket{ + PkgName: pkgName, + VulnID: rhsaID, } - defs[bkt] = Definition{ - Entry: Entry{ + defs[bkt] = redhatovaltypes.Definition{ + Entry: redhatovaltypes.Entry{ Cves: cveEntries, FixedVersion: affectedPkg.FixedVersion, AffectedCPEList: advisory.Metadata.Advisory.AffectedCpeList, @@ -372,13 +378,13 @@ func (vs VulnSrc) parseDefinitions(advisories []redhatOVAL, tests map[string]rpm } } else { // For unpatched vulnerabilities for _, cve := range cveEntries { - bkt := bucket{ - pkgName: pkgName, - vulnID: cve.ID, + bkt := redhatovaltypes.Bucket{ + PkgName: pkgName, + VulnID: cve.ID, } - defs[bkt] = Definition{ - Entry: Entry{ - Cves: []CveEntry{ + defs[bkt] = redhatovaltypes.Definition{ + Entry: redhatovaltypes.Entry{ + Cves: []redhatovaltypes.CveEntry{ { Severity: cve.Severity, }, @@ -395,12 +401,12 @@ func (vs VulnSrc) parseDefinitions(advisories []redhatOVAL, tests map[string]rpm updateCPEs(advisory.Metadata.Advisory.AffectedCpeList, uniqCPEs) } - return defs, nil + return defs } -func walkCriterion(cri criteria, tests map[string]rpmInfoTest) (string, []pkg) { +func walkCriterion(cri redhatovaltypes.Criteria, tests map[string]rpmInfoTest) (string, []redhatovaltypes.Pkg) { var moduleName string - var packages []pkg + var packages []redhatovaltypes.Pkg for _, c := range cri.Criterions { // Parse module name @@ -425,7 +431,7 @@ func walkCriterion(cri criteria, tests map[string]rpmInfoTest) (string, []pkg) { arches = strings.Split(t.Arch, "|") // affected arches are merged with '|'(e.g. 'aarch64|ppc64le|x86_64') } - packages = append(packages, pkg{ + packages = append(packages, redhatovaltypes.Pkg{ Name: t.Name, FixedVersion: t.FixedVersion, Arches: arches, @@ -458,7 +464,7 @@ func updateCPEs(cpes []string, uniqCPEs CPEMap) { } } -func vendorID(refs []reference) string { +func vendorID(refs []redhatovaltypes.Reference) string { for _, ref := range refs { switch ref.Source { case "RHSA", "RHBA": diff --git a/pkg/vulnsrc/redhat-oval/redhat-oval_test.go b/pkg/vulnsrc/redhat-oval/redhat-oval_test.go index da235405..24fff190 100644 --- a/pkg/vulnsrc/redhat-oval/redhat-oval_test.go +++ b/pkg/vulnsrc/redhat-oval/redhat-oval_test.go @@ -6,6 +6,7 @@ import ( "sort" "testing" + redhatovaltypes "github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat-oval/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrctest" "github.com/stretchr/testify/assert" @@ -81,13 +82,13 @@ func TestVulnSrc_Update(t *testing.T) { }, { Key: []string{"advisory-detail", "RHSA-2020:5624", "Red Hat", "thunderbird"}, - Value: redhat.Advisory{ - Entries: []redhat.Entry{ + Value: redhatovaltypes.Advisory{ + Entries: []redhatovaltypes.Entry{ { FixedVersion: "0:78.6.0-1.el8_3", AffectedCPEIndices: []int{1, 2, 6}, Arches: []string{"aarch64", "ppc64le", "x86_64"}, - Cves: []redhat.CveEntry{ + Cves: []redhatovaltypes.CveEntry{ { ID: "CVE-2020-16042", Severity: types.SeverityHigh, @@ -103,13 +104,13 @@ func TestVulnSrc_Update(t *testing.T) { }, { Key: []string{"advisory-detail", "RHSA-2020:5624", "Red Hat", "thunderbird-debugsource"}, - Value: redhat.Advisory{ - Entries: []redhat.Entry{ + Value: redhatovaltypes.Advisory{ + Entries: []redhatovaltypes.Entry{ { FixedVersion: "0:78.6.0-1.el8_3", AffectedCPEIndices: []int{1, 2, 6}, Arches: []string{"aarch64", "ppc64le", "x86_64"}, - Cves: []redhat.CveEntry{ + Cves: []redhatovaltypes.CveEntry{ { ID: "CVE-2020-16042", Severity: types.SeverityHigh, @@ -125,13 +126,13 @@ func TestVulnSrc_Update(t *testing.T) { }, { Key: []string{"advisory-detail", "RHSA-2020:4751", "Red Hat", "httpd:2.4::httpd"}, - Value: redhat.Advisory{ - Entries: []redhat.Entry{ + Value: redhatovaltypes.Advisory{ + Entries: []redhatovaltypes.Entry{ { FixedVersion: "0:2.4.37-30.module+el7.3.0+7001+0766b9e7", AffectedCPEIndices: []int{0, 5}, Arches: []string{"aarch64", "ppc64le", "s390x", "x86_64"}, - Cves: []redhat.CveEntry{ + Cves: []redhatovaltypes.CveEntry{ { ID: "CVE-2018-17189", Severity: types.SeverityCritical, @@ -142,7 +143,7 @@ func TestVulnSrc_Update(t *testing.T) { FixedVersion: "0:2.4.37-30.module+el8.3.0+7001+0766b9e7", AffectedCPEIndices: []int{1, 2}, Arches: []string{"aarch64", "ppc64le", "s390x", "x86_64"}, - Cves: []redhat.CveEntry{ + Cves: []redhatovaltypes.CveEntry{ { ID: "CVE-2018-17189", Severity: types.SeverityLow, @@ -154,12 +155,12 @@ func TestVulnSrc_Update(t *testing.T) { }, { Key: []string{"advisory-detail", "CVE-2020-14342", "Red Hat", "cifs-utils"}, - Value: redhat.Advisory{ - Entries: []redhat.Entry{ + Value: redhatovaltypes.Advisory{ + Entries: []redhatovaltypes.Entry{ { FixedVersion: "", AffectedCPEIndices: []int{3, 5}, - Cves: []redhat.CveEntry{ + Cves: []redhatovaltypes.CveEntry{ { Severity: types.SeverityLow, }, @@ -170,13 +171,13 @@ func TestVulnSrc_Update(t *testing.T) { }, { Key: []string{"advisory-detail", "RHSA-2020:9999", "Red Hat", "thunderbird"}, - Value: redhat.Advisory{ - Entries: []redhat.Entry{ + Value: redhatovaltypes.Advisory{ + Entries: []redhatovaltypes.Entry{ { FixedVersion: "0:999.el8_3", AffectedCPEIndices: []int{4}, Arches: []string{"aarch64", "ppc64le", "x86_64"}, - Cves: []redhat.CveEntry{ + Cves: []redhatovaltypes.CveEntry{ { ID: "CVE-2020-26971", Severity: types.SeverityCritical, diff --git a/pkg/vulnsrc/redhat-oval/types.go b/pkg/vulnsrc/redhat-oval/types/types.go similarity index 62% rename from pkg/vulnsrc/redhat-oval/types.go rename to pkg/vulnsrc/redhat-oval/types/types.go index c078a576..8ca66a70 100644 --- a/pkg/vulnsrc/redhat-oval/types.go +++ b/pkg/vulnsrc/redhat-oval/types/types.go @@ -1,65 +1,65 @@ -package redhatoval +package types import "github.com/aquasecurity/trivy-db/pkg/types" -type redhatOVAL struct { +type RedhatOVAL struct { Class string ID string Version string - Metadata ovalMetadata - Criteria criteria + Metadata OvalMetadata + Criteria Criteria } -type ovalMetadata struct { +type OvalMetadata struct { Title string - AffectedList []affected - References []reference + AffectedList []Affected + References []Reference Description string - Advisory ovalAdvisory + Advisory OvalAdvisory } -type ovalAdvisory struct { +type OvalAdvisory struct { From string Severity string Rights string - Issued issued - Updated updated - Cves []ovalCVE - Bugzilla []bugzilla + Issued Issued + Updated Updated + Cves []OvalCVE + Bugzilla []Bugzilla AffectedCpeList []string } -type criteria struct { +type Criteria struct { Operator string - Criterias []criteria - Criterions []criterion + Criterias []Criteria + Criterions []Criterion } -type criterion struct { +type Criterion struct { TestRef string Comment string } -type affected struct { +type Affected struct { Family string Platforms []string } -type reference struct { +type Reference struct { Source string RefID string RefURL string } -type issued struct { +type Issued struct { Date string } -type updated struct { +type Updated struct { Date string } -type ovalCVE struct { +type OvalCVE struct { CveID string Cvss2 string Cvss3 string @@ -69,83 +69,83 @@ type ovalCVE struct { Public string } -type bugzilla struct { +type Bugzilla struct { ID string Href string } -type ovalTests struct { - RpminfoTests []rpminfoTest +type OvalTests struct { + RpminfoTests []RpminfoTest } -type ovalObjects struct { - RpminfoObjects []rpminfoObject +type OvalObjects struct { + RpminfoObjects []RpminfoObject } -type ovalStates struct { - RpminfoState []rpminfoState +type OvalStates struct { + RpminfoState []RpminfoState } -type ovalstate struct { +type Ovalstate struct { Text string StateRef string } -type ovalObject struct { +type OvalObject struct { Text string ObjectRef string } -type rpminfoTest struct { +type RpminfoTest struct { Check string Comment string ID string Version string CheckExistence string - Object ovalObject - State ovalstate + Object OvalObject + State Ovalstate } -type rpminfoObject struct { +type RpminfoObject struct { ID string Version string Name string } -type rpminfoState struct { +type RpminfoState struct { ID string Version string - Arch arch - Evr evr - SignatureKeyID signatureKeyID + Arch Arch + Evr Evr + SignatureKeyID SignatureKeyID } -type signatureKeyID struct { +type SignatureKeyID struct { Text string Operation string } -type arch struct { +type Arch struct { Text string Datatype string Operation string } -type evr struct { +type Evr struct { Text string Datatype string Operation string } -type pkg struct { +type Pkg struct { Name string FixedVersion string Arches []string } -type bucket struct { - pkgName string - vulnID string +type Bucket struct { + PkgName string + VulnID string } type Advisory struct { diff --git a/pkg/vulnsrc/vulnerability/vulnerability.go b/pkg/vulnsrc/vulnerability/vulnerability.go index 8205255c..dffb2e45 100644 --- a/pkg/vulnsrc/vulnerability/vulnerability.go +++ b/pkg/vulnsrc/vulnerability/vulnerability.go @@ -38,7 +38,7 @@ func (v Vulnerability) GetDetails(vulnID string) map[types.SourceID]types.Vulner return details } -func IsRejected(details map[types.SourceID]types.VulnerabilityDetail) bool { +func (Vulnerability) IsRejected(details map[types.SourceID]types.VulnerabilityDetail) bool { return getRejectedStatus(details) } diff --git a/pkg/vulnsrc/vulnerability/vulnerability_test.go b/pkg/vulnsrc/vulnerability/vulnerability_test.go index fead56cd..c24eff73 100644 --- a/pkg/vulnsrc/vulnerability/vulnerability_test.go +++ b/pkg/vulnsrc/vulnerability/vulnerability_test.go @@ -126,7 +126,7 @@ func TestIsRejected(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - got := vulnerability.IsRejected(tc.details) + got := vulnerability.New(nil).IsRejected(tc.details) assert.Equal(t, tc.want, got) }) }