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

feat: add vulnerability status #328

Merged
merged 14 commits into from
Jul 26, 2023
43 changes: 42 additions & 1 deletion pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,47 @@ func (s Severity) String() string {
return SeverityNames[s]
}

type Status int

// Statuses is a list of statuses.
// VEX has 4 statuses: not-affected, affected, fixed, and under_investigation.
// cf. https://www.cisa.gov/sites/default/files/2023-04/minimum-requirements-for-vex-508c.pdf
//
// In addition to them, Red Hat has "will_not_fix" and "fix_deferred".
// cf. https://access.redhat.com/blogs/product-security/posts/2066793
var Statuses = []string{
"Unknown",
"Not affected",
"Affected",
"Fixed",
"Under investigation",
"Will not fix",
"Fix deferred",
"End of life",
}

const (
StatusUnknown Status = iota
StatusNotAffected
StatusAffected
StatusFixed
StatusUnderInvestigation
StatusWillNotFix // Red Hat specific
StatusFixDeferred
StatusEndOfLife
)

func (s Status) String() string {
if s < 0 || int(s) >= len(Statuses) {
return "unknown"
}
return [...]string{}[s]
}

func (s Status) Index() int {
return int(s)
}

type LastUpdated struct {
Date time.Time
}
Expand Down Expand Up @@ -104,7 +145,7 @@ type Advisory struct {

// 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
State string `json:",omitempty"`
Status Status `json:",omitempty"`

// Trivy DB has "vulnerability" bucket and severities are usually stored in the bucket per a vulnerability ID.
// In some cases, the advisory may have multiple severities depending on the packages.
Expand Down
18 changes: 17 additions & 1 deletion pkg/vulnsrc/debian/debian.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ func defaultPut(dbc db.Operation, tx *bolt.Tx, advisory interface{}) error {

detail := types.Advisory{
VendorIDs: adv.VendorIDs,
State: adv.State,
Status: newStatus(adv.State),
Severity: severityFromUrgency(adv.Severity),
FixedVersion: adv.FixedVersion,
}
Expand Down Expand Up @@ -660,3 +660,19 @@ func compareVersions(v1, v2 string) (int, error) {

return ver1.Compare(ver2), nil
}

func newStatus(s string) types.Status {
switch strings.ToLower(s) {
// "end-of-life" is considered as vulnerable
// e.g. https://security-tracker.debian.org/tracker/CVE-2022-1488
case "no-dsa", "unfixed":
return types.StatusAffected
case "ignored":
return types.StatusWillNotFix
case "postponed":
return types.StatusFixDeferred
case "end-of-life":
return types.StatusEndOfLife
}
return types.StatusUnknown
}
95 changes: 78 additions & 17 deletions pkg/vulnsrc/debian/debian_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ func TestVulnSrc_Update(t *testing.T) {
dir: filepath.Join("testdata", "happy"),
wantValues: []vulnsrctest.WantValues{
{
Key: []string{"data-source", "debian 9"},
Key: []string{
"data-source",
"debian 9",
},
Value: types.DataSource{
ID: vulnerability.Debian,
Name: "Debian Security Tracker",
Expand All @@ -32,79 +35,137 @@ func TestVulnSrc_Update(t *testing.T) {
},
// Ref. https://security-tracker.debian.org/tracker/CVE-2021-33560
{
Key: []string{"advisory-detail", "CVE-2021-33560", "debian 9", "libgcrypt20"},
Key: []string{
"advisory-detail",
"CVE-2021-33560",
"debian 9",
"libgcrypt20",
},
Value: types.Advisory{
VendorIDs: []string{"DLA-2691-1"},
FixedVersion: "1.7.6-2+deb9u4",
},
},
{
Key: []string{"advisory-detail", "CVE-2021-33560", "debian 10", "libgcrypt20"},
Key: []string{
"advisory-detail",
"CVE-2021-33560",
"debian 10",
"libgcrypt20",
},
Value: types.Advisory{
FixedVersion: "1.8.4-5+deb10u1",
},
},
{
Key: []string{"advisory-detail", "CVE-2021-33560", "debian 11", "libgcrypt20"},
Key: []string{
"advisory-detail",
"CVE-2021-33560",
"debian 11",
"libgcrypt20",
},
Value: types.Advisory{
FixedVersion: "1.8.7-6",
},
},
{
Key: []string{"advisory-detail", "CVE-2021-29629", "debian 10", "dacs"},
Key: []string{
"advisory-detail",
"CVE-2021-29629",
"debian 10",
"dacs",
},
Value: types.Advisory{
State: "ignored",
Status: types.StatusWillNotFix,
Severity: types.SeverityLow,
},
},
{
Key: []string{"advisory-detail", "DSA-3714-1", "debian 8", "akonadi"},
Key: []string{
"advisory-detail",
"DSA-3714-1",
"debian 8",
"akonadi",
},
Value: types.Advisory{
VendorIDs: []string{"DSA-3714-1"},
FixedVersion: "1.13.0-2+deb8u2",
},
},
{
// wrong no-dsa
Key: []string{"advisory-detail", "CVE-2020-8631", "debian 11", "cloud-init"},
Key: []string{
"advisory-detail",
"CVE-2020-8631",
"debian 11",
"cloud-init",
},
Value: types.Advisory{
FixedVersion: "19.4-2",
},
},
{
Key: []string{"vulnerability-detail", "CVE-2021-33560", string(vulnerability.Debian)},
Key: []string{
"vulnerability-detail",
"CVE-2021-33560",
string(vulnerability.Debian),
},
Value: types.VulnerabilityDetail{
Title: "Libgcrypt before 1.8.8 and 1.9.x before 1.9.3 mishandles ElGamal encry ...",
},
},
{
Key: []string{"vulnerability-detail", "CVE-2021-29629", string(vulnerability.Debian)},
Key: []string{
"vulnerability-detail",
"CVE-2021-29629",
string(vulnerability.Debian),
},
Value: types.VulnerabilityDetail{
Title: "In FreeBSD 13.0-STABLE before n245765-bec0d2c9c841, 12.2-STABLE before ...",
},
},
{
Key: []string{"vulnerability-detail", "DSA-3714-1", string(vulnerability.Debian)},
Key: []string{
"vulnerability-detail",
"DSA-3714-1",
string(vulnerability.Debian),
},
Value: types.VulnerabilityDetail{
Title: "akonadi - update",
},
},
{
Key: []string{"vulnerability-id", "CVE-2021-33560"},
Key: []string{
"vulnerability-id",
"CVE-2021-33560",
},
Value: map[string]interface{}{},
},
{
Key: []string{"vulnerability-id", "CVE-2021-29629"},
Key: []string{
"vulnerability-id",
"CVE-2021-29629",
},
Value: map[string]interface{}{},
},
{
Key: []string{"vulnerability-id", "DSA-3714-1"},
Key: []string{
"vulnerability-id",
"DSA-3714-1",
},
Value: map[string]interface{}{},
},
},
noBuckets: [][]string{
{"advisory-detail", "CVE-2021-29629", "debian 9"}, // not-affected in debian stretch
{"advisory-detail", "CVE-2016-4606"}, // not-affected in sid
{
"advisory-detail",
"CVE-2021-29629",
"debian 9",
}, // not-affected in debian stretch
{
"advisory-detail",
"CVE-2016-4606",
}, // not-affected in sid
},
},
{
Expand Down Expand Up @@ -162,7 +223,7 @@ func TestVulnSrc_Get(t *testing.T) {
},
{
VulnerabilityID: "CVE-2021-38370",
State: "no-dsa",
Status: types.StatusAffected,
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion pkg/vulnsrc/debian/testdata/fixtures/debian.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- key: CVE-2021-38370
value:
FixedVersion: ""
State: "no-dsa"
Status: 2
- key: CVE-2008-5514
value:
FixedVersion: "2.02-3.1"
23 changes: 20 additions & 3 deletions pkg/vulnsrc/redhat-oval/redhat-oval.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ func (vs VulnSrc) Get(pkgName string, repositories, nvrs []string) ([]types.Advi
Severity: cve.Severity,
FixedVersion: entry.FixedVersion,
Arches: entry.Arches,
State: entry.State,
Status: entry.State,
DataSource: &v.Source,
}

Expand Down Expand Up @@ -366,8 +366,11 @@ func parseDefinitions(advisories []redhatOVAL, tests map[string]rpmInfoTest, uni
Cves: cveEntries,
FixedVersion: affectedPkg.FixedVersion,
AffectedCPEList: advisory.Metadata.Advisory.AffectedCpeList,
State: advisory.Metadata.Advisory.Affected.Resolution.State,
Arches: affectedPkg.Arches,

// The status is obviously "fixed" when there is a patch.
// To keep the database size small, we don't store the status for patched vulns.
// Status: StatusFixed,
},
}
} else { // For unpatched vulnerabilities
Expand All @@ -386,7 +389,7 @@ func parseDefinitions(advisories []redhatOVAL, tests map[string]rpmInfoTest, uni
FixedVersion: affectedPkg.FixedVersion,
AffectedCPEList: advisory.Metadata.Advisory.AffectedCpeList,
Arches: affectedPkg.Arches,
State: advisory.Metadata.Advisory.Affected.Resolution.State,
State: newStatus(advisory.Metadata.Advisory.Affected.Resolution.State),
},
}
}
Expand Down Expand Up @@ -482,3 +485,17 @@ func severityFromImpact(sev string) types.Severity {
}
return types.SeverityUnknown
}

func newStatus(s string) types.Status {
switch strings.ToLower(s) {
case "affected", "fix deferred":
return types.StatusAffected
case "under investigation":
return types.StatusUnderInvestigation
case "will not fix":
return types.StatusWillNotFix
case "out of support scope":
return types.StatusEndOfLife
}
return types.StatusUnknown
}
Loading