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(redhat): add rejected vulnerability check for rhsa-xxx #236

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
64 changes: 64 additions & 0 deletions pkg/db/advisory_detail.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Comment on lines +83 to +105
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this function for...?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function finds all Red Hat buckets, decodes them, removes rejected cves and saves to db as SaveAdvisoryDetails.


// 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
}
91 changes: 91 additions & 0 deletions pkg/db/advisory_detail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
})
}
}
18 changes: 18 additions & 0 deletions pkg/db/testdata/fixtures/rhsa-advisory-detail.yaml
Original file line number Diff line number Diff line change
@@ -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


3 changes: 2 additions & 1 deletion pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
27 changes: 24 additions & 3 deletions pkg/vulndb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package vulndb

import (
"log"
"strings"
"time"

bolt "go.etcd.io/bbolt"
Expand Down Expand Up @@ -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)
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 {
Expand Down
15 changes: 8 additions & 7 deletions pkg/vulnsrc/redhat-oval/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)
}
Expand All @@ -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
}
Expand All @@ -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)
}
Expand All @@ -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
Expand Down
Loading