From bc5a95ebb360ef332211bebe45b253fda80b44bc Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Fri, 26 May 2017 12:50:12 +0900 Subject: [PATCH] Fix -to-email --- models/cvecontents.go | 8 +--- models/scanresults.go | 31 +-------------- models/vulninfos.go | 39 +++++++++++++++++- models/vulninfos_test.go | 85 ++++++++++++++++++++++++++++++++++------ report/email.go | 23 ++++++++--- report/slack.go | 2 +- report/util.go | 2 +- 7 files changed, 134 insertions(+), 56 deletions(-) diff --git a/models/cvecontents.go b/models/cvecontents.go index a1330deb..1af139c1 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -231,14 +231,12 @@ func cvss3ScoreToSeverity(score float64) string { // Cvss3Scores returns CVSS V3 Score func (v CveContents) Cvss3Scores() (values []CveContentCvss3) { + // TODO implement NVD order := []CveContentType{RedHat} for _, ctype := range order { if cont, found := v[ctype]; found && 0 < cont.Cvss3Score { // https://nvd.nist.gov/vuln-metrics/cvss sev := cont.Severity - if ctype == NVD { - sev = cvss3ScoreToSeverity(cont.Cvss2Score) - } values = append(values, CveContentCvss3{ Type: ctype, Value: Cvss3{ @@ -254,6 +252,7 @@ func (v CveContents) Cvss3Scores() (values []CveContentCvss3) { // MaxCvss3Score returns Max CVSS V3 Score func (v CveContents) MaxCvss3Score() CveContentCvss3 { + // TODO implement NVD order := []CveContentType{RedHat} max := 0.0 value := CveContentCvss3{ @@ -264,9 +263,6 @@ func (v CveContents) MaxCvss3Score() CveContentCvss3 { if cont, found := v[ctype]; found && max < cont.Cvss3Score { // https://nvd.nist.gov/vuln-metrics/cvss sev := cont.Severity - if ctype == NVD { - sev = cvss3ScoreToSeverity(cont.Cvss2Score) - } value = CveContentCvss3{ Type: ctype, Value: Cvss3{ diff --git a/models/scanresults.go b/models/scanresults.go index 8aa6623d..aa538249 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -23,7 +23,6 @@ import ( "strings" "time" - "github.com/future-architect/vuls/config" cvedict "github.com/kotakanbe/go-cve-dictionary/models" ) @@ -215,34 +214,6 @@ func (r ScanResult) FormatServerName() string { r.Container.Name, r.ServerName) } -// CveSummary summarize the number of CVEs group by CVSSv2 Severity -func (r ScanResult) CveSummary() string { - var high, medium, low, unknown int - for _, vInfo := range r.ScannedCves { - score := vInfo.CveContents.MaxCvss2Score().Value.Score - if score < 0.1 { - score = vInfo.CveContents.MaxCvss3Score().Value.Score - } - switch { - case 7.0 <= score: - high++ - case 4.0 <= score: - medium++ - case 0 < score: - low++ - default: - unknown++ - } - } - - if config.Conf.IgnoreUnscoredCves { - return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)", - high+medium+low, high, medium, low) - } - return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)", - high+medium+low+unknown, high, medium, low, unknown) -} - // FormatTextReportHeadedr returns header of text report func (r ScanResult) FormatTextReportHeadedr() string { serverInfo := r.ServerInfo() @@ -253,7 +224,7 @@ func (r ScanResult) FormatTextReportHeadedr() string { return fmt.Sprintf("%s\n%s\n%s\t%s\n", r.ServerInfo(), buf.String(), - r.CveSummary(), + r.ScannedCves.FormatCveSummary(), r.Packages.FormatUpdatablePacksSummary(), ) } diff --git a/models/vulninfos.go b/models/vulninfos.go index 043d1176..402c2456 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -22,6 +22,8 @@ import ( "sort" "strings" "time" + + "github.com/future-architect/vuls/config" ) // VulnInfos is VulnInfo list, getter/setter, sortable methods. @@ -65,7 +67,42 @@ func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) { return } -// VulnInfo holds a vulnerability information and unsecure packages +// CountGroupBySeverity summarize the number of CVEs group by CVSSv2 Severity +func (v VulnInfos) CountGroupBySeverity() map[string]int { + m := map[string]int{} + for _, vInfo := range v { + score := vInfo.CveContents.MaxCvss2Score().Value.Score + if score < 0.1 { + score = vInfo.CveContents.MaxCvss3Score().Value.Score + } + switch { + case 7.0 <= score: + m["High"]++ + case 4.0 <= score: + m["Medium"]++ + case 0 < score: + m["Low"]++ + default: + m["Unknown"]++ + } + } + return m +} + +// FormatCveSummary summarize the number of CVEs group by CVSSv2 Severity +func (v VulnInfos) FormatCveSummary() string { + m := v.CountGroupBySeverity() + + if config.Conf.IgnoreUnscoredCves { + return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)", + m["High"]+m["Medium"]+m["Low"], m["High"], m["Medium"], m["Low"]) + } + return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)", + m["High"]+m["Medium"]+m["Low"]+m["Unknown"], + m["High"], m["Medium"], m["Low"], m["Unknown"]) +} + +// VulnInfo has a vulnerability information and unsecure packages type VulnInfo struct { CveID string Confidence Confidence diff --git a/models/vulninfos_test.go b/models/vulninfos_test.go index 1e796a3a..7b4f8f40 100644 --- a/models/vulninfos_test.go +++ b/models/vulninfos_test.go @@ -21,6 +21,67 @@ import ( "testing" ) +func TestCountGroupBySeverity(t *testing.T) { + var tests = []struct { + in VulnInfos + out map[string]int + }{ + { + in: VulnInfos{ + "CVE-2017-0002": { + CveID: "CVE-2017-0002", + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss2Score: 6.0, + }, + RedHat: { + Type: RedHat, + Cvss2Score: 7.0, + }, + }, + }, + "CVE-2017-0003": { + CveID: "CVE-2017-0003", + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss2Score: 2.0, + }, + }, + }, + "CVE-2017-0004": { + CveID: "CVE-2017-0004", + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss2Score: 5.0, + }, + }, + }, + "CVE-2017-0005": { + CveID: "CVE-2017-0005", + }, + }, + out: map[string]int{ + "High": 1, + "Medium": 1, + "Low": 1, + "Unknown": 1, + }, + }, + } + for _, tt := range tests { + actual := tt.in.CountGroupBySeverity() + for k := range tt.out { + if tt.out[k] != actual[k] { + t.Errorf("\nexpected %s: %d\n actual %d\n", + k, tt.out[k], actual[k]) + } + } + } +} + func TestToSortedSlice(t *testing.T) { var tests = []struct { in VulnInfos @@ -33,11 +94,11 @@ func TestToSortedSlice(t *testing.T) { CveContents: CveContents{ NVD: { Type: NVD, - Cvss3Score: 6.0, + Cvss2Score: 6.0, }, RedHat: { Type: RedHat, - Cvss2Score: 7.0, + Cvss3Score: 7.0, }, }, }, @@ -46,11 +107,11 @@ func TestToSortedSlice(t *testing.T) { CveContents: CveContents{ NVD: { Type: NVD, - Cvss3Score: 7.0, + Cvss2Score: 7.0, }, RedHat: { Type: RedHat, - Cvss2Score: 8.0, + Cvss3Score: 8.0, }, }, }, @@ -61,11 +122,11 @@ func TestToSortedSlice(t *testing.T) { CveContents: CveContents{ NVD: { Type: NVD, - Cvss3Score: 7.0, + Cvss2Score: 7.0, }, RedHat: { Type: RedHat, - Cvss2Score: 8.0, + Cvss3Score: 8.0, }, }, }, @@ -74,11 +135,11 @@ func TestToSortedSlice(t *testing.T) { CveContents: CveContents{ NVD: { Type: NVD, - Cvss3Score: 6.0, + Cvss2Score: 6.0, }, RedHat: { Type: RedHat, - Cvss2Score: 7.0, + Cvss3Score: 7.0, }, }, }, @@ -92,11 +153,11 @@ func TestToSortedSlice(t *testing.T) { CveContents: CveContents{ NVD: { Type: NVD, - Cvss3Score: 6.0, + Cvss2Score: 6.0, }, RedHat: { Type: RedHat, - Cvss2Score: 7.0, + Cvss3Score: 7.0, }, }, }, @@ -125,11 +186,11 @@ func TestToSortedSlice(t *testing.T) { CveContents: CveContents{ NVD: { Type: NVD, - Cvss3Score: 6.0, + Cvss2Score: 6.0, }, RedHat: { Type: RedHat, - Cvss2Score: 7.0, + Cvss3Score: 7.0, }, }, }, diff --git a/report/email.go b/report/email.go index f20194c4..d3c1717d 100644 --- a/report/email.go +++ b/report/email.go @@ -35,12 +35,18 @@ type EMailWriter struct{} func (w EMailWriter) Write(rs ...models.ScanResult) (err error) { conf := config.Conf var message string - var totalResult models.ScanResult sender := NewEMailSender() + m := map[string]int{} for _, r := range rs { if conf.FormatOneEMail { message += formatFullPlainText(r) + "\r\n\r\n" + + mm := r.ScannedCves.CountGroupBySeverity() + keys := []string{"High", "Medium", "Low", "Unknown"} + for _, k := range keys { + m[k] += mm[k] + } } else { var subject string if len(r.Errors) != 0 { @@ -50,7 +56,7 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) { subject = fmt.Sprintf("%s%s %s", conf.EMail.SubjectPrefix, r.ServerInfo(), - r.CveSummary()) + r.ScannedCves.FormatCveSummary()) } message = formatFullPlainText(r) if err := sender.Send(subject, message); err != nil { @@ -59,6 +65,15 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) { } } + summary := "" + if config.Conf.IgnoreUnscoredCves { + summary = fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)", + m["High"]+m["Medium"]+m["Low"], m["High"], m["Medium"], m["Low"]) + } + summary = fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)", + m["High"]+m["Medium"]+m["Low"]+m["Unknown"], + m["High"], m["Medium"], m["Low"], m["Unknown"]) + if conf.FormatOneEMail { message = fmt.Sprintf( ` @@ -71,9 +86,7 @@ One Line Summary formatOneLineSummary(rs...), message) subject := fmt.Sprintf("%s %s", - conf.EMail.SubjectPrefix, - totalResult.CveSummary(), - ) + conf.EMail.SubjectPrefix, summary) return sender.Send(subject, message) } return nil diff --git a/report/slack.go b/report/slack.go index 2b1e143e..0094184e 100644 --- a/report/slack.go +++ b/report/slack.go @@ -159,7 +159,7 @@ func msgText(r models.ScanResult) string { return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, serverInfo, - r.CveSummary()) + r.ScannedCves.FormatCveSummary()) } func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) { diff --git a/report/util.go b/report/util.go index d5a68d2e..6f27aa2e 100644 --- a/report/util.go +++ b/report/util.go @@ -72,7 +72,7 @@ func formatOneLineSummary(rs ...models.ScanResult) string { if len(r.Errors) == 0 { cols = []interface{}{ r.FormatServerName(), - r.CveSummary(), + r.ScannedCves.FormatCveSummary(), r.Packages.FormatUpdatablePacksSummary(), } } else {