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  }