github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/protecode/report.go (about) 1 package protecode 2 3 import ( 4 "crypto/sha1" 5 "encoding/json" 6 "fmt" 7 "path/filepath" 8 "strings" 9 "time" 10 11 "github.com/pkg/errors" 12 13 "github.com/SAP/jenkins-library/pkg/log" 14 "github.com/SAP/jenkins-library/pkg/piperutils" 15 "github.com/SAP/jenkins-library/pkg/reporting" 16 ) 17 18 // ReportData is representing the data of the step report JSON 19 type ReportData struct { 20 Target string `json:"target,omitempty"` 21 Mandatory bool `json:"mandatory,omitempty"` 22 ProductID string `json:"productID,omitempty"` 23 ServerURL string `json:"serverUrl,omitempty"` 24 FailOnSevereVulnerabilities bool `json:"failOnSevereVulnerabilities,omitempty"` 25 ExcludeCVEs string `json:"excludeCVEs,omitempty"` 26 Count string `json:"count,omitempty"` 27 Cvss2GreaterOrEqualSeven string `json:"cvss2GreaterOrEqualSeven,omitempty"` 28 Cvss3GreaterOrEqualSeven string `json:"cvss3GreaterOrEqualSeven,omitempty"` 29 ExcludedVulnerabilities string `json:"excludedVulnerabilities,omitempty"` 30 TriagedVulnerabilities string `json:"triagedVulnerabilities,omitempty"` 31 HistoricalVulnerabilities string `json:"historicalVulnerabilities,omitempty"` 32 Vulnerabilities []Vuln `json:"Vulnerabilities,omitempty"` 33 } 34 35 // WriteReport ... 36 func WriteReport(data ReportData, reportPath string, reportFileName string, result map[string]int, fileUtils piperutils.FileUtils) error { 37 data.Mandatory = true 38 data.Count = fmt.Sprintf("%v", result["count"]) 39 data.Cvss2GreaterOrEqualSeven = fmt.Sprintf("%v", result["cvss2GreaterOrEqualSeven"]) 40 data.Cvss3GreaterOrEqualSeven = fmt.Sprintf("%v", result["cvss3GreaterOrEqualSeven"]) 41 data.ExcludedVulnerabilities = fmt.Sprintf("%v", result["excluded_vulnerabilities"]) 42 data.TriagedVulnerabilities = fmt.Sprintf("%v", result["triaged_vulnerabilities"]) 43 data.HistoricalVulnerabilities = fmt.Sprintf("%v", result["historical_vulnerabilities"]) 44 45 log.Entry().Infof("Protecode scan info, %v of which %v had a CVSS v2 score >= 7.0 and %v had a CVSS v3 score >= 7.0.\n %v vulnerabilities were excluded via configuration (%v) and %v vulnerabilities were triaged via the webUI.\nIn addition %v historical vulnerabilities were spotted. \n\n Vulnerabilities: %v", 46 data.Count, data.Cvss2GreaterOrEqualSeven, data.Cvss3GreaterOrEqualSeven, 47 data.ExcludedVulnerabilities, data.ExcludeCVEs, data.TriagedVulnerabilities, 48 data.HistoricalVulnerabilities, data.Vulnerabilities) 49 return writeJSON(reportPath, reportFileName, data, fileUtils) 50 } 51 52 func writeJSON(path, name string, data interface{}, fileUtils piperutils.FileUtils) error { 53 jsonData, err := json.Marshal(data) 54 if err != nil { 55 return err 56 } 57 return fileUtils.FileWrite(filepath.Join(path, name), jsonData, 0644) 58 } 59 60 func CreateCustomReport(productName string, productID int, data map[string]int, vulns []Vuln) reporting.ScanReport { 61 scanReport := reporting.ScanReport{ 62 ReportTitle: "Protecode Vulnerability Report", 63 Subheaders: []reporting.Subheader{ 64 {Description: "Product name", Details: productName}, 65 {Description: "Product ID", Details: fmt.Sprint(productID)}, 66 }, 67 Overview: []reporting.OverviewRow{ 68 {Description: "Vulnerabilities", Details: fmt.Sprint(data["vulnerabilities"])}, 69 {Description: "Major Vulnerabilities", Details: fmt.Sprint(data["major_vulnerabilities"])}, 70 {Description: "Minor Vulnerabilities", Details: fmt.Sprint(data["minor_vulnerabilities"])}, 71 {Description: "Triaged Vulnerabilities", Details: fmt.Sprint(data["triaged_vulnerabilities"])}, 72 {Description: "Excluded Vulnerabilities", Details: fmt.Sprint(data["excluded_vulnerabilities"])}, 73 }, 74 ReportTime: time.Now(), 75 } 76 77 detailTable := reporting.ScanDetailTable{ 78 NoRowsMessage: "No findings detected", 79 Headers: []string{ 80 "Issue CVE", 81 "CVSS Score", 82 "CVSS v3 Score", 83 }, 84 WithCounter: true, 85 CounterHeader: "Entry #", 86 } 87 88 for _, vuln := range vulns { 89 row := reporting.ScanRow{} 90 row.AddColumn(fmt.Sprint(*&vuln.Cve), 0) 91 row.AddColumn(fmt.Sprint(*&vuln.Cvss), 0) 92 row.AddColumn(fmt.Sprint(*&vuln.Cvss3Score), 0) 93 94 detailTable.Rows = append(detailTable.Rows, row) 95 } 96 scanReport.DetailTable = detailTable 97 98 return scanReport 99 } 100 101 func WriteCustomReports(scanReport reporting.ScanReport, projectName, projectID string, fileUtils piperutils.FileUtils) ([]piperutils.Path, error) { 102 reportPaths := []piperutils.Path{} 103 104 // ignore templating errors since template is in our hands and issues will be detected with the automated tests 105 htmlReport, _ := scanReport.ToHTML() 106 htmlReportPath := filepath.Join(ReportsDirectory, "piper_protecode_report.html") 107 // Ensure reporting directory exists 108 if err := fileUtils.MkdirAll(ReportsDirectory, 0777); err != nil { 109 return reportPaths, errors.Wrapf(err, "failed to create report directory") 110 } 111 if err := fileUtils.FileWrite(htmlReportPath, htmlReport, 0666); err != nil { 112 log.SetErrorCategory(log.ErrorConfiguration) 113 return reportPaths, errors.Wrapf(err, "failed to write html report") 114 } 115 reportPaths = append(reportPaths, piperutils.Path{Name: "Protecode Vulnerability Report", Target: htmlReportPath}) 116 117 // JSON reports are used by step pipelineCreateSummary in order to e.g. prepare an issue creation in GitHub 118 // ignore JSON errors since structure is in our hands 119 jsonReport, _ := scanReport.ToJSON() 120 if exists, _ := fileUtils.DirExists(reporting.StepReportDirectory); !exists { 121 err := fileUtils.MkdirAll(reporting.StepReportDirectory, 0777) 122 if err != nil { 123 return reportPaths, errors.Wrap(err, "failed to create reporting directory") 124 } 125 } 126 if err := fileUtils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("protecodeExecuteScan_osvm_%v.json", reportShaProtecode([]string{projectName, projectID}))), jsonReport, 0666); err != nil { 127 return reportPaths, errors.Wrapf(err, "failed to write json report") 128 } 129 // we do not add the json report to the overall list of reports for now, 130 // since it is just an intermediary report used as input for later 131 // and there does not seem to be real benefit in archiving it. 132 133 return reportPaths, nil 134 } 135 136 func reportShaProtecode(parts []string) string { 137 reportShaData := []byte(strings.Join(parts, ",")) 138 return fmt.Sprintf("%x", sha1.Sum(reportShaData)) 139 }