1
0
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:
Sven Merk 2020-09-18 08:19:34 +02:00 committed by GitHub
parent a47d0443f2
commit 612d3a645b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 263 additions and 113 deletions

View File

@ -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
}

View File

@ -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{},

View File

@ -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) {

View File

@ -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")

View File

@ -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{},
},
},
},
},

View File

@ -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)
}
}
}
})
}
}

View File

@ -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

View File

@ -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"