github.com/jaylevin/jenkins-library@v1.230.4/cmd/fortifyExecuteScan.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "math" 9 "os" 10 "path/filepath" 11 "regexp" 12 "runtime" 13 "strconv" 14 "strings" 15 "time" 16 17 piperhttp "github.com/SAP/jenkins-library/pkg/http" 18 19 "github.com/bmatcuk/doublestar" 20 21 "github.com/google/go-github/v32/github" 22 "github.com/google/uuid" 23 24 "github.com/piper-validation/fortify-client-go/models" 25 26 "github.com/SAP/jenkins-library/pkg/command" 27 "github.com/SAP/jenkins-library/pkg/fortify" 28 "github.com/SAP/jenkins-library/pkg/gradle" 29 "github.com/SAP/jenkins-library/pkg/log" 30 "github.com/SAP/jenkins-library/pkg/maven" 31 "github.com/SAP/jenkins-library/pkg/piperutils" 32 "github.com/SAP/jenkins-library/pkg/reporting" 33 "github.com/SAP/jenkins-library/pkg/telemetry" 34 "github.com/SAP/jenkins-library/pkg/toolrecord" 35 "github.com/SAP/jenkins-library/pkg/versioning" 36 37 piperGithub "github.com/SAP/jenkins-library/pkg/github" 38 39 "github.com/pkg/errors" 40 ) 41 42 const getClasspathScriptContent = ` 43 gradle.allprojects { 44 task getClasspath { 45 doLast { 46 new File(projectDir, filename).text = sourceSets.main.compileClasspath.asPath 47 } 48 } 49 } 50 ` 51 52 type pullRequestService interface { 53 ListPullRequestsWithCommit(ctx context.Context, owner, repo, sha string, opts *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error) 54 } 55 56 type fortifyUtils interface { 57 maven.Utils 58 gradle.Utils 59 60 SetDir(d string) 61 GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) 62 CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error 63 } 64 65 type fortifyUtilsBundle struct { 66 *command.Command 67 *piperutils.Files 68 *piperhttp.Client 69 } 70 71 func (f *fortifyUtilsBundle) GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) { 72 return versioning.GetArtifact(buildTool, buildDescriptorFile, options, f) 73 } 74 75 func (f *fortifyUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { 76 return piperGithub.CreateIssue(ghCreateIssueOptions) 77 } 78 79 func newFortifyUtilsBundle() fortifyUtils { 80 utils := fortifyUtilsBundle{ 81 Command: &command.Command{}, 82 Files: &piperutils.Files{}, 83 Client: &piperhttp.Client{}, 84 } 85 utils.Stdout(log.Writer()) 86 utils.Stderr(log.Writer()) 87 return &utils 88 } 89 90 const checkString = "<---CHECK FORTIFY---" 91 const classpathFileName = "fortify-execute-scan-cp.txt" 92 93 func fortifyExecuteScan(config fortifyExecuteScanOptions, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux) { 94 auditStatus := map[string]string{} 95 sys := fortify.NewSystemInstance(config.ServerURL, config.APIEndpoint, config.AuthToken, time.Minute*15) 96 utils := newFortifyUtilsBundle() 97 98 influx.step_data.fields.fortify = false 99 reports, err := runFortifyScan(config, sys, utils, telemetryData, influx, auditStatus) 100 piperutils.PersistReportsAndLinks("fortifyExecuteScan", config.ModulePath, reports, nil) 101 if err != nil { 102 log.Entry().WithError(err).Fatal("Fortify scan and check failed") 103 } 104 influx.step_data.fields.fortify = true 105 // make sure that no specific error category is set in success case 106 log.SetErrorCategory(log.ErrorUndefined) 107 } 108 109 func determineArtifact(config fortifyExecuteScanOptions, utils fortifyUtils) (versioning.Artifact, error) { 110 versioningOptions := versioning.Options{ 111 M2Path: config.M2Path, 112 GlobalSettingsFile: config.GlobalSettingsFile, 113 ProjectSettingsFile: config.ProjectSettingsFile, 114 } 115 116 artifact, err := utils.GetArtifact(config.BuildTool, config.BuildDescriptorFile, &versioningOptions) 117 if err != nil { 118 return nil, fmt.Errorf("Unable to get artifact from descriptor %v: %w", config.BuildDescriptorFile, err) 119 } 120 return artifact, nil 121 } 122 123 func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils fortifyUtils, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux, auditStatus map[string]string) ([]piperutils.Path, error) { 124 var reports []piperutils.Path 125 log.Entry().Debugf("Running Fortify scan against SSC at %v", config.ServerURL) 126 127 if config.BuildTool == "maven" && config.InstallArtifacts { 128 err := maven.InstallMavenArtifacts(&maven.EvaluateOptions{ 129 M2Path: config.M2Path, 130 ProjectSettingsFile: config.ProjectSettingsFile, 131 GlobalSettingsFile: config.GlobalSettingsFile, 132 PomPath: config.BuildDescriptorFile, 133 }, utils) 134 if err != nil { 135 return reports, fmt.Errorf("Unable to install artifacts: %w", err) 136 } 137 } 138 139 artifact, err := determineArtifact(config, utils) 140 if err != nil { 141 log.Entry().WithError(err).Fatal() 142 } 143 coordinates, err := artifact.GetCoordinates() 144 if err != nil { 145 log.SetErrorCategory(log.ErrorConfiguration) 146 return reports, fmt.Errorf("unable to get project coordinates from descriptor %v: %w", config.BuildDescriptorFile, err) 147 } 148 log.Entry().Debugf("loaded project coordinates %v from descriptor", coordinates) 149 150 if len(config.Version) > 0 { 151 log.Entry().Infof("Resolving product version from default provided '%s' with versioning '%s'", config.Version, config.VersioningModel) 152 coordinates.Version = config.Version 153 } 154 155 fortifyProjectName, fortifyProjectVersion := versioning.DetermineProjectCoordinatesWithCustomVersion(config.ProjectName, config.VersioningModel, config.CustomScanVersion, coordinates) 156 project, err := sys.GetProjectByName(fortifyProjectName, config.AutoCreate, fortifyProjectVersion) 157 if err != nil { 158 classifyErrorOnLookup(err) 159 return reports, fmt.Errorf("Failed to load project %v: %w", fortifyProjectName, err) 160 } 161 projectVersion, err := sys.GetProjectVersionDetailsByProjectIDAndVersionName(project.ID, fortifyProjectVersion, config.AutoCreate, fortifyProjectName) 162 if err != nil { 163 classifyErrorOnLookup(err) 164 return reports, fmt.Errorf("Failed to load project version %v: %w", fortifyProjectVersion, err) 165 } 166 167 if len(config.PullRequestName) > 0 { 168 fortifyProjectVersion = config.PullRequestName 169 projectVersion, err = sys.LookupOrCreateProjectVersionDetailsForPullRequest(project.ID, projectVersion, fortifyProjectVersion) 170 if err != nil { 171 classifyErrorOnLookup(err) 172 return reports, fmt.Errorf("Failed to lookup / create project version for pull request %v: %w", fortifyProjectVersion, err) 173 } 174 log.Entry().Debugf("Looked up / created project version with ID %v for PR %v", projectVersion.ID, fortifyProjectVersion) 175 } else { 176 prID, prAuthor := determinePullRequestMerge(config) 177 if prID != "0" { 178 log.Entry().Debugf("Determined PR ID '%v' for merge check", prID) 179 if len(prAuthor) > 0 && !piperutils.ContainsString(config.Assignees, prAuthor) { 180 log.Entry().Debugf("Determined PR Author '%v' for result assignment", prAuthor) 181 config.Assignees = append(config.Assignees, prAuthor) 182 } else { 183 log.Entry().Debugf("Unable to determine PR Author, using assignees: %v", config.Assignees) 184 } 185 pullRequestProjectName := fmt.Sprintf("PR-%v", prID) 186 err = sys.MergeProjectVersionStateOfPRIntoMaster(config.FprDownloadEndpoint, config.FprUploadEndpoint, project.ID, projectVersion.ID, pullRequestProjectName) 187 if err != nil { 188 return reports, fmt.Errorf("Failed to merge project version state for pull request %v into project version %v of project %v: %w", pullRequestProjectName, fortifyProjectVersion, project.ID, err) 189 } 190 } 191 } 192 193 filterSet, err := sys.GetFilterSetOfProjectVersionByTitle(projectVersion.ID, config.FilterSetTitle) 194 if filterSet == nil || err != nil { 195 return reports, fmt.Errorf("Failed to load filter set with title %v", config.FilterSetTitle) 196 } 197 198 // create toolrecord file 199 // tbd - how to handle verifyOnly 200 toolRecordFileName, err := createToolRecordFortify("./", config, project.ID, fortifyProjectName, projectVersion.ID, fortifyProjectVersion) 201 if err != nil { 202 // do not fail until the framework is well established 203 log.Entry().Warning("TR_FORTIFY: Failed to create toolrecord file ...", err) 204 } else { 205 reports = append(reports, piperutils.Path{Target: toolRecordFileName}) 206 } 207 208 if config.VerifyOnly { 209 log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) 210 err, paths := verifyFFProjectCompliance(config, utils, sys, project, projectVersion, filterSet, influx, auditStatus) 211 reports = append(reports, paths...) 212 return reports, err 213 } 214 215 log.Entry().Infof("Scanning and uploading to project %v with version %v and projectVersionId %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) 216 buildLabel := fmt.Sprintf("%v/repos/%v/%v/commits/%v", config.GithubAPIURL, config.Owner, config.Repository, config.CommitID) 217 218 // Create sourceanalyzer command based on configuration 219 buildID := uuid.New().String() 220 utils.SetDir(config.ModulePath) 221 os.MkdirAll(fmt.Sprintf("%v/%v", config.ModulePath, "target"), os.ModePerm) 222 223 if config.UpdateRulePack { 224 err := utils.RunExecutable("fortifyupdate", "-acceptKey", "-acceptSSLCertificate", "-url", config.ServerURL) 225 if err != nil { 226 return reports, fmt.Errorf("failed to update rule pack, serverUrl: %v", config.ServerURL) 227 } 228 err = utils.RunExecutable("fortifyupdate", "-acceptKey", "-acceptSSLCertificate", "-showInstalledRules") 229 if err != nil { 230 return reports, fmt.Errorf("failed to fetch details of installed rule pack, serverUrl: %v", config.ServerURL) 231 } 232 } 233 234 err = triggerFortifyScan(config, utils, buildID, buildLabel, fortifyProjectName) 235 reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/fortify-scan.*", config.ModulePath)}) 236 reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/*.fpr", config.ModulePath)}) 237 if err != nil { 238 return reports, errors.Wrapf(err, "failed to scan project") 239 } 240 241 var message string 242 if config.UploadResults { 243 log.Entry().Debug("Uploading results") 244 resultFilePath := fmt.Sprintf("%vtarget/result.fpr", config.ModulePath) 245 err = sys.UploadResultFile(config.FprUploadEndpoint, resultFilePath, projectVersion.ID) 246 message = fmt.Sprintf("Failed to upload result file %v to Fortify SSC at %v", resultFilePath, config.ServerURL) 247 } else { 248 log.Entry().Debug("Generating XML report") 249 xmlReportName := "fortify_result.xml" 250 err = utils.RunExecutable("ReportGenerator", "-format", "xml", "-f", xmlReportName, "-source", fmt.Sprintf("%vtarget/result.fpr", config.ModulePath)) 251 message = fmt.Sprintf("Failed to generate XML report %v", xmlReportName) 252 if err != nil { 253 reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vfortify_result.xml", config.ModulePath)}) 254 } 255 } 256 if err != nil { 257 return reports, fmt.Errorf(message+": %w", err) 258 } 259 260 log.Entry().Infof("Ensuring latest FPR is processed for project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) 261 // Ensure latest FPR is processed 262 err = verifyScanResultsFinishedUploading(config, sys, projectVersion.ID, buildLabel, filterSet, 263 10*time.Second, time.Duration(config.PollingMinutes)*time.Minute) 264 if err != nil { 265 return reports, err 266 } 267 268 // SARIF conversion done after latest FPR is processed, but before the compliance is checked 269 if config.ConvertToSarif { 270 resultFilePath := fmt.Sprintf("%vtarget/result.fpr", config.ModulePath) 271 log.Entry().Info("Calling conversion to SARIF function.") 272 sarif, err := fortify.ConvertFprToSarif(sys, project, projectVersion, resultFilePath, filterSet) 273 if err != nil { 274 return reports, fmt.Errorf("failed to generate SARIF") 275 } 276 log.Entry().Debug("Writing sarif file to disk.") 277 paths, err := fortify.WriteSarif(sarif) 278 if err != nil { 279 return reports, fmt.Errorf("failed to write sarif") 280 } 281 reports = append(reports, paths...) 282 } 283 284 log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) 285 err, paths := verifyFFProjectCompliance(config, utils, sys, project, projectVersion, filterSet, influx, auditStatus) 286 reports = append(reports, paths...) 287 return reports, err 288 } 289 290 func classifyErrorOnLookup(err error) { 291 if strings.Contains(err.Error(), "connect: connection refused") || strings.Contains(err.Error(), "net/http: TLS handshake timeout") { 292 log.SetErrorCategory(log.ErrorService) 293 } 294 } 295 296 func verifyFFProjectCompliance(config fortifyExecuteScanOptions, utils fortifyUtils, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) (error, []piperutils.Path) { 297 reports := []piperutils.Path{} 298 // Generate report 299 if config.Reporting { 300 resultURL := []byte(fmt.Sprintf("%v/html/ssc/version/%v/fix/null/", config.ServerURL, projectVersion.ID)) 301 ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, "txt"), resultURL, 0700) 302 303 data, err := generateAndDownloadQGateReport(config, sys, project, projectVersion) 304 if err != nil { 305 return err, reports 306 } 307 ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, config.ReportType), data, 0700) 308 } 309 310 // Perform audit compliance checks 311 issueFilterSelectorSet, err := sys.GetIssueFilterSelectorOfProjectVersionByName(projectVersion.ID, []string{"Analysis", "Folder", "Category"}, nil) 312 if err != nil { 313 return errors.Wrapf(err, "failed to fetch project version issue filter selector for project version ID %v", projectVersion.ID), reports 314 } 315 log.Entry().Debugf("initial filter selector set: %v", issueFilterSelectorSet) 316 317 spotChecksCountByCategory := []fortify.SpotChecksAuditCount{} 318 numberOfViolations, issueGroups, err := analyseUnauditedIssues(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus, &spotChecksCountByCategory) 319 if err != nil { 320 return errors.Wrap(err, "failed to analyze unaudited issues"), reports 321 } 322 numberOfSuspiciousExploitable, issueGroupsSuspiciousExploitable := analyseSuspiciousExploitable(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus) 323 numberOfViolations += numberOfSuspiciousExploitable 324 issueGroups = append(issueGroups, issueGroupsSuspiciousExploitable...) 325 326 log.Entry().Infof("Counted %v violations, details: %v", numberOfViolations, auditStatus) 327 328 influx.fortify_data.fields.projectID = project.ID 329 influx.fortify_data.fields.projectName = *project.Name 330 influx.fortify_data.fields.projectVersion = *projectVersion.Name 331 influx.fortify_data.fields.projectVersionID = projectVersion.ID 332 influx.fortify_data.fields.violations = numberOfViolations 333 334 fortifyReportingData := prepareReportData(influx) 335 scanReport := fortify.CreateCustomReport(fortifyReportingData, issueGroups) 336 paths, err := fortify.WriteCustomReports(scanReport) 337 if err != nil { 338 return errors.Wrap(err, "failed to write custom reports"), reports 339 } 340 reports = append(reports, paths...) 341 342 log.Entry().Debug("Checking whether GitHub issue creation/update is active") 343 log.Entry().Debugf("%v, %v, %v, %v, %v, %v", config.CreateResultIssue, numberOfViolations > 0, len(config.GithubToken) > 0, len(config.GithubAPIURL) > 0, len(config.Owner) > 0, len(config.Repository) > 0) 344 if config.CreateResultIssue && numberOfViolations > 0 && len(config.GithubToken) > 0 && len(config.GithubAPIURL) > 0 && len(config.Owner) > 0 && len(config.Repository) > 0 { 345 log.Entry().Debug("Creating/updating GitHub issue with scan results") 346 err = reporting.UploadSingleReportToGithub(scanReport, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, utils) 347 if err != nil { 348 return errors.Wrap(err, "failed to upload scan results into GitHub"), reports 349 } 350 } 351 352 jsonReport := fortify.CreateJSONReport(fortifyReportingData, spotChecksCountByCategory, config.ServerURL) 353 paths, err = fortify.WriteJSONReport(jsonReport) 354 if err != nil { 355 return errors.Wrap(err, "failed to write json report"), reports 356 } 357 reports = append(reports, paths...) 358 359 if numberOfViolations > 0 { 360 log.SetErrorCategory(log.ErrorCompliance) 361 return errors.New("fortify scan failed, the project is not compliant. For details check the archived report"), reports 362 } 363 return nil, reports 364 } 365 366 func prepareReportData(influx *fortifyExecuteScanInflux) fortify.FortifyReportData { 367 input := influx.fortify_data.fields 368 output := fortify.FortifyReportData{} 369 output.ProjectID = input.projectID 370 output.ProjectName = input.projectName 371 output.ProjectVersion = input.projectVersion 372 output.AuditAllAudited = input.auditAllAudited 373 output.AuditAllTotal = input.auditAllTotal 374 output.CorporateAudited = input.corporateAudited 375 output.CorporateTotal = input.corporateTotal 376 output.SpotChecksAudited = input.spotChecksAudited 377 output.SpotChecksGap = input.spotChecksGap 378 output.SpotChecksTotal = input.spotChecksTotal 379 output.Exploitable = input.exploitable 380 output.Suppressed = input.suppressed 381 output.Suspicious = input.suspicious 382 output.ProjectVersionID = input.projectVersionID 383 output.Violations = input.violations 384 return output 385 } 386 387 func analyseUnauditedIssues(config fortifyExecuteScanOptions, sys fortify.System, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string, spotChecksCountByCategory *[]fortify.SpotChecksAuditCount) (int, []*models.ProjectVersionIssueGroup, error) { 388 log.Entry().Info("Analyzing unaudited issues") 389 reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Folder"}, nil) 390 fetchedIssueGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersion.ID, "", filterSet.GUID, reducedFilterSelectorSet) 391 if err != nil { 392 return 0, fetchedIssueGroups, errors.Wrapf(err, "failed to fetch project version issue groups with filter set %v and selector %v for project version ID %v", filterSet, issueFilterSelectorSet, projectVersion.ID) 393 } 394 overallViolations := 0 395 for _, issueGroup := range fetchedIssueGroups { 396 issueDelta, err := getIssueDeltaFor(config, sys, issueGroup, projectVersion.ID, filterSet, issueFilterSelectorSet, influx, auditStatus, spotChecksCountByCategory) 397 if err != nil { 398 return overallViolations, fetchedIssueGroups, errors.Wrap(err, "failed to get issue delta") 399 } 400 overallViolations += issueDelta 401 } 402 return overallViolations, fetchedIssueGroups, nil 403 } 404 405 func getIssueDeltaFor(config fortifyExecuteScanOptions, sys fortify.System, issueGroup *models.ProjectVersionIssueGroup, projectVersionID int64, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string, spotChecksCountByCategory *[]fortify.SpotChecksAuditCount) (int, error) { 406 totalMinusAuditedDelta := 0 407 group := "" 408 total := 0 409 audited := 0 410 if issueGroup != nil { 411 group = *issueGroup.ID 412 total = int(*issueGroup.TotalCount) 413 audited = int(*issueGroup.AuditedCount) 414 } 415 groupTotalMinusAuditedDelta := total - audited 416 if groupTotalMinusAuditedDelta > 0 { 417 reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Folder", "Analysis"}, []string{group}) 418 folderSelector := sys.GetFilterSetByDisplayName(reducedFilterSelectorSet, "Folder") 419 if folderSelector == nil { 420 return totalMinusAuditedDelta, fmt.Errorf("folder selector not found") 421 } 422 analysisSelector := sys.GetFilterSetByDisplayName(reducedFilterSelectorSet, "Analysis") 423 424 auditStatus[group] = fmt.Sprintf("%v total : %v audited", total, audited) 425 426 if strings.Contains(config.MustAuditIssueGroups, group) { 427 totalMinusAuditedDelta += groupTotalMinusAuditedDelta 428 if group == "Corporate Security Requirements" { 429 influx.fortify_data.fields.corporateTotal = total 430 influx.fortify_data.fields.corporateAudited = audited 431 } 432 if group == "Audit All" { 433 influx.fortify_data.fields.auditAllTotal = total 434 influx.fortify_data.fields.auditAllAudited = audited 435 } 436 log.Entry().Errorf("[projectVersionId %v]: Unaudited %v detected, count %v", projectVersionID, group, totalMinusAuditedDelta) 437 logIssueURL(config, projectVersionID, folderSelector, analysisSelector) 438 } 439 440 if strings.Contains(config.SpotAuditIssueGroups, group) { 441 log.Entry().Infof("Analyzing %v", config.SpotAuditIssueGroups) 442 filter := fmt.Sprintf("%v:%v", folderSelector.EntityType, folderSelector.SelectorOptions[0].Value) 443 fetchedIssueGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersionID, filter, filterSet.GUID, sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Category"}, nil)) 444 if err != nil { 445 return totalMinusAuditedDelta, errors.Wrapf(err, "failed to fetch project version issue groups with filter %v, filter set %v and selector %v for project version ID %v", filter, filterSet, issueFilterSelectorSet, projectVersionID) 446 } 447 totalMinusAuditedDelta += getSpotIssueCount(config, sys, fetchedIssueGroups, projectVersionID, filterSet, reducedFilterSelectorSet, influx, auditStatus, spotChecksCountByCategory) 448 } 449 } 450 return totalMinusAuditedDelta, nil 451 } 452 453 func getSpotIssueCount(config fortifyExecuteScanOptions, sys fortify.System, spotCheckCategories []*models.ProjectVersionIssueGroup, projectVersionID int64, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string, spotChecksCountByCategory *[]fortify.SpotChecksAuditCount) int { 454 overallDelta := 0 455 overallIssues := 0 456 overallIssuesAudited := 0 457 for _, issueGroup := range spotCheckCategories { 458 group := "" 459 total := 0 460 audited := 0 461 if issueGroup != nil { 462 group = *issueGroup.ID 463 total = int(*issueGroup.TotalCount) 464 audited = int(*issueGroup.AuditedCount) 465 } 466 flagOutput := "" 467 468 if ((total <= config.SpotCheckMinimum || config.SpotCheckMinimum < 0) && audited != total) || (total > config.SpotCheckMinimum && audited < config.SpotCheckMinimum) { 469 currentDelta := config.SpotCheckMinimum - audited 470 if config.SpotCheckMinimum < 0 || config.SpotCheckMinimum > total { 471 currentDelta = total - audited 472 } 473 if currentDelta > 0 { 474 filterSelectorFolder := sys.GetFilterSetByDisplayName(issueFilterSelectorSet, "Folder") 475 filterSelectorAnalysis := sys.GetFilterSetByDisplayName(issueFilterSelectorSet, "Analysis") 476 overallDelta += currentDelta 477 log.Entry().Errorf("[projectVersionId %v]: %v unaudited spot check issues detected in group %v", projectVersionID, currentDelta, group) 478 logIssueURL(config, projectVersionID, filterSelectorFolder, filterSelectorAnalysis) 479 flagOutput = checkString 480 } 481 } 482 483 overallIssues += total 484 overallIssuesAudited += audited 485 486 auditStatus[group] = fmt.Sprintf("%v total : %v audited %v", total, audited, flagOutput) 487 *spotChecksCountByCategory = append(*spotChecksCountByCategory, fortify.SpotChecksAuditCount{Audited: audited, Total: total, Type: group}) 488 } 489 490 influx.fortify_data.fields.spotChecksTotal = overallIssues 491 influx.fortify_data.fields.spotChecksAudited = overallIssuesAudited 492 influx.fortify_data.fields.spotChecksGap = overallDelta 493 494 return overallDelta 495 } 496 497 func analyseSuspiciousExploitable(config fortifyExecuteScanOptions, sys fortify.System, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) (int, []*models.ProjectVersionIssueGroup) { 498 log.Entry().Info("Analyzing suspicious and exploitable issues") 499 reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Analysis"}, []string{}) 500 fetchedGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersion.ID, "", filterSet.GUID, reducedFilterSelectorSet) 501 502 suspiciousCount := 0 503 exploitableCount := 0 504 for _, issueGroup := range fetchedGroups { 505 if *issueGroup.ID == "3" { 506 suspiciousCount = int(*issueGroup.TotalCount) 507 } else if *issueGroup.ID == "4" { 508 exploitableCount = int(*issueGroup.TotalCount) 509 } 510 } 511 512 result := 0 513 if (suspiciousCount > 0 && config.ConsiderSuspicious) || exploitableCount > 0 { 514 result = result + suspiciousCount + exploitableCount 515 log.Entry().Errorf("[projectVersionId %v]: %v suspicious and %v exploitable issues detected", projectVersion.ID, suspiciousCount, exploitableCount) 516 log.Entry().Errorf("%v/html/ssc/index.jsp#!/version/%v/fix?issueGrouping=%v_%v&issueFilters=%v_%v", config.ServerURL, projectVersion.ID, reducedFilterSelectorSet.GroupBySet[0].EntityType, reducedFilterSelectorSet.GroupBySet[0].Value, reducedFilterSelectorSet.FilterBySet[0].EntityType, reducedFilterSelectorSet.FilterBySet[0].Value) 517 } 518 issueStatistics, err := sys.GetIssueStatisticsOfProjectVersion(projectVersion.ID) 519 if err != nil { 520 log.Entry().WithError(err).Errorf("Failed to fetch project version statistics for project version ID %v", projectVersion.ID) 521 } 522 auditStatus["Suspicious"] = fmt.Sprintf("%v", suspiciousCount) 523 auditStatus["Exploitable"] = fmt.Sprintf("%v", exploitableCount) 524 suppressedCount := *issueStatistics[0].SuppressedCount 525 if suppressedCount > 0 { 526 auditStatus["Suppressed"] = fmt.Sprintf("WARNING: Detected %v suppressed issues which could violate audit compliance!!!", suppressedCount) 527 } 528 influx.fortify_data.fields.suspicious = suspiciousCount 529 influx.fortify_data.fields.exploitable = exploitableCount 530 influx.fortify_data.fields.suppressed = int(suppressedCount) 531 532 return result, fetchedGroups 533 } 534 535 func logIssueURL(config fortifyExecuteScanOptions, projectVersionID int64, folderSelector, analysisSelector *models.IssueFilterSelector) { 536 url := fmt.Sprintf("%v/html/ssc/index.jsp#!/version/%v/fix", config.ServerURL, projectVersionID) 537 if len(folderSelector.SelectorOptions) > 0 { 538 url += fmt.Sprintf("?issueFilters=%v_%v:%v", 539 folderSelector.EntityType, 540 folderSelector.Value, 541 folderSelector.SelectorOptions[0].Value) 542 } else { 543 log.Entry().Debugf("no 'filter by set' array entries") 544 } 545 if analysisSelector != nil { 546 url += fmt.Sprintf("&issueFilters=%v_%v:", 547 analysisSelector.EntityType, 548 analysisSelector.Value) 549 } else { 550 log.Entry().Debugf("no second entry in 'filter by set' array") 551 } 552 log.Entry().Error(url) 553 } 554 555 func generateAndDownloadQGateReport(config fortifyExecuteScanOptions, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion) ([]byte, error) { 556 log.Entry().Infof("Generating report with template ID %v", config.ReportTemplateID) 557 report, err := sys.GenerateQGateReport(project.ID, projectVersion.ID, int64(config.ReportTemplateID), *project.Name, *projectVersion.Name, config.ReportType) 558 if err != nil { 559 return []byte{}, errors.Wrap(err, "failed to generate Q-Gate report") 560 } 561 log.Entry().Debugf("Triggered report generation of report ID %v", report.ID) 562 status := report.Status 563 for status == "PROCESSING" || status == "SCHED_PROCESSING" { 564 time.Sleep(10 * time.Second) 565 report, err = sys.GetReportDetails(report.ID) 566 if err != nil { 567 return []byte{}, fmt.Errorf("Failed to fetch Q-Gate report generation status: %w", err) 568 } 569 status = report.Status 570 } 571 data, err := sys.DownloadReportFile(config.ReportDownloadEndpoint, report.ID) 572 if err != nil { 573 return []byte{}, fmt.Errorf("Failed to download Q-Gate Report: %w", err) 574 } 575 return data, nil 576 } 577 578 var errProcessing = errors.New("artifact still processing") 579 580 func checkArtifactStatus(config fortifyExecuteScanOptions, projectVersionID int64, filterSet *models.FilterSet, artifact *models.Artifact, retries int, pollingDelay, timeout time.Duration) error { 581 if "PROCESSING" == artifact.Status || "SCHED_PROCESSING" == artifact.Status { 582 pollingTime := time.Duration(retries) * pollingDelay 583 if pollingTime >= timeout { 584 log.SetErrorCategory(log.ErrorService) 585 return fmt.Errorf("terminating after %v since artifact for Project Version %v is still in status %v", timeout, projectVersionID, artifact.Status) 586 } 587 log.Entry().Infof("Most recent artifact uploaded on %v of Project Version %v is still in status %v...", artifact.UploadDate, projectVersionID, artifact.Status) 588 time.Sleep(pollingDelay) 589 return errProcessing 590 } 591 if "REQUIRE_AUTH" == artifact.Status { 592 // verify no manual issue approval needed 593 log.SetErrorCategory(log.ErrorCompliance) 594 return fmt.Errorf("There are artifacts that require manual approval for Project Version %v, please visit Fortify SSC and approve them for processing\n%v/html/ssc/index.jsp#!/version/%v/artifacts?filterSet=%v", projectVersionID, config.ServerURL, projectVersionID, filterSet.GUID) 595 } 596 if "ERROR_PROCESSING" == artifact.Status { 597 log.SetErrorCategory(log.ErrorService) 598 return fmt.Errorf("There are artifacts that failed processing for Project Version %v\n%v/html/ssc/index.jsp#!/version/%v/artifacts?filterSet=%v", projectVersionID, config.ServerURL, projectVersionID, filterSet.GUID) 599 } 600 return nil 601 } 602 603 func verifyScanResultsFinishedUploading(config fortifyExecuteScanOptions, sys fortify.System, projectVersionID int64, buildLabel string, filterSet *models.FilterSet, pollingDelay, timeout time.Duration) error { 604 log.Entry().Debug("Verifying scan results have finished uploading and processing") 605 var artifacts []*models.Artifact 606 var relatedUpload *models.Artifact 607 var err error 608 retries := 0 609 for relatedUpload == nil { 610 artifacts, err = sys.GetArtifactsOfProjectVersion(projectVersionID) 611 log.Entry().Debugf("Received %v artifacts for project version ID %v", len(artifacts), projectVersionID) 612 if err != nil { 613 return fmt.Errorf("failed to fetch artifacts of project version ID %v", projectVersionID) 614 } 615 if len(artifacts) == 0 { 616 return fmt.Errorf("no uploaded artifacts for assessment detected for project version with ID %v", projectVersionID) 617 } 618 latest := artifacts[0] 619 err = checkArtifactStatus(config, projectVersionID, filterSet, latest, retries, pollingDelay, timeout) 620 if err != nil { 621 if err == errProcessing { 622 retries++ 623 continue 624 } 625 return err 626 } 627 relatedUpload = findArtifactByBuildLabel(artifacts, buildLabel) 628 if relatedUpload == nil { 629 log.Entry().Warn("Unable to identify artifact based on the build label, will consider most recent artifact as related to the scan") 630 relatedUpload = artifacts[0] 631 } 632 } 633 634 differenceInSeconds := calculateTimeDifferenceToLastUpload(relatedUpload.UploadDate, projectVersionID) 635 // Use the absolute value for checking the time difference 636 if differenceInSeconds > float64(60*config.DeltaMinutes) { 637 return errors.New("no recent upload detected on Project Version") 638 } 639 for _, upload := range artifacts { 640 if upload.Status == "ERROR_PROCESSING" { 641 log.Entry().Warn("Previous uploads detected that failed processing, please ensure that your scans are properly configured") 642 break 643 } 644 } 645 return nil 646 } 647 648 func findArtifactByBuildLabel(artifacts []*models.Artifact, buildLabel string) *models.Artifact { 649 if len(buildLabel) == 0 { 650 return nil 651 } 652 for _, artifact := range artifacts { 653 if len(buildLabel) > 0 && artifact.Embed != nil && artifact.Embed.Scans != nil && len(artifact.Embed.Scans) > 0 { 654 scan := artifact.Embed.Scans[0] 655 if scan != nil && strings.HasSuffix(scan.BuildLabel, buildLabel) { 656 return artifact 657 } 658 } 659 } 660 return nil 661 } 662 663 func calculateTimeDifferenceToLastUpload(uploadDate models.Iso8601MilliDateTime, projectVersionID int64) float64 { 664 log.Entry().Infof("Last upload on project version %v happened on %v", projectVersionID, uploadDate) 665 uploadDateAsTime := time.Time(uploadDate) 666 duration := time.Since(uploadDateAsTime) 667 log.Entry().Debugf("Difference duration is %v", duration) 668 absoluteSeconds := math.Abs(duration.Seconds()) 669 log.Entry().Infof("Difference since %v in seconds is %v", uploadDateAsTime, absoluteSeconds) 670 return absoluteSeconds 671 } 672 673 func executeTemplatedCommand(utils fortifyUtils, cmdTemplate []string, context map[string]string) error { 674 for index, cmdTemplatePart := range cmdTemplate { 675 result, err := piperutils.ExecuteTemplate(cmdTemplatePart, context) 676 if err != nil { 677 return errors.Wrapf(err, "failed to transform template for command fragment: %v", cmdTemplatePart) 678 } 679 cmdTemplate[index] = result 680 } 681 err := utils.RunExecutable(cmdTemplate[0], cmdTemplate[1:]...) 682 if err != nil { 683 return errors.Wrapf(err, "failed to execute command %v", cmdTemplate) 684 } 685 return nil 686 } 687 688 func autoresolvePipClasspath(executable string, parameters []string, file string, utils fortifyUtils) (string, error) { 689 // redirect stdout and create cp file from command output 690 outfile, err := os.Create(file) 691 if err != nil { 692 return "", errors.Wrapf(err, "failed to create classpath file") 693 } 694 defer outfile.Close() 695 utils.Stdout(outfile) 696 err = utils.RunExecutable(executable, parameters...) 697 if err != nil { 698 return "", errors.Wrapf(err, "failed to run classpath autodetection command %v with parameters %v", executable, parameters) 699 } 700 utils.Stdout(log.Entry().Writer()) 701 return readClasspathFile(file), nil 702 } 703 704 func autoresolveMavenClasspath(config fortifyExecuteScanOptions, file string, utils fortifyUtils) (string, error) { 705 if filepath.IsAbs(file) { 706 log.Entry().Warnf("Passing an absolute path for -Dmdep.outputFile results in the classpath only for the last module in multi-module maven projects.") 707 } 708 defines := generateMavenFortifyDefines(&config, file) 709 executeOptions := maven.ExecuteOptions{ 710 PomPath: config.BuildDescriptorFile, 711 ProjectSettingsFile: config.ProjectSettingsFile, 712 GlobalSettingsFile: config.GlobalSettingsFile, 713 M2Path: config.M2Path, 714 Goals: []string{"dependency:build-classpath", "package"}, 715 Defines: defines, 716 ReturnStdout: false, 717 } 718 _, err := maven.Execute(&executeOptions, utils) 719 if err != nil { 720 log.Entry().WithError(err).Warnf("failed to determine classpath using Maven: %v", err) 721 } 722 return readAllClasspathFiles(file), nil 723 } 724 725 func autoresolveGradleClasspath(config fortifyExecuteScanOptions, file string, utils fortifyUtils) (string, error) { 726 gradleOptions := &gradle.ExecuteOptions{ 727 Task: "getClasspath", 728 UseWrapper: true, 729 InitScriptContent: getClasspathScriptContent, 730 ProjectProperties: map[string]string{"filename": file}, 731 } 732 if _, err := gradle.Execute(gradleOptions, utils); err != nil { 733 log.Entry().WithError(err).Warnf("failed to determine classpath using Gradle: %v", err) 734 } 735 return readAllClasspathFiles(file), nil 736 } 737 738 func generateMavenFortifyDefines(config *fortifyExecuteScanOptions, file string) []string { 739 defines := []string{ 740 fmt.Sprintf("-Dmdep.outputFile=%v", file), 741 // Parameter to indicate to maven build that the fortify step is the trigger, can be used for optimizations 742 "-Dfortify", 743 "-DincludeScope=compile", 744 "-DskipTests", 745 "-Dmaven.javadoc.skip=true", 746 "--fail-at-end"} 747 748 if len(config.BuildDescriptorExcludeList) > 0 { 749 // From the documentation, these are file paths to a module's pom.xml. 750 // For MTA projects, we support pom.xml files here and skip others. 751 for _, exclude := range config.BuildDescriptorExcludeList { 752 if !strings.HasSuffix(exclude, "pom.xml") { 753 continue 754 } 755 exists, _ := piperutils.FileExists(exclude) 756 if !exists { 757 continue 758 } 759 moduleName := filepath.Dir(exclude) 760 if moduleName != "" { 761 defines = append(defines, "-pl", "!"+moduleName) 762 } 763 } 764 } 765 766 return defines 767 } 768 769 // readAllClasspathFiles tests whether the passed file is an absolute path. If not, it will glob for 770 // all files under the current directory with the given file name and concatenate their contents. 771 // Otherwise it will return the contents pointed to by the absolute path. 772 func readAllClasspathFiles(file string) string { 773 var paths []string 774 if filepath.IsAbs(file) { 775 paths = []string{file} 776 } else { 777 paths, _ = doublestar.Glob(filepath.Join("**", file)) 778 log.Entry().Debugf("Concatenating the class paths from %v", paths) 779 } 780 var contents string 781 const separator = ":" 782 for _, path := range paths { 783 contents += separator + readClasspathFile(path) 784 } 785 return removeDuplicates(contents, separator) 786 } 787 788 func readClasspathFile(file string) string { 789 data, err := ioutil.ReadFile(file) 790 if err != nil { 791 log.Entry().WithError(err).Warnf("failed to read classpath from file '%v'", file) 792 } 793 result := strings.TrimSpace(string(data)) 794 if len(result) == 0 { 795 log.Entry().Warnf("classpath from file '%v' was empty", file) 796 } 797 return result 798 } 799 800 func removeDuplicates(contents, separator string) string { 801 if separator == "" || contents == "" { 802 return contents 803 } 804 entries := strings.Split(contents, separator) 805 entrySet := map[string]struct{}{} 806 contents = "" 807 for _, entry := range entries { 808 if entry == "" { 809 continue 810 } 811 _, contained := entrySet[entry] 812 if !contained { 813 entrySet[entry] = struct{}{} 814 contents += entry + separator 815 } 816 } 817 if contents != "" { 818 // Remove trailing "separator" 819 contents = contents[:len(contents)-len(separator)] 820 } 821 return contents 822 } 823 824 func triggerFortifyScan(config fortifyExecuteScanOptions, utils fortifyUtils, buildID, buildLabel, buildProject string) error { 825 var err error = nil 826 // Do special Python related prep 827 pipVersion := "pip3" 828 if config.PythonVersion != "python3" { 829 pipVersion = "pip2" 830 } 831 832 classpath := "" 833 if config.BuildTool == "maven" { 834 if config.AutodetectClasspath { 835 classpath, err = autoresolveMavenClasspath(config, classpathFileName, utils) 836 if err != nil { 837 return err 838 } 839 } 840 config.Translate, err = populateMavenGradleTranslate(&config, classpath) 841 if err != nil { 842 log.Entry().WithError(err).Warnf("failed to apply src ('%s') or exclude ('%s') parameter", config.Src, config.Exclude) 843 } 844 } else if config.BuildTool == "gradle" { 845 if config.AutodetectClasspath { 846 classpath, err = autoresolveGradleClasspath(config, classpathFileName, utils) 847 if err != nil { 848 return err 849 } 850 } 851 config.Translate, err = populateMavenGradleTranslate(&config, classpath) 852 if err != nil { 853 log.Entry().WithError(err).Warnf("failed to apply src ('%s') or exclude ('%s') parameter", config.Src, config.Exclude) 854 } 855 } else if config.BuildTool == "pip" { 856 if config.AutodetectClasspath { 857 separator := getSeparator() 858 script := fmt.Sprintf("import sys;p=sys.path;p.remove('');print('%v'.join(p))", separator) 859 classpath, err = autoresolvePipClasspath(config.PythonVersion, []string{"-c", script}, classpathFileName, utils) 860 if err != nil { 861 return errors.Wrap(err, "failed to autoresolve pip classpath") 862 } 863 } 864 // install the dev dependencies 865 if len(config.PythonRequirementsFile) > 0 { 866 context := map[string]string{} 867 cmdTemplate := []string{pipVersion, "install", "--user", "-r", config.PythonRequirementsFile} 868 cmdTemplate = append(cmdTemplate, tokenize(config.PythonRequirementsInstallSuffix)...) 869 executeTemplatedCommand(utils, cmdTemplate, context) 870 } 871 872 executeTemplatedCommand(utils, tokenize(config.PythonInstallCommand), map[string]string{"Pip": pipVersion}) 873 874 config.Translate, err = populatePipTranslate(&config, classpath) 875 if err != nil { 876 log.Entry().WithError(err).Warnf("failed to apply pythonAdditionalPath ('%s') or src ('%s') parameter", config.PythonAdditionalPath, config.Src) 877 } 878 879 } else { 880 return fmt.Errorf("buildTool '%s' is not supported by this step", config.BuildTool) 881 } 882 883 err = translateProject(&config, utils, buildID, classpath) 884 if err != nil { 885 return err 886 } 887 888 return scanProject(&config, utils, buildID, buildLabel, buildProject) 889 } 890 891 func populatePipTranslate(config *fortifyExecuteScanOptions, classpath string) (string, error) { 892 if len(config.Translate) > 0 { 893 return config.Translate, nil 894 } 895 896 var translateList []map[string]interface{} 897 translateList = append(translateList, make(map[string]interface{})) 898 899 separator := getSeparator() 900 901 translateList[0]["pythonPath"] = classpath + separator + 902 getSuppliedOrDefaultListAsString(config.PythonAdditionalPath, []string{}, separator) 903 translateList[0]["src"] = getSuppliedOrDefaultListAsString( 904 config.Src, []string{"./**/*"}, ":") 905 translateList[0]["exclude"] = getSuppliedOrDefaultListAsString( 906 config.Exclude, []string{"./**/tests/**/*", "./**/setup.py"}, separator) 907 908 translateJSON, err := json.Marshal(translateList) 909 910 return string(translateJSON), err 911 } 912 913 func populateMavenGradleTranslate(config *fortifyExecuteScanOptions, classpath string) (string, error) { 914 if len(config.Translate) > 0 { 915 return config.Translate, nil 916 } 917 918 var translateList []map[string]interface{} 919 translateList = append(translateList, make(map[string]interface{})) 920 translateList[0]["classpath"] = classpath 921 922 setTranslateEntryIfNotEmpty(translateList[0], "src", ":", config.Src, 923 []string{"**/*.xml", "**/*.html", "**/*.jsp", "**/*.js", "**/src/main/resources/**/*", "**/src/main/java/**/*", "**/target/main/java/**/*", "**/target/main/resources/**/*", "**/target/generated-sources/**/*"}) 924 925 setTranslateEntryIfNotEmpty(translateList[0], "exclude", getSeparator(), config.Exclude, []string{"**/src/test/**/*"}) 926 927 translateJSON, err := json.Marshal(translateList) 928 929 return string(translateJSON), err 930 } 931 932 func translateProject(config *fortifyExecuteScanOptions, utils fortifyUtils, buildID, classpath string) error { 933 var translateList []map[string]string 934 json.Unmarshal([]byte(config.Translate), &translateList) 935 log.Entry().Debugf("Translating with options: %v", translateList) 936 for _, translate := range translateList { 937 if len(classpath) > 0 { 938 translate["autoClasspath"] = classpath 939 } 940 err := handleSingleTranslate(config, utils, buildID, translate) 941 if err != nil { 942 return err 943 } 944 } 945 return nil 946 } 947 948 func handleSingleTranslate(config *fortifyExecuteScanOptions, command fortifyUtils, buildID string, t map[string]string) error { 949 if t != nil { 950 log.Entry().Debugf("Handling translate config %v", t) 951 translateOptions := []string{ 952 "-verbose", 953 "-64", 954 "-b", 955 buildID, 956 } 957 translateOptions = append(translateOptions, tokenize(config.Memory)...) 958 translateOptions = appendToOptions(config, translateOptions, t) 959 log.Entry().Debugf("Running sourceanalyzer translate command with options %v", translateOptions) 960 err := command.RunExecutable("sourceanalyzer", translateOptions...) 961 if err != nil { 962 return errors.Wrapf(err, "failed to execute sourceanalyzer translate command with options %v", translateOptions) 963 } 964 } else { 965 log.Entry().Debug("Skipping translate with nil value") 966 } 967 return nil 968 } 969 970 func scanProject(config *fortifyExecuteScanOptions, command fortifyUtils, buildID, buildLabel, buildProject string) error { 971 var scanOptions = []string{ 972 "-verbose", 973 "-64", 974 "-b", 975 buildID, 976 "-scan", 977 } 978 scanOptions = append(scanOptions, tokenize(config.Memory)...) 979 if config.QuickScan { 980 scanOptions = append(scanOptions, "-quick") 981 } 982 if len(config.AdditionalScanParameters) > 0 { 983 for _, scanParameter := range config.AdditionalScanParameters { 984 scanOptions = append(scanOptions, scanParameter) 985 } 986 } 987 if len(buildLabel) > 0 { 988 scanOptions = append(scanOptions, "-build-label", buildLabel) 989 } 990 if len(buildProject) > 0 { 991 scanOptions = append(scanOptions, "-build-project", buildProject) 992 } 993 scanOptions = append(scanOptions, "-logfile", "target/fortify-scan.log", "-f", "target/result.fpr") 994 995 err := command.RunExecutable("sourceanalyzer", scanOptions...) 996 if err != nil { 997 return errors.Wrapf(err, "failed to execute sourceanalyzer scan command with scanOptions %v", scanOptions) 998 } 999 return nil 1000 } 1001 1002 func determinePullRequestMerge(config fortifyExecuteScanOptions) (string, string) { 1003 author := "" 1004 //TODO provide parameter for trusted certs 1005 ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", []string{}) 1006 if err == nil && ctx != nil && client != nil { 1007 prID, author, err := determinePullRequestMergeGithub(ctx, config, client.PullRequests) 1008 if err != nil { 1009 log.Entry().WithError(err).Warn("Failed to get PR metadata via GitHub client") 1010 } else { 1011 return prID, author 1012 } 1013 } else { 1014 log.Entry().WithError(err).Warn("Failed to instantiate GitHub client to get PR metadata") 1015 } 1016 1017 log.Entry().Infof("Trying to determine PR ID in commit message: %v", config.CommitMessage) 1018 r, _ := regexp.Compile(config.PullRequestMessageRegex) 1019 matches := r.FindSubmatch([]byte(config.CommitMessage)) 1020 if matches != nil && len(matches) > 1 { 1021 return string(matches[config.PullRequestMessageRegexGroup]), author 1022 } 1023 return "0", "" 1024 } 1025 1026 func determinePullRequestMergeGithub(ctx context.Context, config fortifyExecuteScanOptions, pullRequestServiceInstance pullRequestService) (string, string, error) { 1027 number := "0" 1028 author := "" 1029 options := github.PullRequestListOptions{State: "closed", Sort: "updated", Direction: "desc"} 1030 prList, _, err := pullRequestServiceInstance.ListPullRequestsWithCommit(ctx, config.Owner, config.Repository, config.CommitID, &options) 1031 if err == nil && prList != nil && len(prList) > 0 { 1032 number = fmt.Sprintf("%v", prList[0].GetNumber()) 1033 if prList[0].GetUser() != nil { 1034 author = prList[0].GetUser().GetLogin() 1035 } 1036 return number, author, nil 1037 } else { 1038 log.Entry().Infof("Unable to resolve PR via commit ID: %v", config.CommitID) 1039 } 1040 return number, author, err 1041 } 1042 1043 func appendToOptions(config *fortifyExecuteScanOptions, options []string, t map[string]string) []string { 1044 switch config.BuildTool { 1045 case "windows": 1046 if len(t["aspnetcore"]) > 0 { 1047 options = append(options, "-aspnetcore") 1048 } 1049 if len(t["dotNetCoreVersion"]) > 0 { 1050 options = append(options, "-dotnet-core-version", t["dotNetCoreVersion"]) 1051 } 1052 if len(t["libDirs"]) > 0 { 1053 options = append(options, "-libdirs", t["libDirs"]) 1054 } 1055 1056 case "maven", "gradle": 1057 if len(t["autoClasspath"]) > 0 { 1058 options = append(options, "-cp", t["autoClasspath"]) 1059 } else if len(t["classpath"]) > 0 { 1060 options = append(options, "-cp", t["classpath"]) 1061 } else { 1062 log.Entry().Debugf("no field 'autoClasspath' or 'classpath' in map or both empty") 1063 } 1064 if len(t["extdirs"]) > 0 { 1065 options = append(options, "-extdirs", t["extdirs"]) 1066 } 1067 if len(t["javaBuildDir"]) > 0 { 1068 options = append(options, "-java-build-dir", t["javaBuildDir"]) 1069 } 1070 if len(t["source"]) > 0 { 1071 options = append(options, "-source", t["source"]) 1072 } 1073 if len(t["jdk"]) > 0 { 1074 options = append(options, "-jdk", t["jdk"]) 1075 } 1076 if len(t["sourcepath"]) > 0 { 1077 options = append(options, "-sourcepath", t["sourcepath"]) 1078 } 1079 1080 case "pip": 1081 if len(t["autoClasspath"]) > 0 { 1082 options = append(options, "-python-path", t["autoClasspath"]) 1083 } else if len(t["pythonPath"]) > 0 { 1084 options = append(options, "-python-path", t["pythonPath"]) 1085 } 1086 if len(t["djangoTemplatDirs"]) > 0 { 1087 options = append(options, "-django-template-dirs", t["djangoTemplatDirs"]) 1088 } 1089 1090 default: 1091 return options 1092 } 1093 1094 if len(t["exclude"]) > 0 { 1095 options = append(options, "-exclude", t["exclude"]) 1096 } 1097 return append(options, strings.Split(t["src"], ":")...) 1098 } 1099 1100 func getSuppliedOrDefaultList(suppliedList, defaultList []string) []string { 1101 if len(suppliedList) > 0 { 1102 return suppliedList 1103 } 1104 return defaultList 1105 } 1106 1107 func getSuppliedOrDefaultListAsString(suppliedList, defaultList []string, separator string) string { 1108 effectiveList := getSuppliedOrDefaultList(suppliedList, defaultList) 1109 return strings.Join(effectiveList, separator) 1110 } 1111 1112 // setTranslateEntryIfNotEmpty builds a string from either the user-supplied list, or the default list, 1113 // by joining the entries with the given separator. If the resulting string is not empty, it will be 1114 // placed as an entry in the provided map under the given key. 1115 func setTranslateEntryIfNotEmpty(translate map[string]interface{}, key, separator string, suppliedList, defaultList []string) { 1116 value := getSuppliedOrDefaultListAsString(suppliedList, defaultList, separator) 1117 if value != "" { 1118 translate[key] = value 1119 } 1120 } 1121 1122 // getSeparator returns the separator string depending on the host platform. This assumes that 1123 // Piper executes the Fortify command line tools within the same OS platform as it is running on itself. 1124 func getSeparator() string { 1125 if runtime.GOOS == "windows" { 1126 return ";" 1127 } 1128 return ":" 1129 } 1130 1131 func createToolRecordFortify(workspace string, config fortifyExecuteScanOptions, projectID int64, projectName string, projectVersionID int64, projectVersion string) (string, error) { 1132 record := toolrecord.New(workspace, "fortify", config.ServerURL) 1133 // Project 1134 err := record.AddKeyData("project", 1135 strconv.FormatInt(projectID, 10), 1136 projectName, 1137 "") 1138 if err != nil { 1139 return "", err 1140 } 1141 // projectVersion 1142 projectVersionURL := config.ServerURL + "/html/ssc/version/" + strconv.FormatInt(projectVersionID, 10) 1143 err = record.AddKeyData("projectVersion", 1144 strconv.FormatInt(projectVersionID, 10), 1145 projectVersion, 1146 projectVersionURL) 1147 if err != nil { 1148 return "", err 1149 } 1150 err = record.Persist() 1151 if err != nil { 1152 return "", err 1153 } 1154 return record.GetFileName(), nil 1155 }