From a14810bbd45836ee0a71a05f4b0d444e8539cdc8 Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Mon, 29 May 2017 10:50:39 +0900 Subject: [PATCH] Fix -to-slack --- models/cvecontents.go | 129 ++++++++++++-------- models/cvecontents_test.go | 95 ++++++++++----- models/packages.go | 25 ++-- models/vulninfos.go | 4 +- report/slack.go | 240 ++++++++++++++++++------------------- report/slack_test.go | 36 +++--- report/util.go | 4 +- 7 files changed, 295 insertions(+), 238 deletions(-) diff --git a/models/cvecontents.go b/models/cvecontents.go index 1af139c1..69126f5d 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -59,22 +59,40 @@ func (v CveContents) Except(exceptCtypes ...CveContentType) (values CveContents) return } -// CveContentCvss2 has CveContentType and Cvss2 -type CveContentCvss2 struct { +// CveContentCvss has CveContentType and Cvss2 +type CveContentCvss struct { Type CveContentType - Value Cvss2 + Value Cvss } -// Cvss2 has CVSS v2 -type Cvss2 struct { +// CvssType Represent the type of CVSS +type CvssType string + +const ( + // CVSS2 means CVSS vesion2 + CVSS2 CvssType = "2" + + // CVSS3 means CVSS vesion3 + CVSS3 CvssType = "3" +) + +// Cvss has CVSS Score +type Cvss struct { + Type CvssType Score float64 Vector string Severity string } // Format CVSS Score and Vector -func (c Cvss2) Format() string { - return fmt.Sprintf("%3.1f/%s", c.Score, c.Vector) +func (c Cvss) Format() string { + switch c.Type { + case CVSS2: + return fmt.Sprintf("%3.1f/%s", c.Score, c.Vector) + case CVSS3: + return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector) + } + return "" } func cvss2ScoreToSeverity(score float64) string { @@ -87,7 +105,7 @@ func cvss2ScoreToSeverity(score float64) string { } // Cvss2Scores returns CVSS V2 Scores -func (v CveContents) Cvss2Scores() (values []CveContentCvss2) { +func (v CveContents) Cvss2Scores() (values []CveContentCvss) { order := []CveContentType{NVD, RedHat, JVN} for _, ctype := range order { if cont, found := v[ctype]; found && 0 < cont.Cvss2Score { @@ -96,9 +114,10 @@ func (v CveContents) Cvss2Scores() (values []CveContentCvss2) { if ctype == NVD { sev = cvss2ScoreToSeverity(cont.Cvss2Score) } - values = append(values, CveContentCvss2{ + values = append(values, CveContentCvss{ Type: ctype, - Value: Cvss2{ + Value: Cvss{ + Type: CVSS2, Score: cont.Cvss2Score, Vector: cont.Cvss2Vector, Severity: sev, @@ -111,13 +130,13 @@ func (v CveContents) Cvss2Scores() (values []CveContentCvss2) { } // MaxCvss2Score returns Max CVSS V2 Score -func (v CveContents) MaxCvss2Score() CveContentCvss2 { +func (v CveContents) MaxCvss2Score() CveContentCvss { //TODO Severity Ubuntu, Debian... order := []CveContentType{NVD, RedHat, JVN} max := 0.0 - value := CveContentCvss2{ + value := CveContentCvss{ Type: Unknown, - Value: Cvss2{}, + Value: Cvss{Type: CVSS2}, } for _, ctype := range order { if cont, found := v[ctype]; found && max < cont.Cvss2Score { @@ -126,9 +145,10 @@ func (v CveContents) MaxCvss2Score() CveContentCvss2 { if ctype == NVD { sev = cvss2ScoreToSeverity(cont.Cvss2Score) } - value = CveContentCvss2{ + value = CveContentCvss{ Type: ctype, - Value: Cvss2{ + Value: Cvss{ + Type: CVSS2, Score: cont.Cvss2Score, Vector: cont.Cvss2Vector, Severity: sev, @@ -155,9 +175,10 @@ func (v CveContents) MaxCvss2Score() CveContentCvss2 { score = severityToScoreForRedHat(cont.Severity) } if max < score { - value = CveContentCvss2{ + value = CveContentCvss{ Type: ctype, - Value: Cvss2{ + Value: Cvss{ + Type: CVSS2, Score: score, Vector: cont.Cvss2Vector, Severity: cont.Severity, @@ -201,45 +222,46 @@ func severityToScoreForRedHat(severity string) float64 { } // CveContentCvss3 has CveContentType and Cvss3 -type CveContentCvss3 struct { - Type CveContentType - Value Cvss3 -} +// type CveContentCvss3 struct { +// Type CveContentType +// Value Cvss3 +// } // Cvss3 has CVSS v3 Score, Vector and Severity -type Cvss3 struct { - Score float64 - Vector string - Severity string -} +// type Cvss3 struct { +// Score float64 +// Vector string +// Severity string +// } // Format CVSS Score and Vector -func (c Cvss3) Format() string { - return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector) -} +// func (c Cvss3) Format() string { +// return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector) +// } -func cvss3ScoreToSeverity(score float64) string { - if 9.0 <= score { - return "CRITICAL" - } else if 7.0 <= score { - return "HIGH" - } else if 4.0 <= score { - return "MEDIUM" - } - return "LOW" -} +// func cvss3ScoreToSeverity(score float64) string { +// if 9.0 <= score { +// return "CRITICAL" +// } else if 7.0 <= score { +// return "HIGH" +// } else if 4.0 <= score { +// return "MEDIUM" +// } +// return "LOW" +// } // Cvss3Scores returns CVSS V3 Score -func (v CveContents) Cvss3Scores() (values []CveContentCvss3) { +func (v CveContents) Cvss3Scores() (values []CveContentCvss) { // 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 - values = append(values, CveContentCvss3{ + values = append(values, CveContentCvss{ Type: ctype, - Value: Cvss3{ + Value: Cvss{ + Type: CVSS3, Score: cont.Cvss3Score, Vector: cont.Cvss3Vector, Severity: sev, @@ -251,21 +273,22 @@ func (v CveContents) Cvss3Scores() (values []CveContentCvss3) { } // MaxCvss3Score returns Max CVSS V3 Score -func (v CveContents) MaxCvss3Score() CveContentCvss3 { +func (v CveContents) MaxCvss3Score() CveContentCvss { // TODO implement NVD order := []CveContentType{RedHat} max := 0.0 - value := CveContentCvss3{ + value := CveContentCvss{ Type: Unknown, - Value: Cvss3{}, + Value: Cvss{Type: CVSS3}, } for _, ctype := range order { if cont, found := v[ctype]; found && max < cont.Cvss3Score { // https://nvd.nist.gov/vuln-metrics/cvss sev := cont.Severity - value = CveContentCvss3{ + value = CveContentCvss{ Type: ctype, - Value: Cvss3{ + Value: Cvss{ + Type: CVSS3, Score: cont.Cvss3Score, Vector: cont.Cvss3Vector, Severity: sev, @@ -279,12 +302,12 @@ func (v CveContents) MaxCvss3Score() CveContentCvss3 { // MaxCvssScore returns max CVSS Score // If there is no CVSS Score, return Severity as a numerical value. -func (v CveContents) MaxCvssScore() float64 { +func (v CveContents) MaxCvssScore() CveContentCvss { v3Max := v.MaxCvss3Score() v2Max := v.MaxCvss2Score() - max := v3Max.Value.Score - if max < v2Max.Value.Score { - max = v2Max.Value.Score + max := v3Max + if max.Value.Score < v2Max.Value.Score { + max = v2Max } return max } @@ -396,13 +419,13 @@ func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveCont } // Severities returns Severities -// func (v CveContents) Severities(myFamily string) (values []CveContentValue) { +// func (v CveContents) Severities(myFamily string) (values []CveContentStr) { // order := CveContentTypes{NVD, NewCveContentType(myFamily)} // order = append(order, AllCveContetTypes.Except(append(order)...)...) // for _, ctype := range order { // if cont, found := v[ctype]; found && 0 < len(cont.Severity) { -// values = append(values, CveContentValue{ +// values = append(values, CveContentStr{ // Type: ctype, // Value: cont.Severity, // }) diff --git a/models/cvecontents_test.go b/models/cvecontents_test.go index 5eabfb10..eca860c2 100644 --- a/models/cvecontents_test.go +++ b/models/cvecontents_test.go @@ -47,7 +47,7 @@ func TestExcept(t *testing.T) { func TestCvss2Scores(t *testing.T) { var tests = []struct { in CveContents - out []CveContentCvss2 + out []CveContentCvss }{ { in: CveContents{ @@ -70,10 +70,11 @@ func TestCvss2Scores(t *testing.T) { // Severity is NIOT included in NVD }, }, - out: []CveContentCvss2{ + out: []CveContentCvss{ { Type: NVD, - Value: Cvss2{ + Value: Cvss{ + Type: CVSS2, Score: 8.1, Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", Severity: "HIGH", @@ -81,7 +82,8 @@ func TestCvss2Scores(t *testing.T) { }, { Type: RedHat, - Value: Cvss2{ + Value: Cvss{ + Type: CVSS2, Score: 8.0, Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", Severity: "HIGH", @@ -89,7 +91,8 @@ func TestCvss2Scores(t *testing.T) { }, { Type: JVN, - Value: Cvss2{ + Value: Cvss{ + Type: CVSS2, Score: 8.2, Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", Severity: "HIGH", @@ -114,7 +117,7 @@ func TestCvss2Scores(t *testing.T) { func TestMaxCvss2Scores(t *testing.T) { var tests = []struct { in CveContents - out CveContentCvss2 + out CveContentCvss }{ { in: CveContents{ @@ -137,9 +140,10 @@ func TestMaxCvss2Scores(t *testing.T) { // Severity is NIOT included in NVD }, }, - out: CveContentCvss2{ + out: CveContentCvss{ Type: JVN, - Value: Cvss2{ + Value: Cvss{ + Type: CVSS2, Score: 8.2, Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", Severity: "HIGH", @@ -154,9 +158,10 @@ func TestMaxCvss2Scores(t *testing.T) { Severity: "HIGH", }, }, - out: CveContentCvss2{ + out: CveContentCvss{ Type: Ubuntu, - Value: Cvss2{ + Value: Cvss{ + Type: CVSS2, Score: 10, Severity: "HIGH", }, @@ -165,9 +170,10 @@ func TestMaxCvss2Scores(t *testing.T) { // Empty { in: CveContents{}, - out: CveContentCvss2{ + out: CveContentCvss{ Type: Unknown, - Value: Cvss2{ + Value: Cvss{ + Type: CVSS2, Score: 0.0, Vector: "", Severity: "", @@ -186,7 +192,7 @@ func TestMaxCvss2Scores(t *testing.T) { func TestCvss3Scores(t *testing.T) { var tests = []struct { in CveContents - out []CveContentCvss3 + out []CveContentCvss }{ { in: CveContents{ @@ -203,10 +209,11 @@ func TestCvss3Scores(t *testing.T) { // Severity is NIOT included in NVD }, }, - out: []CveContentCvss3{ + out: []CveContentCvss{ { Type: RedHat, - Value: Cvss3{ + Value: Cvss{ + Type: CVSS3, Score: 8.0, Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", Severity: "HIGH", @@ -231,7 +238,7 @@ func TestCvss3Scores(t *testing.T) { func TestMaxCvss3Scores(t *testing.T) { var tests = []struct { in CveContents - out CveContentCvss3 + out CveContentCvss }{ { in: CveContents{ @@ -242,9 +249,10 @@ func TestMaxCvss3Scores(t *testing.T) { Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", }, }, - out: CveContentCvss3{ + out: CveContentCvss{ Type: RedHat, - Value: Cvss3{ + Value: Cvss{ + Type: CVSS3, Score: 8.0, Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", Severity: "HIGH", @@ -254,9 +262,10 @@ func TestMaxCvss3Scores(t *testing.T) { // Empty { in: CveContents{}, - out: CveContentCvss3{ + out: CveContentCvss{ Type: Unknown, - Value: Cvss3{ + Value: Cvss{ + Type: CVSS3, Score: 0.0, Vector: "", Severity: "", @@ -275,7 +284,7 @@ func TestMaxCvss3Scores(t *testing.T) { func TestMaxCvssScores(t *testing.T) { var tests = []struct { in CveContents - out float64 + out CveContentCvss }{ { in: CveContents{ @@ -288,7 +297,13 @@ func TestMaxCvssScores(t *testing.T) { Cvss2Score: 8.0, }, }, - out: 8.0, + out: CveContentCvss{ + Type: RedHat, + Value: Cvss{ + Type: CVSS2, + Score: 8.0, + }, + }, }, { in: CveContents{ @@ -297,7 +312,13 @@ func TestMaxCvssScores(t *testing.T) { Cvss3Score: 8.0, }, }, - out: 8.0, + out: CveContentCvss{ + Type: RedHat, + Value: Cvss{ + Type: CVSS3, + Score: 8.0, + }, + }, }, { in: CveContents{ @@ -306,7 +327,14 @@ func TestMaxCvssScores(t *testing.T) { Severity: "HIGH", }, }, - out: 10.0, + out: CveContentCvss{ + Type: Ubuntu, + Value: Cvss{ + Type: CVSS2, + Score: 10.0, + Severity: "HIGH", + }, + }, }, { in: CveContents{ @@ -319,12 +347,25 @@ func TestMaxCvssScores(t *testing.T) { Cvss2Score: 7.0, }, }, - out: 7.0, + out: CveContentCvss{ + Type: NVD, + Value: Cvss{ + Type: CVSS2, + Score: 7.0, + Severity: "HIGH", + }, + }, }, // Empty { - in: CveContents{}, - out: 0, + in: CveContents{}, + out: CveContentCvss{ + Type: Unknown, + Value: Cvss{ + Type: CVSS3, + Score: 0, + }, + }, }, } for i, tt := range tests { diff --git a/models/packages.go b/models/packages.go index 056c4f83..8439f90a 100644 --- a/models/packages.go +++ b/models/packages.go @@ -90,33 +90,28 @@ type Package struct { NotFixedYet bool // Ubuntu OVAL Only } -// FormatVer returns package name-version-release +// FormatVer returns package version-release func (p Package) FormatVer() string { - str := p.Name - if 0 < len(p.Version) { - str = fmt.Sprintf("%s-%s", str, p.Version) - } + ver := p.Version if 0 < len(p.Release) { - str = fmt.Sprintf("%s-%s", str, p.Release) + ver = fmt.Sprintf("%s-%s", ver, p.Release) } - return str + return ver } -// FormatNewVer returns package name-version-release +// FormatNewVer returns package version-release func (p Package) FormatNewVer() string { - str := p.Name - if 0 < len(p.NewVersion) { - str = fmt.Sprintf("%s-%s", str, p.NewVersion) - } + ver := p.NewVersion if 0 < len(p.NewRelease) { - str = fmt.Sprintf("%s-%s", str, p.NewRelease) + ver = fmt.Sprintf("%s-%s", ver, p.NewRelease) } - return str + return ver } // FormatVersionFromTo formats installed and new package version func (p Package) FormatVersionFromTo() string { - return fmt.Sprintf("%s -> %s", p.FormatVer(), p.FormatNewVer()) + return fmt.Sprintf("%s-%s -> %s", + p.Name, p.FormatVer(), p.FormatNewVer()) } // Changelog has contents of changelog and how to get it. diff --git a/models/vulninfos.go b/models/vulninfos.go index 402c2456..13813ea1 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -59,8 +59,8 @@ func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) { sort.Slice(sorted, func(i, j int) bool { maxI := sorted[i].CveContents.MaxCvssScore() maxJ := sorted[j].CveContents.MaxCvssScore() - if maxI != maxJ { - return maxJ < maxI + if maxI.Value.Score != maxJ.Value.Score { + return maxJ.Value.Score < maxI.Value.Score } return sorted[i].CveID < sorted[j].CveID }) diff --git a/report/slack.go b/report/slack.go index 0094184e..3ffd6f83 100644 --- a/report/slack.go +++ b/report/slack.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "sort" + "strings" "time" log "github.com/Sirupsen/logrus" @@ -44,6 +45,7 @@ type attachment struct { Color string `json:"color"` Fields []*field `json:"fields"` MrkdwnIn []string `json:"mrkdwn_in"` + Footer string `json:"footer"` } type message struct { Text string `json:"text"` @@ -66,12 +68,12 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error { } if 0 < len(r.Errors) { - //TODO - // serverInfo := fmt.Sprintf("*%s*", r.ServerInfo()) - // notifyUsers := getNotifyUsers(config.Conf.Slack.NotifyUsers) - // txt := fmt.Sprintf("%s\n%s\nError: %s", notifyUsers, serverInfo, r.Errors) + serverInfo := fmt.Sprintf("*%s*", r.ServerInfo()) + notifyUsers := getNotifyUsers(config.Conf.Slack.NotifyUsers) + txt := fmt.Sprintf("%s\n%s\nError: %s", + notifyUsers, serverInfo, r.Errors) msg := message{ - // Text: txt, + Text: txt, Username: conf.AuthUser, IconEmoji: conf.IconEmoji, Channel: channel, @@ -152,9 +154,9 @@ func send(msg message) error { func msgText(r models.ScanResult) string { notifyUsers := "" - // if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) { - // notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers) - // } + if 0 < len(r.ScannedCves) { + notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers) + } serverInfo := fmt.Sprintf("*%s*", r.ServerInfo()) return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, @@ -162,50 +164,62 @@ func msgText(r models.ScanResult) string { r.ScannedCves.FormatCveSummary()) } -func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) { - // cves := scanResult.KnownCves - // if !config.Conf.IgnoreUnscoredCves { - // cves = append(cves, scanResult.UnknownCves...) - // } +func toSlackAttachments(r models.ScanResult) (attaches []*attachment) { + var vinfos []models.VulnInfo + if config.Conf.IgnoreUnscoredCves { + vinfos = r.ScannedCves.FindScoredVulns().ToSortedSlice() + } else { + vinfos = r.ScannedCves.ToSortedSlice() + } - // for _, cveInfo := range cves { - // cveID := cveInfo.VulnInfo.CveID + for _, vinfo := range vinfos { + curent := []string{} + for _, name := range vinfo.PackageNames { + if p, ok := r.Packages[name]; ok { + curent = append(curent, + fmt.Sprintf("%s-%s", p.Name, p.FormatVer())) + } else { + curent = append(curent, name) + } + } + for _, n := range vinfo.CpeNames { + curent = append(curent, n) + } - // curentPackages := []string{} - // for _, p := range cveInfo.Packages { - // curentPackages = append(curentPackages, p.FormatCurrentVer()) - // } - // for _, n := range cveInfo.CpeNames { - // curentPackages = append(curentPackages, n) - // } + new := []string{} + for _, name := range vinfo.PackageNames { + if p, ok := r.Packages[name]; ok { + new = append(new, p.FormatNewVer()) + } else { + new = append(new, "?") + } + } + for range vinfo.CpeNames { + new = append(new, "?") + } - // newPackages := []string{} - // for _, p := range cveInfo.Packages { - // newPackages = append(newPackages, p.FormatNewVer()) - // } - - // a := attachment{ - // Title: cveID, - // TitleLink: fmt.Sprintf("%s/%s", nvdBaseURL, cveID), - // Text: attachmentText(cveInfo, scanResult.Family), - // MrkdwnIn: []string{"text", "pretext"}, - // Fields: []*field{ - // { - // // Title: "Current Package/CPE", - // Title: "Installed", - // Value: strings.Join(curentPackages, "\n"), - // Short: true, - // }, - // { - // Title: "Candidate", - // Value: strings.Join(newPackages, "\n"), - // Short: true, - // }, - // }, - // Color: color(cveInfo.CvssV2Score()), - // } - // attaches = append(attaches, &a) - // } + a := attachment{ + Title: vinfo.CveID, + TitleLink: "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID, + Text: attachmentText(vinfo, r.Family), + MrkdwnIn: []string{"text", "pretext"}, + Fields: []*field{ + { + // Title: "Current Package/CPE", + Title: "Installed", + Value: strings.Join(curent, "\n"), + Short: true, + }, + { + Title: "Candidate", + Value: strings.Join(new, "\n"), + Short: true, + }, + }, + Color: color(vinfo.CveContents.MaxCvssScore().Value.Score), + } + attaches = append(attaches, &a) + } return } @@ -223,80 +237,62 @@ func color(cvssScore float64) string { } } -// func attachmentText(cveInfo models.CveInfo, osFamily string) string { -// linkText := links(cveInfo, osFamily) -//TODO -// return "" -// switch { -// case config.Conf.Lang == "ja" && -// 0 < cveInfo.CveDetail.Jvn.CvssScore(): +func attachmentText(vinfo models.VulnInfo, osFamily string) string { + maxCvss := vinfo.CveContents.MaxCvssScore() + vectors := []string{} + for _, cvss := range vinfo.CveContents.Cvss2Scores() { + calcURL := "" + switch cvss.Value.Type { + case models.CVSS2: + calcURL = fmt.Sprintf( + "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?vector=%s", + cvss.Value.Vector) + case models.CVSS3: + calcURL = fmt.Sprintf( + "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=%s", + cvss.Value.Vector) + } + v := fmt.Sprintf("<%s|%s> (<%s|%s>)", + calcURL, + cvss.Value.Format(), + vinfo.CveContents[cvss.Type].SourceLink, + cvss.Type) + vectors = append(vectors, v) + } -// jvn := cveInfo.CveDetail.Jvn -// return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s\n*Confidence:* %v", -// cveInfo.CveDetail.CvssScore(config.Conf.Lang), -// jvn.CvssSeverity(), -// fmt.Sprintf(cvssV2CalcBaseURL, cveInfo.CveDetail.CveID), -// jvn.CvssVector(), -// jvn.CveTitle(), -// linkText, -// cveInfo.VulnInfo.Confidence, -// ) -// case 0 < cveInfo.CveDetail.CvssScore("en"): -// nvd := cveInfo.CveDetail.Nvd -// return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s\n*Confidence:* %v", -// cveInfo.CveDetail.CvssScore(config.Conf.Lang), -// nvd.CvssSeverity(), -// fmt.Sprintf(cvssV2CalcBaseURL, cveInfo.CveDetail.CveID), -// nvd.CvssVector(), -// nvd.CveSummary(), -// linkText, -// cveInfo.VulnInfo.Confidence, -// ) -// default: -// nvd := cveInfo.CveDetail.Nvd -// return fmt.Sprintf("?\n%s\n%s\n*Confidence:* %v", -// nvd.CveSummary(), linkText, cveInfo.VulnInfo.Confidence) -// } -// } + severity := strings.ToUpper(maxCvss.Value.Severity) + if severity == "" { + severity = "?" + } -// func links(cveInfo models.CveInfo, osFamily string) string { -// links := []string{} + return fmt.Sprintf("*%4.1f (%s)* %s\n%s\n```%s```", + maxCvss.Value.Score, + severity, + cweIDs(vinfo, osFamily), + strings.Join(vectors, "\n"), + vinfo.CveContents.Summaries(config.Conf.Lang, osFamily)[0].Value, + ) +} -// //TODO -// // cweID := cveInfo.CveDetail.CweID() -// // if 0 < len(cweID) { -// // links = append(links, fmt.Sprintf("<%s|%s>", -// // cweURL(cweID), cweID)) -// // if config.Conf.Lang == "ja" { -// // links = append(links, fmt.Sprintf("<%s|%s(JVN)>", -// // cweJvnURL(cweID), cweID)) -// // } -// // } +func cweIDs(vinfo models.VulnInfo, osFamily string) string { + links := []string{} + for _, cwe := range vinfo.CveContents.CweIDs(osFamily) { + if config.Conf.Lang == "ja" { + links = append(links, fmt.Sprintf("<%s|%s>", + cweJvnURL(cwe.Value), cwe.Value)) + } else { + links = append(links, fmt.Sprintf("<%s|%s>", + cweURL(cwe.Value), cwe.Value)) + } + } + return strings.Join(links, " / ") +} -// cveID := cveInfo.VulnInfo.CveID -// //TODO -// // if config.Conf.Lang == "ja" && 0 < len(cveInfo.CveDetail.Jvn.Link()) { -// // jvn := fmt.Sprintf("<%s|JVN>", cveInfo.CveDetail.Jvn.Link()) -// // links = append(links, jvn) -// // } -// dlinks := distroLinks(cveInfo, osFamily) -// for _, link := range dlinks { -// links = append(links, -// fmt.Sprintf("<%s|%s>", link.url, link.title)) -// } -// links = append(links, fmt.Sprintf("<%s|MITRE>", -// fmt.Sprintf("%s%s", mitreBaseURL, cveID))) -// links = append(links, fmt.Sprintf("<%s|CVEDetails>", -// fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))) - -// return strings.Join(links, " / ") -// } - -// // See testcase -// func getNotifyUsers(notifyUsers []string) string { -// slackStyleTexts := []string{} -// for _, username := range notifyUsers { -// slackStyleTexts = append(slackStyleTexts, fmt.Sprintf("<%s>", username)) -// } -// return strings.Join(slackStyleTexts, " ") -// } +// See testcase +func getNotifyUsers(notifyUsers []string) string { + slackStyleTexts := []string{} + for _, username := range notifyUsers { + slackStyleTexts = append(slackStyleTexts, fmt.Sprintf("<%s>", username)) + } + return strings.Join(slackStyleTexts, " ") +} diff --git a/report/slack_test.go b/report/slack_test.go index 66e77d98..0eae9031 100644 --- a/report/slack_test.go +++ b/report/slack_test.go @@ -1,21 +1,23 @@ package report -// func TestGetNotifyUsers(t *testing.T) { -// var tests = []struct { -// in []string -// expected string -// }{ -// { -// []string{"@user1", "@user2"}, -// "<@user1> <@user2>", -// }, -// } +import "testing" -// for _, tt := range tests { -// actual := getNotifyUsers(tt.in) -// if tt.expected != actual { -// t.Errorf("expected %s, actual %s", tt.expected, actual) -// } -// } +func TestGetNotifyUsers(t *testing.T) { + var tests = []struct { + in []string + expected string + }{ + { + []string{"@user1", "@user2"}, + "<@user1> <@user2>", + }, + } -// } + for _, tt := range tests { + actual := getNotifyUsers(tt.in) + if tt.expected != actual { + t.Errorf("expected %s, actual %s", tt.expected, actual) + } + } + +} diff --git a/report/util.go b/report/util.go index 6f27aa2e..db909bcd 100644 --- a/report/util.go +++ b/report/util.go @@ -493,8 +493,8 @@ func formatOneChangelog(p models.Package) string { return "" } - packVer := fmt.Sprintf("%s -> %s", - p.FormatVer(), p.FormatNewVer()) + packVer := fmt.Sprintf("%s-%s -> %s", + p.Name, p.FormatVer(), p.FormatNewVer()) var delim bytes.Buffer for i := 0; i < len(packVer); i++ { delim.WriteString("-")