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