github.com/jaylevin/jenkins-library@v1.230.4/pkg/checkmarx/reporting.go (about) 1 package checkmarx 2 3 import ( 4 "bytes" 5 "crypto/sha1" 6 "encoding/json" 7 "fmt" 8 "path/filepath" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/SAP/jenkins-library/pkg/format" 14 "github.com/SAP/jenkins-library/pkg/log" 15 "github.com/SAP/jenkins-library/pkg/piperutils" 16 "github.com/SAP/jenkins-library/pkg/reporting" 17 18 "github.com/pkg/errors" 19 ) 20 21 type CheckmarxReportData struct { 22 ToolName string `json:"toolName"` 23 ProjectName string `json:"projectName"` 24 ProjectID int64 `json:"projectID"` 25 ScanID int64 `json:"scanID"` 26 TeamName string `json:"teamName"` 27 TeamPath string `json:"teamPath"` 28 DeepLink string `json:"deepLink"` 29 Preset string `json:"preset"` 30 CheckmarxVersion string `json:"checkmarxVersion"` 31 ScanType string `json:"scanType"` 32 HighTotal int `json:"highTotal"` 33 HighAudited int `json:"highAudited"` 34 MediumTotal int `json:"mediumTotal"` 35 MediumAudited int `json:"mediumAudited"` 36 LowTotal int `json:"lowTotal"` 37 LowAudited int `json:"lowAudited"` 38 InformationTotal int `json:"informationTotal"` 39 InformationAudited int `json:"informationAudited"` 40 } 41 42 func CreateCustomReport(data map[string]interface{}, insecure, neutral []string) reporting.ScanReport { 43 deepLink := fmt.Sprintf(`<a href="%v" target="_blank">Link to scan in CX UI</a>`, data["DeepLink"]) 44 45 scanReport := reporting.ScanReport{ 46 ReportTitle: "Checkmarx SAST Report", 47 Subheaders: []reporting.Subheader{ 48 {Description: "Project name", Details: fmt.Sprint(data["ProjectName"])}, 49 {Description: "Project ID", Details: fmt.Sprint(data["ProjectID"])}, 50 {Description: "Owner", Details: fmt.Sprint(data["Owner"])}, 51 {Description: "Scan ID", Details: fmt.Sprint(data["ScanID"])}, 52 {Description: "Team", Details: fmt.Sprint(data["Team"])}, 53 {Description: "Team full path", Details: fmt.Sprint(data["TeamFullPathOnReportDate"])}, 54 {Description: "Scan start", Details: fmt.Sprint(data["ScanStart"])}, 55 {Description: "Scan duration", Details: fmt.Sprint(data["ScanTime"])}, 56 {Description: "Scan type", Details: fmt.Sprint(data["ScanType"])}, 57 {Description: "Preset", Details: fmt.Sprint(data["Preset"])}, 58 {Description: "Report creation time", Details: fmt.Sprint(data["ReportCreationTime"])}, 59 {Description: "Lines of code scanned", Details: fmt.Sprint(data["LinesOfCodeScanned)"])}, 60 {Description: "Files scanned", Details: fmt.Sprint(data["FilesScanned)"])}, 61 {Description: "Checkmarx version", Details: fmt.Sprint(data["CheckmarxVersion"])}, 62 {Description: "Deep link", Details: deepLink}, 63 }, 64 Overview: []reporting.OverviewRow{}, 65 ReportTime: time.Now(), 66 } 67 68 for _, issue := range insecure { 69 row := reporting.OverviewRow{} 70 row.Description = fmt.Sprint(issue) 71 row.Style = reporting.Red 72 73 scanReport.Overview = append(scanReport.Overview, row) 74 } 75 for _, issue := range neutral { 76 row := reporting.OverviewRow{} 77 row.Description = fmt.Sprint(issue) 78 79 scanReport.Overview = append(scanReport.Overview, row) 80 } 81 82 detailTable := reporting.ScanDetailTable{ 83 Headers: []string{ 84 "KPI", 85 "Count", 86 }, 87 WithCounter: false, 88 } 89 detailRows := []reporting.OverviewRow{ 90 {Description: "High issues", Details: fmt.Sprint(data["High"].(map[string]int)["Issues"])}, 91 {Description: "High not false positive issues", Details: fmt.Sprint(data["High"].(map[string]int)["NotFalsePositive"])}, 92 {Description: "High not exploitable issues", Details: fmt.Sprint(data["High"].(map[string]int)["NotExploitable"])}, 93 {Description: "High confirmed issues", Details: fmt.Sprint(data["High"].(map[string]int)["Confirmed"])}, 94 {Description: "High urgent issues", Details: fmt.Sprint(data["High"].(map[string]int)["Urgent"])}, 95 {Description: "High proposed not exploitable issues", Details: fmt.Sprint(data["High"].(map[string]int)["ProposedNotExploitable"])}, 96 {Description: "High to verify issues", Details: fmt.Sprint(data["High"].(map[string]int)["ToVerify"])}, 97 {Description: "Medium issues", Details: fmt.Sprint(data["Medium"].(map[string]int)["Issues"])}, 98 {Description: "Medium not false positive issues", Details: fmt.Sprint(data["Medium"].(map[string]int)["NotFalsePositive"])}, 99 {Description: "Medium not exploitable issues", Details: fmt.Sprint(data["Medium"].(map[string]int)["NotExploitable"])}, 100 {Description: "Medium confirmed issues", Details: fmt.Sprint(data["Medium"].(map[string]int)["Confirmed"])}, 101 {Description: "Medium urgent issues", Details: fmt.Sprint(data["Medium"].(map[string]int)["Urgent"])}, 102 {Description: "Medium proposed not exploitable issues", Details: fmt.Sprint(data["Medium"].(map[string]int)["ProposedNotExploitable"])}, 103 {Description: "Medium to verify issues", Details: fmt.Sprint(data["Medium"].(map[string]int)["ToVerify"])}, 104 {Description: "Low issues", Details: fmt.Sprint(data["Low"].(map[string]int)["Issues"])}, 105 {Description: "Low not false positive issues", Details: fmt.Sprint(data["Low"].(map[string]int)["NotFalsePositive"])}, 106 {Description: "Low not exploitable issues", Details: fmt.Sprint(data["Low"].(map[string]int)["NotExploitable"])}, 107 {Description: "Low confirmed issues", Details: fmt.Sprint(data["Low"].(map[string]int)["Confirmed"])}, 108 {Description: "Low urgent issues", Details: fmt.Sprint(data["Low"].(map[string]int)["Urgent"])}, 109 {Description: "Low proposed not exploitable issues", Details: fmt.Sprint(data["Low"].(map[string]int)["ProposedNotExploitable"])}, 110 {Description: "Low to verify issues", Details: fmt.Sprint(data["Low"].(map[string]int)["ToVerify"])}, 111 {Description: "Informational issues", Details: fmt.Sprint(data["Information"].(map[string]int)["Issues"])}, 112 {Description: "Informational not false positive issues", Details: fmt.Sprint(data["Information"].(map[string]int)["NotFalsePositive"])}, 113 {Description: "Informational not exploitable issues", Details: fmt.Sprint(data["Information"].(map[string]int)["NotExploitable"])}, 114 {Description: "Informational confirmed issues", Details: fmt.Sprint(data["Information"].(map[string]int)["Confirmed"])}, 115 {Description: "Informational urgent issues", Details: fmt.Sprint(data["Information"].(map[string]int)["Urgent"])}, 116 {Description: "Informational proposed not exploitable issues", Details: fmt.Sprint(data["Information"].(map[string]int)["ProposedNotExploitable"])}, 117 {Description: "Informational to verify issues", Details: fmt.Sprint(data["Information"].(map[string]int)["ToVerify"])}, 118 } 119 for _, detailRow := range detailRows { 120 row := reporting.ScanRow{} 121 row.AddColumn(detailRow.Description, 0) 122 row.AddColumn(detailRow.Details, 0) 123 124 detailTable.Rows = append(detailTable.Rows, row) 125 } 126 scanReport.DetailTable = detailTable 127 128 return scanReport 129 } 130 131 func CreateJSONReport(data map[string]interface{}) CheckmarxReportData { 132 checkmarxReportData := CheckmarxReportData{ 133 ToolName: `checkmarx`, 134 ProjectName: fmt.Sprint(data["ProjectName"]), 135 TeamName: fmt.Sprint(data["Team"]), 136 TeamPath: fmt.Sprint(data["TeamFullPathOnReportDate"]), 137 DeepLink: fmt.Sprint(data["DeepLink"]), 138 Preset: fmt.Sprint(data["Preset"]), 139 CheckmarxVersion: fmt.Sprint(data["CheckmarxVersion"]), 140 ScanType: fmt.Sprint(data["ScanType"]), 141 } 142 143 if s, err := strconv.ParseInt(fmt.Sprint(data["ProjectId"]), 10, 64); err == nil { 144 checkmarxReportData.ProjectID = s 145 } 146 147 if s, err := strconv.ParseInt(fmt.Sprint(data["ScanId"]), 10, 64); err == nil { 148 checkmarxReportData.ScanID = s 149 } 150 151 checkmarxReportData.HighAudited = data["High"].(map[string]int)["Issues"] - data["High"].(map[string]int)["NotFalsePositive"] 152 checkmarxReportData.HighTotal = data["High"].(map[string]int)["Issues"] 153 154 checkmarxReportData.MediumAudited = data["Medium"].(map[string]int)["Issues"] - data["Medium"].(map[string]int)["NotFalsePositive"] 155 checkmarxReportData.MediumTotal = data["Medium"].(map[string]int)["Issues"] 156 157 checkmarxReportData.LowAudited = data["Low"].(map[string]int)["Issues"] - data["Low"].(map[string]int)["NotFalsePositive"] 158 checkmarxReportData.LowTotal = data["Low"].(map[string]int)["Issues"] 159 160 checkmarxReportData.InformationAudited = data["Information"].(map[string]int)["Issues"] - data["Information"].(map[string]int)["NotFalsePositive"] 161 checkmarxReportData.InformationTotal = data["Information"].(map[string]int)["Issues"] 162 163 return checkmarxReportData 164 } 165 166 func WriteJSONReport(jsonReport CheckmarxReportData) ([]piperutils.Path, error) { 167 utils := piperutils.Files{} 168 reportPaths := []piperutils.Path{} 169 170 // Standard JSON Report 171 jsonComplianceReportPath := filepath.Join(ReportsDirectory, "piper_checkmarx_report.json") 172 // Ensure reporting directory exists 173 if err := utils.MkdirAll(ReportsDirectory, 0777); err != nil { 174 return reportPaths, errors.Wrapf(err, "failed to create report directory") 175 } 176 177 file, _ := json.Marshal(jsonReport) 178 if err := utils.FileWrite(jsonComplianceReportPath, file, 0666); err != nil { 179 log.SetErrorCategory(log.ErrorConfiguration) 180 return reportPaths, errors.Wrapf(err, "failed to write Checkmarx JSON compliance report") 181 } 182 reportPaths = append(reportPaths, piperutils.Path{Name: "Checkmarx JSON Compliance Report", Target: jsonComplianceReportPath}) 183 184 return reportPaths, nil 185 } 186 187 // WriteSarif writes a json file to disk as a .sarif if it respects the specification declared in format.SARIF 188 func WriteSarif(sarif format.SARIF) ([]piperutils.Path, error) { 189 utils := piperutils.Files{} 190 reportPaths := []piperutils.Path{} 191 192 sarifReportPath := filepath.Join(ReportsDirectory, "result.sarif") 193 // Ensure reporting directory exists 194 if err := utils.MkdirAll(ReportsDirectory, 0777); err != nil { 195 return reportPaths, errors.Wrapf(err, "failed to create report directory") 196 } 197 198 // HTML characters will most likely be present: we need to use encode: create a buffer to hold JSON data 199 buffer := new(bytes.Buffer) 200 // create JSON encoder for buffer 201 bufEncoder := json.NewEncoder(buffer) 202 // set options 203 bufEncoder.SetEscapeHTML(false) 204 bufEncoder.SetIndent("", " ") 205 //encode to buffer 206 bufEncoder.Encode(sarif) 207 log.Entry().Info("Writing file to disk: ", sarifReportPath) 208 if err := utils.FileWrite(sarifReportPath, buffer.Bytes(), 0666); err != nil { 209 log.SetErrorCategory(log.ErrorConfiguration) 210 return reportPaths, errors.Wrapf(err, "failed to write Checkmarx SARIF report") 211 } 212 reportPaths = append(reportPaths, piperutils.Path{Name: "Checkmarx SARIF Report", Target: sarifReportPath}) 213 214 return reportPaths, nil 215 } 216 217 func WriteCustomReports(scanReport reporting.ScanReport, projectName, projectID string) ([]piperutils.Path, error) { 218 utils := piperutils.Files{} 219 reportPaths := []piperutils.Path{} 220 221 // ignore templating errors since template is in our hands and issues will be detected with the automated tests 222 htmlReport, _ := scanReport.ToHTML() 223 htmlReportPath := filepath.Join(ReportsDirectory, "piper_checkmarx_report.html") 224 // Ensure reporting directory exists 225 if err := utils.MkdirAll(ReportsDirectory, 0777); err != nil { 226 return reportPaths, errors.Wrapf(err, "failed to create report directory") 227 } 228 if err := utils.FileWrite(htmlReportPath, htmlReport, 0666); err != nil { 229 log.SetErrorCategory(log.ErrorConfiguration) 230 return reportPaths, errors.Wrapf(err, "failed to write html report") 231 } 232 reportPaths = append(reportPaths, piperutils.Path{Name: "Checkmarx Report", Target: htmlReportPath}) 233 234 // JSON reports are used by step pipelineCreateSummary in order to e.g. prepare an issue creation in GitHub 235 // ignore JSON errors since structure is in our hands 236 jsonReport, _ := scanReport.ToJSON() 237 if exists, _ := utils.DirExists(reporting.StepReportDirectory); !exists { 238 err := utils.MkdirAll(reporting.StepReportDirectory, 0777) 239 if err != nil { 240 return reportPaths, errors.Wrap(err, "failed to create reporting directory") 241 } 242 } 243 if err := utils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("checkmarxExecuteScan_sast_%v.json", reportShaCheckmarx([]string{projectName, projectID}))), jsonReport, 0666); err != nil { 244 return reportPaths, errors.Wrapf(err, "failed to write json report") 245 } 246 // we do not add the json report to the overall list of reports for now, 247 // since it is just an intermediary report used as input for later 248 // and there does not seem to be real benefit in archiving it. 249 250 return reportPaths, nil 251 } 252 253 func reportShaCheckmarx(parts []string) string { 254 reportShaData := []byte(strings.Join(parts, ",")) 255 return fmt.Sprintf("%x", sha1.Sum(reportShaData)) 256 }