mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-02-21 19:48:53 +02:00
Support verify only mode for SAST tools (#2018)
* Support verify only mode for SAST * Include feedback * Add tests * Fix imports
This commit is contained in:
parent
a47d0443f2
commit
612d3a645b
@ -108,27 +108,36 @@ func zipWorkspaceFiles(workspace, filterPattern string) *os.File {
|
||||
}
|
||||
|
||||
func uploadAndScan(config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, workspace string, influx *checkmarxExecuteScanInflux) {
|
||||
zipFile := zipWorkspaceFiles(workspace, config.FilterPattern)
|
||||
sourceCodeUploaded := sys.UploadProjectSourceCode(project.ID, zipFile.Name())
|
||||
if sourceCodeUploaded {
|
||||
log.Entry().Debugf("Source code uploaded for project %v", project.Name)
|
||||
err := os.Remove(zipFile.Name())
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Warnf("Failed to delete zipped source code for project %v", project.Name)
|
||||
}
|
||||
|
||||
incremental := config.Incremental
|
||||
fullScanCycle, err := strconv.Atoi(config.FullScanCycle)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatalf("Invalid configuration value for fullScanCycle %v, must be a positive int", config.FullScanCycle)
|
||||
}
|
||||
if incremental && config.FullScansScheduled && fullScanCycle > 0 && (getNumCoherentIncrementalScans(sys, project.ID)+1)%fullScanCycle == 0 {
|
||||
incremental = false
|
||||
}
|
||||
|
||||
triggerScan(config, sys, project, workspace, incremental, influx)
|
||||
ok, previousScans := sys.GetScans(project.ID)
|
||||
if !ok && config.VerifyOnly {
|
||||
log.Entry().Warnf("Cannot load scans for project %v, verification only mode aborted", project.Name)
|
||||
}
|
||||
if len(previousScans) > 0 && config.VerifyOnly {
|
||||
verifyCxProjectCompliance(config, sys, previousScans[0].ID, workspace, influx)
|
||||
} else {
|
||||
log.Entry().Fatalf("Cannot upload source code for project %v", project.Name)
|
||||
zipFile := zipWorkspaceFiles(workspace, config.FilterPattern)
|
||||
sourceCodeUploaded := sys.UploadProjectSourceCode(project.ID, zipFile.Name())
|
||||
if sourceCodeUploaded {
|
||||
log.Entry().Debugf("Source code uploaded for project %v", project.Name)
|
||||
err := os.Remove(zipFile.Name())
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Warnf("Failed to delete zipped source code for project %v", project.Name)
|
||||
}
|
||||
|
||||
incremental := config.Incremental
|
||||
fullScanCycle, err := strconv.Atoi(config.FullScanCycle)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatalf("Invalid configuration value for fullScanCycle %v, must be a positive int", config.FullScanCycle)
|
||||
}
|
||||
|
||||
if incremental && config.FullScansScheduled && fullScanCycle > 0 && (getNumCoherentIncrementalScans(sys, previousScans)+1)%fullScanCycle == 0 {
|
||||
incremental = false
|
||||
}
|
||||
|
||||
triggerScan(config, sys, project, workspace, incremental, influx)
|
||||
} else {
|
||||
log.Entry().Fatalf("Cannot upload source code for project %v", project.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,43 +149,47 @@ func triggerScan(config checkmarxExecuteScanOptions, sys checkmarx.System, proje
|
||||
|
||||
log.Entry().Debugln("Scan finished")
|
||||
|
||||
var reports []piperutils.Path
|
||||
if config.GeneratePdfReport {
|
||||
pdfReportName := createReportName(workspace, "CxSASTReport_%v.pdf")
|
||||
ok := downloadAndSaveReport(sys, pdfReportName, scan)
|
||||
if ok {
|
||||
reports = append(reports, piperutils.Path{Target: pdfReportName, Mandatory: true})
|
||||
}
|
||||
} else {
|
||||
log.Entry().Debug("Report generation is disabled via configuration")
|
||||
}
|
||||
|
||||
xmlReportName := createReportName(workspace, "CxSASTResults_%v.xml")
|
||||
results := getDetailedResults(sys, xmlReportName, scan.ID)
|
||||
reports = append(reports, piperutils.Path{Target: xmlReportName})
|
||||
links := []piperutils.Path{piperutils.Path{Target: results["DeepLink"].(string), Name: "Checkmarx Web UI"}}
|
||||
piperutils.PersistReportsAndLinks("checkmarxExecuteScan", workspace, reports, links)
|
||||
|
||||
reportToInflux(results, influx)
|
||||
|
||||
insecure := false
|
||||
if config.VulnerabilityThresholdEnabled {
|
||||
insecure = enforceThresholds(config, results)
|
||||
}
|
||||
|
||||
if insecure {
|
||||
if config.VulnerabilityThresholdResult == "FAILURE" {
|
||||
log.Entry().Fatalln("Checkmarx scan failed, the project is not compliant. For details see the archived report.")
|
||||
}
|
||||
log.Entry().Errorf("Checkmarx scan result set to %v, some results are not meeting defined thresholds. For details see the archived report.", config.VulnerabilityThresholdResult)
|
||||
} else {
|
||||
log.Entry().Infoln("Checkmarx scan finished")
|
||||
}
|
||||
verifyCxProjectCompliance(config, sys, scan.ID, workspace, influx)
|
||||
} else {
|
||||
log.Entry().Fatalf("Cannot scan project %v", project.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx.System, scanID int, workspace string, influx *checkmarxExecuteScanInflux) {
|
||||
var reports []piperutils.Path
|
||||
if config.GeneratePdfReport {
|
||||
pdfReportName := createReportName(workspace, "CxSASTReport_%v.pdf")
|
||||
ok := downloadAndSaveReport(sys, pdfReportName, scanID)
|
||||
if ok {
|
||||
reports = append(reports, piperutils.Path{Target: pdfReportName, Mandatory: true})
|
||||
}
|
||||
} else {
|
||||
log.Entry().Debug("Report generation is disabled via configuration")
|
||||
}
|
||||
|
||||
xmlReportName := createReportName(workspace, "CxSASTResults_%v.xml")
|
||||
results := getDetailedResults(sys, xmlReportName, scanID)
|
||||
reports = append(reports, piperutils.Path{Target: xmlReportName})
|
||||
links := []piperutils.Path{piperutils.Path{Target: results["DeepLink"].(string), Name: "Checkmarx Web UI"}}
|
||||
piperutils.PersistReportsAndLinks("checkmarxExecuteScan", workspace, reports, links)
|
||||
|
||||
reportToInflux(results, influx)
|
||||
|
||||
insecure := false
|
||||
if config.VulnerabilityThresholdEnabled {
|
||||
insecure = enforceThresholds(config, results)
|
||||
}
|
||||
|
||||
if insecure {
|
||||
if config.VulnerabilityThresholdResult == "FAILURE" {
|
||||
log.Entry().Fatalln("Checkmarx scan failed, the project is not compliant. For details see the archived report.")
|
||||
}
|
||||
log.Entry().Errorf("Checkmarx scan result set to %v, some results are not meeting defined thresholds. For details see the archived report.", config.VulnerabilityThresholdResult)
|
||||
} else {
|
||||
log.Entry().Infoln("Checkmarx scan finished")
|
||||
}
|
||||
}
|
||||
|
||||
func createReportName(workspace, reportFileNameTemplate string) string {
|
||||
regExpFileName := regexp.MustCompile(`[^\w\d]`)
|
||||
timeStamp, _ := time.Now().Local().MarshalText()
|
||||
@ -265,8 +278,8 @@ func reportToInflux(results map[string]interface{}, influx *checkmarxExecuteScan
|
||||
influx.checkmarx_data.fields.report_creation_time = results["ReportCreationTime"].(string)
|
||||
}
|
||||
|
||||
func downloadAndSaveReport(sys checkmarx.System, reportFileName string, scan checkmarx.Scan) bool {
|
||||
ok, report := generateAndDownloadReport(sys, scan.ID, "PDF")
|
||||
func downloadAndSaveReport(sys checkmarx.System, reportFileName string, scanID int) bool {
|
||||
ok, report := generateAndDownloadReport(sys, scanID, "PDF")
|
||||
if ok {
|
||||
log.Entry().Debugf("Saving report to file %v...", reportFileName)
|
||||
ioutil.WriteFile(reportFileName, report, 0700)
|
||||
@ -431,16 +444,13 @@ func generateAndDownloadReport(sys checkmarx.System, scanID int, reportType stri
|
||||
return false, []byte{}
|
||||
}
|
||||
|
||||
func getNumCoherentIncrementalScans(sys checkmarx.System, projectID int) int {
|
||||
ok, scans := sys.GetScans(projectID)
|
||||
func getNumCoherentIncrementalScans(sys checkmarx.System, scans []checkmarx.ScanStatus) int {
|
||||
count := 0
|
||||
if ok {
|
||||
for _, scan := range scans {
|
||||
if !scan.IsIncremental {
|
||||
break
|
||||
}
|
||||
count++
|
||||
for _, scan := range scans {
|
||||
if !scan.IsIncremental {
|
||||
break
|
||||
}
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ type checkmarxExecuteScanOptions struct {
|
||||
TeamID string `json:"teamId,omitempty"`
|
||||
TeamName string `json:"teamName,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
VerifyOnly bool `json:"verifyOnly,omitempty"`
|
||||
VulnerabilityThresholdEnabled bool `json:"vulnerabilityThresholdEnabled,omitempty"`
|
||||
VulnerabilityThresholdHigh int `json:"vulnerabilityThresholdHigh,omitempty"`
|
||||
VulnerabilityThresholdLow int `json:"vulnerabilityThresholdLow,omitempty"`
|
||||
@ -241,6 +242,7 @@ func addCheckmarxExecuteScanFlags(cmd *cobra.Command, stepConfig *checkmarxExecu
|
||||
cmd.Flags().StringVar(&stepConfig.TeamID, "teamId", os.Getenv("PIPER_teamId"), "The group ID related to your team which can be obtained via the Pipeline Syntax plugin as described in the `Details` section")
|
||||
cmd.Flags().StringVar(&stepConfig.TeamName, "teamName", os.Getenv("PIPER_teamName"), "The full name of the team to assign newly created projects to which is preferred to teamId")
|
||||
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "The username to authenticate")
|
||||
cmd.Flags().BoolVar(&stepConfig.VerifyOnly, "verifyOnly", false, "Whether the step shall only apply verification checks or whether it does a full scan and check cycle")
|
||||
cmd.Flags().BoolVar(&stepConfig.VulnerabilityThresholdEnabled, "vulnerabilityThresholdEnabled", true, "Whether the thresholds are enabled or not. If enabled the build will be set to `vulnerabilityThresholdResult` in case a specific threshold value is exceeded")
|
||||
cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdHigh, "vulnerabilityThresholdHigh", 100, "The specific threshold for high severity findings")
|
||||
cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdLow, "vulnerabilityThresholdLow", 10, "The specific threshold for low severity findings")
|
||||
@ -396,6 +398,14 @@ func checkmarxExecuteScanMetadata() config.StepData {
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "verifyOnly",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "bool",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "vulnerabilityThresholdEnabled",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
|
@ -127,10 +127,11 @@ func (sys *systemMock) GetTeams() []checkmarx.Team {
|
||||
}
|
||||
|
||||
type systemMockForExistingProject struct {
|
||||
response interface{}
|
||||
isIncremental bool
|
||||
isPublic bool
|
||||
forceScan bool
|
||||
response interface{}
|
||||
isIncremental bool
|
||||
isPublic bool
|
||||
forceScan bool
|
||||
scanProjectCalled bool
|
||||
}
|
||||
|
||||
func (sys *systemMockForExistingProject) FilterPresetByName(presets []checkmarx.Preset, presetName string) checkmarx.Preset {
|
||||
@ -173,6 +174,7 @@ func (sys *systemMockForExistingProject) GetScanStatusAndDetail(scanID int) (str
|
||||
return "Finished", checkmarx.ScanStatusDetail{Stage: "", Step: ""}
|
||||
}
|
||||
func (sys *systemMockForExistingProject) ScanProject(projectID int, isIncrementalV, isPublicV, forceScanV bool) (bool, checkmarx.Scan) {
|
||||
sys.scanProjectCalled = true
|
||||
sys.isIncremental = isIncrementalV
|
||||
sys.isPublic = isPublicV
|
||||
sys.forceScan = forceScanV
|
||||
@ -305,6 +307,23 @@ func TestRunScan(t *testing.T) {
|
||||
assert.Equal(t, false, sys.isIncremental, "isIncremental has wrong value")
|
||||
assert.Equal(t, true, sys.isPublic, "isPublic has wrong value")
|
||||
assert.Equal(t, true, sys.forceScan, "forceScan has wrong value")
|
||||
assert.Equal(t, true, sys.scanProjectCalled, "ScanProject was not invoked")
|
||||
}
|
||||
|
||||
func TestVerifyOnly(t *testing.T) {
|
||||
sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)}
|
||||
options := checkmarxExecuteScanOptions{VerifyOnly: true, ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
|
||||
workspace, err := ioutil.TempDir("", "workspace1")
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create temporary workspace directory")
|
||||
}
|
||||
// clean up tmp dir
|
||||
defer os.RemoveAll(workspace)
|
||||
|
||||
influx := checkmarxExecuteScanInflux{}
|
||||
|
||||
runScan(options, sys, workspace, &influx)
|
||||
assert.Equal(t, false, sys.scanProjectCalled, "ScanProject was invoked but shouldn't")
|
||||
}
|
||||
|
||||
func TestRunScanWOtherCycle(t *testing.T) {
|
||||
|
@ -50,47 +50,60 @@ const classpathFileName = "fortify-execute-scan-cp.txt"
|
||||
func fortifyExecuteScan(config fortifyExecuteScanOptions, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux) {
|
||||
auditStatus := map[string]string{}
|
||||
sys := fortify.NewSystemInstance(config.ServerURL, config.APIEndpoint, config.AuthToken, time.Second*30)
|
||||
c := command.Command{}
|
||||
c := &command.Command{}
|
||||
// reroute command output to logging framework
|
||||
c.Stdout(log.Entry().Writer())
|
||||
c.Stderr(log.Entry().Writer())
|
||||
err := runFortifyScan(config, sys, &c, telemetryData, influx, auditStatus)
|
||||
|
||||
artifact, err := determineArtifact(config, c)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatalf("Fortify scan and check failed")
|
||||
log.Entry().WithError(err).Fatal()
|
||||
}
|
||||
|
||||
reports, err := runFortifyScan(config, sys, c, artifact, telemetryData, influx, auditStatus)
|
||||
piperutils.PersistReportsAndLinks("fortifyExecuteScan", config.ModulePath, reports, nil)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatal("Fortify scan and check failed")
|
||||
}
|
||||
}
|
||||
|
||||
func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, command fortifyExecRunner, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux, auditStatus map[string]string) error {
|
||||
log.Entry().Debugf("Running Fortify scan against SSC at %v", config.ServerURL)
|
||||
func determineArtifact(config fortifyExecuteScanOptions, c *command.Command) (versioning.Artifact, error) {
|
||||
versioningOptions := versioning.Options{
|
||||
M2Path: config.M2Path,
|
||||
GlobalSettingsFile: config.GlobalSettingsFile,
|
||||
ProjectSettingsFile: config.ProjectSettingsFile,
|
||||
}
|
||||
artifact, err := versioning.GetArtifact(config.BuildTool, config.BuildDescriptorFile, &versioningOptions, command)
|
||||
|
||||
artifact, err := versioning.GetArtifact(config.BuildTool, config.BuildDescriptorFile, &versioningOptions, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get artifact from descriptor %v: %w", config.BuildDescriptorFile, err)
|
||||
return nil, fmt.Errorf("Unable to get artifact from descriptor %v: %w", config.BuildDescriptorFile, err)
|
||||
}
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, command fortifyExecRunner, artifact versioning.Artifact, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux, auditStatus map[string]string) ([]piperutils.Path, error) {
|
||||
var reports []piperutils.Path
|
||||
log.Entry().Debugf("Running Fortify scan against SSC at %v", config.ServerURL)
|
||||
coordinates, err := artifact.GetCoordinates()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get project coordinates from descriptor %v: %w", config.BuildDescriptorFile, err)
|
||||
return reports, fmt.Errorf("unable to get project coordinates from descriptor %v: %w", config.BuildDescriptorFile, err)
|
||||
}
|
||||
log.Entry().Debugf("determined project coordinates %v", coordinates)
|
||||
fortifyProjectName, fortifyProjectVersion := versioning.DetermineProjectCoordinates(config.ProjectName, config.VersioningModel, coordinates)
|
||||
project, err := sys.GetProjectByName(fortifyProjectName, config.AutoCreate, fortifyProjectVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load project %v: %w", fortifyProjectName, err)
|
||||
return reports, fmt.Errorf("Failed to load project %v: %w", fortifyProjectName, err)
|
||||
}
|
||||
projectVersion, err := sys.GetProjectVersionDetailsByProjectIDAndVersionName(project.ID, fortifyProjectVersion, config.AutoCreate, fortifyProjectName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load project version %v: %w", fortifyProjectVersion, err)
|
||||
return reports, fmt.Errorf("Failed to load project version %v: %w", fortifyProjectVersion, err)
|
||||
}
|
||||
|
||||
if len(config.PullRequestName) > 0 {
|
||||
fortifyProjectVersion = config.PullRequestName
|
||||
projectVersion, err := sys.LookupOrCreateProjectVersionDetailsForPullRequest(project.ID, projectVersion, fortifyProjectVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to lookup / create project version for pull request %v: %w", fortifyProjectVersion, err)
|
||||
return reports, fmt.Errorf("Failed to lookup / create project version for pull request %v: %w", fortifyProjectVersion, err)
|
||||
}
|
||||
log.Entry().Debugf("Looked up / created project version with ID %v for PR %v", projectVersion.ID, fortifyProjectVersion)
|
||||
} else {
|
||||
@ -100,12 +113,22 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, comman
|
||||
pullRequestProjectName := fmt.Sprintf("PR-%v", prID)
|
||||
err = sys.MergeProjectVersionStateOfPRIntoMaster(config.FprDownloadEndpoint, config.FprUploadEndpoint, project.ID, projectVersion.ID, pullRequestProjectName)
|
||||
if err != nil {
|
||||
return 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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Entry().Debugf("Scanning and uploading to project %v with version %v and projectVersionId %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
|
||||
filterSet, err := sys.GetFilterSetOfProjectVersionByTitle(projectVersion.ID, config.FilterSetTitle)
|
||||
if filterSet == nil || err != nil {
|
||||
return reports, fmt.Errorf("Failed to load filter set with title %v", config.FilterSetTitle)
|
||||
}
|
||||
|
||||
if config.VerifyOnly {
|
||||
log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
|
||||
return reports, verifyFFProjectCompliance(config, sys, project, projectVersion, filterSet, influx, auditStatus)
|
||||
}
|
||||
|
||||
log.Entry().Infof("Scanning and uploading to project %v with version %v and projectVersionId %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
|
||||
buildLabel := fmt.Sprintf("%v/repos/%v/%v/commits/%v", config.GithubAPIURL, config.Owner, config.Repository, config.CommitID)
|
||||
|
||||
// Create sourceanalyzer command based on configuration
|
||||
@ -126,7 +149,6 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, comman
|
||||
|
||||
triggerFortifyScan(config, command, buildID, buildLabel, fortifyProjectName)
|
||||
|
||||
var reports []piperutils.Path
|
||||
reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/fortify-scan.*", config.ModulePath)})
|
||||
reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/*.fpr", config.ModulePath)})
|
||||
|
||||
@ -145,24 +167,22 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, comman
|
||||
reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vfortify_result.xml", config.ModulePath)})
|
||||
}
|
||||
}
|
||||
piperutils.PersistReportsAndLinks("fortifyExecuteScan", config.ModulePath, reports, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf(message+": %w", err)
|
||||
}
|
||||
|
||||
log.Entry().Debugf("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
|
||||
filterSet, err := sys.GetFilterSetOfProjectVersionByTitle(projectVersion.ID, config.FilterSetTitle)
|
||||
if filterSet == nil || err != nil {
|
||||
return fmt.Errorf("Failed to load filter set with title %v", config.FilterSetTitle)
|
||||
return reports, fmt.Errorf(message+": %w", err)
|
||||
}
|
||||
|
||||
log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
|
||||
// Ensure latest FPR is processed
|
||||
err = verifyScanResultsFinishedUploading(config, sys, projectVersion.ID, buildLabel, filterSet,
|
||||
10*time.Second, time.Duration(config.PollingMinutes)*time.Minute)
|
||||
if err != nil {
|
||||
return err
|
||||
return reports, err
|
||||
}
|
||||
|
||||
return reports, verifyFFProjectCompliance(config, sys, project, projectVersion, filterSet, influx, auditStatus)
|
||||
}
|
||||
|
||||
func verifyFFProjectCompliance(config fortifyExecuteScanOptions, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) error {
|
||||
// Generate report
|
||||
if config.Reporting {
|
||||
resultURL := []byte(fmt.Sprintf("https://fortify.tools.sap/ssc/html/ssc/version/%v/fix/null/", projectVersion.ID))
|
||||
@ -186,8 +206,8 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, comman
|
||||
|
||||
log.Entry().Infof("Counted %v violations, details: %v", numberOfViolations, auditStatus)
|
||||
|
||||
influx.fortify_data.fields.projectName = fortifyProjectName
|
||||
influx.fortify_data.fields.projectVersion = fortifyProjectVersion
|
||||
influx.fortify_data.fields.projectName = *project.Name
|
||||
influx.fortify_data.fields.projectVersion = *projectVersion.Name
|
||||
influx.fortify_data.fields.violations = fmt.Sprintf("%v", numberOfViolations)
|
||||
if numberOfViolations > 0 {
|
||||
return errors.New("fortify scan failed, the project is not compliant. For details check the archived report")
|
||||
|
@ -64,6 +64,7 @@ type fortifyExecuteScanOptions struct {
|
||||
ProjectSettingsFile string `json:"projectSettingsFile,omitempty"`
|
||||
GlobalSettingsFile string `json:"globalSettingsFile,omitempty"`
|
||||
M2Path string `json:"m2Path,omitempty"`
|
||||
VerifyOnly bool `json:"verifyOnly,omitempty"`
|
||||
}
|
||||
|
||||
type fortifyExecuteScanInflux struct {
|
||||
@ -235,6 +236,7 @@ func addFortifyExecuteScanFlags(cmd *cobra.Command, stepConfig *fortifyExecuteSc
|
||||
cmd.Flags().StringVar(&stepConfig.ProjectSettingsFile, "projectSettingsFile", os.Getenv("PIPER_projectSettingsFile"), "Path to the mvn settings file that should be used as project settings file.")
|
||||
cmd.Flags().StringVar(&stepConfig.GlobalSettingsFile, "globalSettingsFile", os.Getenv("PIPER_globalSettingsFile"), "Path to the mvn settings file that should be used as global settings file.")
|
||||
cmd.Flags().StringVar(&stepConfig.M2Path, "m2Path", os.Getenv("PIPER_m2Path"), "Path to the location of the local repository that should be used.")
|
||||
cmd.Flags().BoolVar(&stepConfig.VerifyOnly, "verifyOnly", false, "Whether the step shall only apply verification checks or whether it does a full scan and check cycle")
|
||||
|
||||
cmd.MarkFlagRequired("authToken")
|
||||
cmd.MarkFlagRequired("serverUrl")
|
||||
@ -664,6 +666,14 @@ func fortifyExecuteScanMetadata() config.StepData {
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "maven/m2Path"}},
|
||||
},
|
||||
{
|
||||
Name: "verifyOnly",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "bool",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -13,8 +13,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
"github.com/SAP/jenkins-library/pkg/fortify"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/versioning"
|
||||
|
||||
"github.com/google/go-github/v32/github"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -22,6 +24,37 @@ import (
|
||||
"github.com/piper-validation/fortify-client-go/models"
|
||||
)
|
||||
|
||||
type artifactMock struct {
|
||||
Coordinates coordinatesMock
|
||||
}
|
||||
|
||||
type coordinatesMock struct {
|
||||
GroupID string
|
||||
ArtifactID string
|
||||
Version string
|
||||
}
|
||||
|
||||
func newCoordinatesMock() coordinatesMock {
|
||||
return coordinatesMock{
|
||||
GroupID: "a",
|
||||
ArtifactID: "b",
|
||||
Version: "1.0.0",
|
||||
}
|
||||
}
|
||||
func (a artifactMock) VersioningScheme() string {
|
||||
return "full"
|
||||
}
|
||||
func (a artifactMock) GetVersion() (string, error) {
|
||||
return a.Coordinates.Version, nil
|
||||
}
|
||||
func (a artifactMock) SetVersion(v string) error {
|
||||
a.Coordinates.Version = v
|
||||
return nil
|
||||
}
|
||||
func (a artifactMock) GetCoordinates() (versioning.Coordinates, error) {
|
||||
return a.Coordinates, nil
|
||||
}
|
||||
|
||||
type fortifyMock struct {
|
||||
Successive bool
|
||||
getArtifactsOfProjectVersionIdx int
|
||||
@ -29,7 +62,7 @@ type fortifyMock struct {
|
||||
}
|
||||
|
||||
func (f *fortifyMock) GetProjectByName(name string, autoCreate bool, projectVersion string) (*models.Project, error) {
|
||||
return &models.Project{Name: &name}, nil
|
||||
return &models.Project{Name: &name, ID: 64}, nil
|
||||
}
|
||||
func (f *fortifyMock) GetProjectVersionDetailsByProjectIDAndVersionName(id int64, name string, autoCreate bool, projectName string) (*models.ProjectVersion, error) {
|
||||
return &models.ProjectVersion{ID: id, Name: &name, Project: &models.Project{Name: &projectName}}, nil
|
||||
@ -143,7 +176,7 @@ func (f *fortifyMock) GetFilterSetByDisplayName(issueFilterSelectorSet *models.I
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return &models.IssueFilterSelector{DisplayName: name}
|
||||
}
|
||||
func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64, filter, filterSetGUID string, issueFilterSelectorSet *models.IssueFilterSelectorSet) ([]*models.ProjectVersionIssueGroup, error) {
|
||||
if filter == "ET1:abcd" {
|
||||
@ -162,7 +195,7 @@ func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64
|
||||
{ID: &group3, TotalCount: &total3, AuditedCount: &audited3},
|
||||
}, nil
|
||||
}
|
||||
if issueFilterSelectorSet != nil && issueFilterSelectorSet.FilterBySet[0].GUID == "3" {
|
||||
if issueFilterSelectorSet != nil && issueFilterSelectorSet.FilterBySet != nil && len(issueFilterSelectorSet.FilterBySet) > 0 && issueFilterSelectorSet.FilterBySet[0].GUID == "3" {
|
||||
group := "3"
|
||||
total := int32(4)
|
||||
audited := int32(0)
|
||||
@ -303,18 +336,65 @@ func TestParametersAreValidated(t *testing.T) {
|
||||
{
|
||||
nameOfRun: "all parameters empty",
|
||||
config: fortifyExecuteScanOptions{},
|
||||
expectedError: "unable to get artifact from descriptor : build tool '' not supported",
|
||||
expectedError: "Unable to get artifact from descriptor : build tool '' not supported",
|
||||
},
|
||||
}
|
||||
|
||||
for _, data := range testData {
|
||||
t.Run(data.nameOfRun, func(t *testing.T) {
|
||||
_, err := determineArtifact(data.config, &command.Command{})
|
||||
assert.EqualError(t, err, data.expectedError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutions(t *testing.T) {
|
||||
type parameterTestData struct {
|
||||
nameOfRun string
|
||||
config fortifyExecuteScanOptions
|
||||
expectedError string
|
||||
expectedReportsLength int
|
||||
expectedReports []string
|
||||
}
|
||||
|
||||
testData := []parameterTestData{
|
||||
{
|
||||
nameOfRun: "golang scan and verify",
|
||||
config: fortifyExecuteScanOptions{BuildTool: "golang", BuildDescriptorFile: "go.mod"},
|
||||
expectedReportsLength: 2,
|
||||
expectedReports: []string{"target/fortify-scan.*", "target/*.fpr"},
|
||||
},
|
||||
{
|
||||
nameOfRun: "golang verify only",
|
||||
config: fortifyExecuteScanOptions{BuildTool: "golang", BuildDescriptorFile: "go.mod", VerifyOnly: true},
|
||||
expectedReportsLength: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, data := range testData {
|
||||
t.Run(data.nameOfRun, func(t *testing.T) {
|
||||
ff := fortifyMock{}
|
||||
runner := execRunnerMock{}
|
||||
runner := &execRunnerMock{}
|
||||
artMock := artifactMock{Coordinates: newCoordinatesMock()}
|
||||
influx := fortifyExecuteScanInflux{}
|
||||
auditStatus := map[string]string{}
|
||||
err := runFortifyScan(data.config, &ff, &runner, nil, &influx, auditStatus)
|
||||
assert.EqualError(t, err, data.expectedError)
|
||||
reports, _ := runFortifyScan(data.config, &ff, runner, artMock, nil, &influx, auditStatus)
|
||||
if len(data.expectedReports) != data.expectedReportsLength {
|
||||
assert.Fail(t, fmt.Sprintf("Wrong number of reports detected, expected %v, actual %v", data.expectedReportsLength, len(data.expectedReports)))
|
||||
}
|
||||
if len(data.expectedReports) > 0 {
|
||||
for _, expectedPath := range data.expectedReports {
|
||||
found := false
|
||||
for _, actualPath := range reports {
|
||||
if actualPath.Target == expectedPath {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
assert.Failf(t, "Expected path %s not found", expectedPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ spec:
|
||||
- name: avoidDuplicateProjectScans
|
||||
type: bool
|
||||
description: Whether duplicate scans of the same project state shall be avoided or not
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -34,7 +33,6 @@ spec:
|
||||
- name: filterPattern
|
||||
type: string
|
||||
description: The filter pattern used to zip the files relevant for scanning, patterns can be negated by setting an exclamation mark in front i.e. `!test/*.js` would avoid adding any javascript files located in the test directory
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -45,7 +43,6 @@ spec:
|
||||
- name: fullScanCycle
|
||||
type: string
|
||||
description: Indicates how often a full scan should happen between the incremental scans when activated
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -54,7 +51,6 @@ spec:
|
||||
- name: fullScansScheduled
|
||||
type: bool
|
||||
description: Whether full scans are to be scheduled or not. Should be used in relation with `incremental` and `fullScanCycle`
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -63,7 +59,6 @@ spec:
|
||||
- name: generatePdfReport
|
||||
type: bool
|
||||
description: Whether to generate a PDF report of the analysis results or not
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -72,7 +67,6 @@ spec:
|
||||
- name: incremental
|
||||
type: bool
|
||||
description: Whether incremental scans are to be applied which optimizes the scan time but might reduce detection capabilities. Therefore full scans are still required from time to time and should be scheduled via `fullScansScheduled` and `fullScanCycle`
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -94,7 +88,6 @@ spec:
|
||||
- name: preset
|
||||
type: string
|
||||
description: The preset to use for scanning, if not set explicitly the step will attempt to look up the project's setting based on the availability of `checkmarxCredentialsId`
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -134,7 +127,6 @@ spec:
|
||||
- name: sourceEncoding
|
||||
type: string
|
||||
description: The source encoding to be used, if not set explicitly the project's default will be used
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -147,7 +139,6 @@ spec:
|
||||
deprecated: true
|
||||
type: string
|
||||
description: The group ID related to your team which can be obtained via the Pipeline Syntax plugin as described in the `Details` section
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -155,7 +146,6 @@ spec:
|
||||
- name: teamName
|
||||
type: string
|
||||
description: The full name of the team to assign newly created projects to which is preferred to teamId
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -173,10 +163,17 @@ spec:
|
||||
- name: checkmarxCredentialsId
|
||||
type: secret
|
||||
param: username
|
||||
- name: verifyOnly
|
||||
type: bool
|
||||
description: Whether the step shall only apply verification checks or whether it does a full scan and check cycle
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: false
|
||||
- name: vulnerabilityThresholdEnabled
|
||||
type: bool
|
||||
description: Whether the thresholds are enabled or not. If enabled the build will be set to `vulnerabilityThresholdResult` in case a specific threshold value is exceeded
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -185,7 +182,6 @@ spec:
|
||||
- name: vulnerabilityThresholdHigh
|
||||
type: int
|
||||
description: The specific threshold for high severity findings
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -194,7 +190,6 @@ spec:
|
||||
- name: vulnerabilityThresholdLow
|
||||
type: int
|
||||
description: The specific threshold for low severity findings
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -203,7 +198,6 @@ spec:
|
||||
- name: vulnerabilityThresholdMedium
|
||||
type: int
|
||||
description: The specific threshold for medium severity findings
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -212,7 +206,6 @@ spec:
|
||||
- name: vulnerabilityThresholdResult
|
||||
type: string
|
||||
description: The result of the build in case thresholds are enabled and exceeded
|
||||
mandatory: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
|
@ -502,6 +502,14 @@ spec:
|
||||
- PARAMETERS
|
||||
aliases:
|
||||
- name: maven/m2Path
|
||||
- name: verifyOnly
|
||||
type: bool
|
||||
description: Whether the step shall only apply verification checks or whether it does a full scan and check cycle
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: false
|
||||
containers:
|
||||
- image: "ppiper/fortify"
|
||||
workingDir: "/home/piper"
|
||||
|
Loading…
x
Reference in New Issue
Block a user