github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/blackduck/reporting.go (about)

     1  package blackduck
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"fmt"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  
    11  	"github.com/SAP/jenkins-library/pkg/format"
    12  	"github.com/SAP/jenkins-library/pkg/log"
    13  	"github.com/SAP/jenkins-library/pkg/piperutils"
    14  	"github.com/SAP/jenkins-library/pkg/reporting"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  var severityIndex = map[string]int{"LOW": 1, "MEDIUM": 2, "HIGH": 3, "CRITICAL": 4}
    19  
    20  // CreateSarifResultFile creates a SARIF result from the Vulnerabilities that were brought up by the scan
    21  func CreateSarifResultFile(vulns *Vulnerabilities, projectName, projectVersion, projectLink string) *format.SARIF {
    22  	log.Entry().Debug("Creating SARIF file for data transfer")
    23  
    24  	// Handle results/vulnerabilities
    25  	rules := []format.SarifRule{}
    26  	collectedRules := []string{}
    27  	cweIdsForTaxonomies := []string{}
    28  	results := []format.Results{}
    29  
    30  	if vulns != nil && vulns.Items != nil {
    31  		for _, v := range vulns.Items {
    32  
    33  			isAudited := true
    34  			if v.RemediationStatus == "NEW" || v.RemediationStatus == "REMEDIATION_REQUIRED" ||
    35  				v.RemediationStatus == "NEEDS_REVIEW" {
    36  				isAudited = false
    37  			}
    38  
    39  			unifiedStatusValue := "new"
    40  
    41  			switch v.RemediationStatus {
    42  			case "NEW":
    43  				unifiedStatusValue = "new"
    44  			case "NEEDS_REVIEW":
    45  				unifiedStatusValue = "inProcess"
    46  			case "REMEDIATION_COMPLETE":
    47  				unifiedStatusValue = "notRelevant"
    48  			case "PATCHED":
    49  				unifiedStatusValue = "notRelevant"
    50  			case "MITIGATED":
    51  				unifiedStatusValue = "notRelevant"
    52  			case "DUPLICATE":
    53  				unifiedStatusValue = "notRelevant"
    54  			case "IGNORED":
    55  				unifiedStatusValue = "notRelevant"
    56  			case "REMEDIATION_REQUIRED":
    57  				unifiedStatusValue = "relevant"
    58  			}
    59  
    60  			log.Entry().Debugf("Transforming alert %v on Package %v Version %v into SARIF format", v.VulnerabilityWithRemediation.VulnerabilityName, v.Component.Name, v.Component.Version)
    61  			result := format.Results{
    62  				RuleID:  v.VulnerabilityWithRemediation.VulnerabilityName,
    63  				Level:   transformToLevel(v.VulnerabilityWithRemediation.Severity),
    64  				Message: &format.Message{Text: v.VulnerabilityWithRemediation.Description},
    65  				AnalysisTarget: &format.ArtifactLocation{
    66  					URI:   v.Component.ToPackageUrl().ToString(),
    67  					Index: 0,
    68  				},
    69  				Locations: []format.Location{{PhysicalLocation: format.PhysicalLocation{ArtifactLocation: format.ArtifactLocation{URI: v.Name}}}},
    70  				PartialFingerprints: format.PartialFingerprints{
    71  					PackageURLPlusCVEHash: base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%v+%v", v.Component.ToPackageUrl().ToString(), v.CweID))),
    72  				},
    73  				Properties: &format.SarifProperties{
    74  					Audited:               isAudited,
    75  					ToolSeverity:          v.Severity,
    76  					ToolSeverityIndex:     severityIndex[v.Severity],
    77  					ToolState:             v.RemediationStatus,
    78  					ToolAuditMessage:      v.VulnerabilityWithRemediation.RemediationComment,
    79  					UnifiedAuditState:     unifiedStatusValue,
    80  					UnifiedSeverity:       strings.ToLower(v.Severity),
    81  					UnifiedCriticality:    v.BaseScore,
    82  					UnifiedAuditUser:      v.VulnerabilityWithRemediation.RemidiatedBy,
    83  					AuditRequirement:      format.AUDIT_REQUIREMENT_GROUP_1_DESC,
    84  					AuditRequirementIndex: format.AUDIT_REQUIREMENT_GROUP_1_INDEX,
    85  				},
    86  			}
    87  
    88  			// append the result
    89  			results = append(results, result)
    90  
    91  			// append taxonomies
    92  			if len(v.VulnerabilityWithRemediation.CweID) > 0 && !piperutils.ContainsString(cweIdsForTaxonomies, v.VulnerabilityWithRemediation.CweID) {
    93  				cweIdsForTaxonomies = append(cweIdsForTaxonomies, v.VulnerabilityWithRemediation.CweID)
    94  			}
    95  
    96  			// only create rule on new CVE
    97  			if !piperutils.ContainsString(collectedRules, result.RuleID) {
    98  				collectedRules = append(collectedRules, result.RuleID)
    99  
   100  				// set information about BlackDuck project
   101  				v.projectVersionLink = projectLink
   102  				v.projectName = projectName
   103  				v.projectVersion = projectVersion
   104  
   105  				markdown, _ := v.ToMarkdown()
   106  
   107  				tags := []string{
   108  					"SECURITY_VULNERABILITY",
   109  					v.Component.ToPackageUrl().ToString(),
   110  				}
   111  
   112  				if CweID := v.VulnerabilityWithRemediation.CweID; CweID != "" {
   113  					tags = append(tags, CweID)
   114  				}
   115  
   116  				if matchedType := v.Component.MatchedType(); matchedType != "" {
   117  					tags = append(tags, matchedType)
   118  				}
   119  
   120  				ruleProp := format.SarifRuleProperties{
   121  					Tags:             tags,
   122  					Precision:        "very-high",
   123  					Impact:           fmt.Sprint(v.VulnerabilityWithRemediation.ImpactSubscore),
   124  					Probability:      fmt.Sprint(v.VulnerabilityWithRemediation.ExploitabilitySubscore),
   125  					SecuritySeverity: fmt.Sprint(v.OverallScore),
   126  				}
   127  				sarifRule := format.SarifRule{
   128  					ID:                   result.RuleID,
   129  					ShortDescription:     &format.Message{Text: fmt.Sprintf("%v in Package %v", v.VulnerabilityName, v.Component.Name)},
   130  					FullDescription:      &format.Message{Text: v.VulnerabilityWithRemediation.Description},
   131  					DefaultConfiguration: &format.DefaultConfiguration{Level: transformToLevel(v.VulnerabilityWithRemediation.Severity)},
   132  					HelpURI:              "",
   133  					Help:                 &format.Help{Text: v.ToTxt(), Markdown: string(markdown)},
   134  					Properties:           &ruleProp,
   135  				}
   136  				// append the rule
   137  				rules = append(rules, sarifRule)
   138  			}
   139  
   140  		}
   141  	}
   142  
   143  	//handle taxonomies
   144  	//Only one exists apparently: CWE. It is fixed
   145  	taxas := []format.Taxa{}
   146  	for _, value := range cweIdsForTaxonomies {
   147  		taxa := format.Taxa{Id: value}
   148  		taxas = append(taxas, taxa)
   149  	}
   150  	taxonomy := format.Taxonomies{
   151  		GUID:             "25F72D7E-8A92-459D-AD67-64853F788765",
   152  		Name:             "CWE",
   153  		Organization:     "MITRE",
   154  		ShortDescription: format.Message{Text: "The MITRE Common Weakness Enumeration"},
   155  		Taxa:             taxas,
   156  	}
   157  	//handle the tool object
   158  	tool := format.Tool{
   159  		Driver: format.Driver{
   160  			Name:           "Black Duck",
   161  			Version:        "unknown",
   162  			InformationUri: "https://community.synopsys.com/s/document-item?bundleId=integrations-detect&topicId=introduction.html&_LANG=enus",
   163  			Rules:          rules,
   164  		},
   165  	}
   166  	sarif := format.SARIF{
   167  		Schema:  "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json",
   168  		Version: "2.1.0",
   169  		Runs: []format.Runs{
   170  			{
   171  				Results:             results,
   172  				Tool:                tool,
   173  				ThreadFlowLocations: []format.Locations{},
   174  				Conversion: &format.Conversion{
   175  					Tool: format.Tool{
   176  						Driver: format.Driver{
   177  							Name:           "Piper FPR to SARIF converter",
   178  							InformationUri: "https://github.com/SAP/jenkins-library",
   179  						},
   180  					},
   181  					Invocation: format.Invocation{
   182  						ExecutionSuccessful: true,
   183  						Properties:          &format.InvocationProperties{Platform: runtime.GOOS},
   184  					},
   185  				},
   186  				Taxonomies: []format.Taxonomies{taxonomy},
   187  			},
   188  		},
   189  	}
   190  
   191  	return &sarif
   192  }
   193  
   194  func transformToLevel(severity string) string {
   195  	switch severity {
   196  	case "LOW":
   197  		return "warning"
   198  	case "MEDIUM":
   199  		return "warning"
   200  	case "HIGH":
   201  		return "error"
   202  	case "CRITICAL":
   203  		return "error"
   204  	}
   205  	return "none"
   206  }
   207  
   208  // WriteVulnerabilityReports writes vulnerability information from ScanReport into dedicated outputs e.g. HTML
   209  func WriteVulnerabilityReports(scanReport reporting.ScanReport, utils piperutils.FileUtils) ([]piperutils.Path, error) {
   210  	reportPaths := []piperutils.Path{}
   211  
   212  	htmlReport, _ := scanReport.ToHTML()
   213  	htmlReportPath := "piper_detect_vulnerability_report.html"
   214  	if err := utils.FileWrite(htmlReportPath, htmlReport, 0666); err != nil {
   215  		log.SetErrorCategory(log.ErrorConfiguration)
   216  		return reportPaths, errors.Wrapf(err, "failed to write html report")
   217  	}
   218  	reportPaths = append(reportPaths, piperutils.Path{Name: "BlackDuck Vulnerability Report", Target: htmlReportPath})
   219  
   220  	jsonReport, _ := scanReport.ToJSON()
   221  	if exists, _ := utils.DirExists(reporting.StepReportDirectory); !exists {
   222  		err := utils.MkdirAll(reporting.StepReportDirectory, 0777)
   223  		if err != nil {
   224  			return reportPaths, errors.Wrap(err, "failed to create reporting directory")
   225  		}
   226  	}
   227  	if err := utils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("detectExecuteScan_oss_%v.json", fmt.Sprintf("%v", utils.CurrentTime("")))), jsonReport, 0666); err != nil {
   228  		return reportPaths, errors.Wrapf(err, "failed to write json report")
   229  	}
   230  
   231  	return reportPaths, nil
   232  }
   233  
   234  // WriteSarifFile write a JSON sarif format file for upload into e.g. GCP
   235  func WriteSarifFile(sarif *format.SARIF, utils piperutils.FileUtils) ([]piperutils.Path, error) {
   236  	reportPaths := []piperutils.Path{}
   237  
   238  	// ignore templating errors since template is in our hands and issues will be detected with the automated tests
   239  	sarifReport, errorMarshall := json.Marshal(sarif)
   240  	if errorMarshall != nil {
   241  		return reportPaths, errors.Wrapf(errorMarshall, "failed to marshall SARIF json file")
   242  	}
   243  	if err := utils.MkdirAll(ReportsDirectory, 0777); err != nil {
   244  		return reportPaths, errors.Wrapf(err, "failed to create report directory")
   245  	}
   246  
   247  	sarifReportPath := filepath.Join(ReportsDirectory, "piper_detect_vulnerability.sarif")
   248  	if err := utils.FileWrite(sarifReportPath, sarifReport, 0666); err != nil {
   249  		log.SetErrorCategory(log.ErrorConfiguration)
   250  		return reportPaths, errors.Wrapf(err, "failed to write SARIF file")
   251  	}
   252  	reportPaths = append(reportPaths, piperutils.Path{Name: "Blackduck Detect Vulnerability SARIF file", Target: sarifReportPath})
   253  
   254  	return reportPaths, nil
   255  }