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 }