mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-18 05:18:24 +02:00
fortifyExecuteScan: Functional enhancements (#2647)
* Improvements * Formatting * Fix test * Update resources/metadata/fortify.yaml Enhance description Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> * Unify version handling with ws step * Part 2 * go fmt Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
parent
3f8a1f141b
commit
d2eb2877e0
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
@ -14,6 +13,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
|
||||
"github.com/bmatcuk/doublestar"
|
||||
|
||||
"github.com/google/go-github/v32/github"
|
||||
@ -58,7 +59,7 @@ func (bundle *fortifyUtilsBundle) GetArtifact(buildTool, buildDescriptorFile str
|
||||
return versioning.GetArtifact(buildTool, buildDescriptorFile, options, bundle)
|
||||
}
|
||||
|
||||
func newFortifyUtilsBundleBundle() fortifyUtils {
|
||||
func newFortifyUtilsBundle() fortifyUtils {
|
||||
utils := fortifyUtilsBundle{
|
||||
Command: &command.Command{},
|
||||
Files: &piperutils.Files{},
|
||||
@ -75,7 +76,7 @@ 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.Minute*15)
|
||||
utils := newFortifyUtilsBundleBundle()
|
||||
utils := newFortifyUtilsBundle()
|
||||
|
||||
reports, err := runFortifyScan(config, sys, utils, telemetryData, influx, auditStatus)
|
||||
piperutils.PersistReportsAndLinks("fortifyExecuteScan", config.ModulePath, reports, nil)
|
||||
@ -125,14 +126,22 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
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)
|
||||
log.Entry().Debugf("loaded project coordinates %v from descriptor", coordinates)
|
||||
|
||||
if len(config.Version) > 0 {
|
||||
log.Entry().Infof("Resolving product version from default provided '%s' with versioning '%s'", config.Version, config.VersioningModel)
|
||||
coordinates.Version = config.Version
|
||||
}
|
||||
|
||||
fortifyProjectName, fortifyProjectVersion := versioning.DetermineProjectCoordinatesWithCustomVersion(config.ProjectName, config.VersioningModel, config.CustomScanVersion, coordinates)
|
||||
project, err := sys.GetProjectByName(fortifyProjectName, config.AutoCreate, fortifyProjectVersion)
|
||||
if err != nil {
|
||||
classifyErrorOnLookup(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 {
|
||||
classifyErrorOnLookup(err)
|
||||
return reports, fmt.Errorf("Failed to load project version %v: %w", fortifyProjectVersion, err)
|
||||
}
|
||||
|
||||
@ -140,6 +149,7 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils
|
||||
fortifyProjectVersion = config.PullRequestName
|
||||
projectVersion, err := sys.LookupOrCreateProjectVersionDetailsForPullRequest(project.ID, projectVersion, fortifyProjectVersion)
|
||||
if err != nil {
|
||||
classifyErrorOnLookup(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)
|
||||
@ -219,6 +229,12 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils
|
||||
return reports, verifyFFProjectCompliance(config, sys, project, projectVersion, filterSet, influx, auditStatus)
|
||||
}
|
||||
|
||||
func classifyErrorOnLookup(err error) {
|
||||
if strings.Contains(err.Error(), "connect: connection refused") || strings.Contains(err.Error(), "net/http: TLS handshake timeout") {
|
||||
log.SetErrorCategory(log.ErrorService)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -452,6 +468,7 @@ func checkArtifactStatus(config fortifyExecuteScanOptions, projectVersionID int6
|
||||
if "PROCESSING" == artifact.Status || "SCHED_PROCESSING" == artifact.Status {
|
||||
pollingTime := time.Duration(retries) * pollingDelay
|
||||
if pollingTime >= timeout {
|
||||
log.SetErrorCategory(log.ErrorService)
|
||||
return fmt.Errorf("terminating after %v since artifact for Project Version %v is still in status %v", timeout, projectVersionID, artifact.Status)
|
||||
}
|
||||
log.Entry().Infof("Most recent artifact uploaded on %v of Project Version %v is still in status %v...", artifact.UploadDate, projectVersionID, artifact.Status)
|
||||
@ -460,9 +477,11 @@ func checkArtifactStatus(config fortifyExecuteScanOptions, projectVersionID int6
|
||||
}
|
||||
if "REQUIRE_AUTH" == artifact.Status {
|
||||
// verify no manual issue approval needed
|
||||
return fmt.Errorf("There are artifacts that require manual approval for Project Version %v\n%v/html/ssc/index.jsp#!/version/%v/artifacts?filterSet=%v", projectVersionID, config.ServerURL, projectVersionID, filterSet.GUID)
|
||||
log.SetErrorCategory(log.ErrorCompliance)
|
||||
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)
|
||||
}
|
||||
if "ERROR_PROCESSING" == artifact.Status {
|
||||
log.SetErrorCategory(log.ErrorService)
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
|
||||
type fortifyExecuteScanOptions struct {
|
||||
AuthToken string `json:"authToken,omitempty"`
|
||||
CustomScanVersion string `json:"customScanVersion,omitempty"`
|
||||
GithubToken string `json:"githubToken,omitempty"`
|
||||
AutoCreate bool `json:"autoCreate,omitempty"`
|
||||
ModulePath string `json:"modulePath,omitempty"`
|
||||
@ -27,6 +28,7 @@ type fortifyExecuteScanOptions struct {
|
||||
PythonRequirementsInstallSuffix string `json:"pythonRequirementsInstallSuffix,omitempty"`
|
||||
PythonVersion string `json:"pythonVersion,omitempty"`
|
||||
UploadResults bool `json:"uploadResults,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
BuildDescriptorFile string `json:"buildDescriptorFile,omitempty"`
|
||||
CommitID string `json:"commitId,omitempty"`
|
||||
CommitMessage string `json:"commitMessage,omitempty"`
|
||||
@ -190,6 +192,7 @@ and Java plus Maven or alternatively Python installed into it for being able to
|
||||
|
||||
func addFortifyExecuteScanFlags(cmd *cobra.Command, stepConfig *fortifyExecuteScanOptions) {
|
||||
cmd.Flags().StringVar(&stepConfig.AuthToken, "authToken", os.Getenv("PIPER_authToken"), "The FortifyToken to use for authentication")
|
||||
cmd.Flags().StringVar(&stepConfig.CustomScanVersion, "customScanVersion", os.Getenv("PIPER_customScanVersion"), "Custom version of the Fortify project used as source.")
|
||||
cmd.Flags().StringVar(&stepConfig.GithubToken, "githubToken", os.Getenv("PIPER_githubToken"), "GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line")
|
||||
cmd.Flags().BoolVar(&stepConfig.AutoCreate, "autoCreate", false, "Whether Fortify project and project version shall be implicitly auto created in case they cannot be found in the backend")
|
||||
cmd.Flags().StringVar(&stepConfig.ModulePath, "modulePath", `./`, "Allows providing the path for the module to scan")
|
||||
@ -200,6 +203,7 @@ func addFortifyExecuteScanFlags(cmd *cobra.Command, stepConfig *fortifyExecuteSc
|
||||
cmd.Flags().StringVar(&stepConfig.PythonRequirementsInstallSuffix, "pythonRequirementsInstallSuffix", os.Getenv("PIPER_pythonRequirementsInstallSuffix"), "The suffix for the command used to install the requirements file in `buildTool: 'pip'` to populate the build environment with the necessary dependencies")
|
||||
cmd.Flags().StringVar(&stepConfig.PythonVersion, "pythonVersion", `python3`, "Python version to be used in `buildTool: 'pip'`")
|
||||
cmd.Flags().BoolVar(&stepConfig.UploadResults, "uploadResults", true, "Whether results shall be uploaded or not")
|
||||
cmd.Flags().StringVar(&stepConfig.Version, "version", os.Getenv("PIPER_version"), "Version used in conjunction with [`versioningModel`](#versioningModel) to identify the Fortify project to be created and used for results aggregation.")
|
||||
cmd.Flags().StringVar(&stepConfig.BuildDescriptorFile, "buildDescriptorFile", `./pom.xml`, "Path to the build descriptor file addressing the module/folder to be scanned.")
|
||||
cmd.Flags().StringVar(&stepConfig.CommitID, "commitId", os.Getenv("PIPER_commitId"), "Set the Git commit ID for identifying artifacts throughout the scan.")
|
||||
cmd.Flags().StringVar(&stepConfig.CommitMessage, "commitMessage", os.Getenv("PIPER_commitMessage"), "Set the Git commit message for identifying pull request merges throughout the scan.")
|
||||
@ -274,6 +278,14 @@ func fortifyExecuteScanMetadata() config.StepData {
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "customScanVersion",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "githubToken",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
@ -365,6 +377,19 @@ func fortifyExecuteScanMetadata() config.StepData {
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "commonPipelineEnvironment",
|
||||
Param: "artifactVersion",
|
||||
},
|
||||
},
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "fortifyProjectVersion"}},
|
||||
},
|
||||
{
|
||||
Name: "buildDescriptorFile",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
|
@ -616,7 +616,7 @@ func TestVerifyScanResultsFinishedUploading(t *testing.T) {
|
||||
t.Run("error required auth", func(t *testing.T) {
|
||||
ffMock := fortifyMock{}
|
||||
err := verifyScanResultsFinishedUploadingDefaults(config, &ffMock, 4713)
|
||||
assert.EqualError(t, err, "There are artifacts that require manual approval for Project Version 4713\n/html/ssc/index.jsp#!/version/4713/artifacts?filterSet=")
|
||||
assert.EqualError(t, err, "There are artifacts that require manual approval for Project Version 4713, please visit Fortify SSC and approve them for processing\n/html/ssc/index.jsp#!/version/4713/artifacts?filterSet=")
|
||||
})
|
||||
|
||||
t.Run("error polling timeout", func(t *testing.T) {
|
||||
|
@ -2,6 +2,7 @@ package fortify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -84,11 +85,12 @@ func NewSystemInstance(serverURL, apiEndpoint, authToken string, timeout time.Du
|
||||
dateTimeFormat := models.Iso8601MilliDateTime{}
|
||||
format.Add("datetime", &dateTimeFormat, models.IsDateTime)
|
||||
clientInstance := ff.NewHTTPClientWithConfig(format, createTransportConfig(serverURL, apiEndpoint))
|
||||
encodedAuthToken := base64EndodePlainToken(authToken)
|
||||
httpClientInstance := &piperHttp.Client{}
|
||||
httpClientOptions := piperHttp.ClientOptions{Token: "FortifyToken " + authToken, TransportTimeout: timeout}
|
||||
httpClientOptions := piperHttp.ClientOptions{Token: "FortifyToken " + encodedAuthToken, TransportTimeout: timeout}
|
||||
httpClientInstance.SetOptions(httpClientOptions)
|
||||
|
||||
return NewSystemInstanceForClient(clientInstance, httpClientInstance, serverURL, authToken, timeout)
|
||||
return NewSystemInstanceForClient(clientInstance, httpClientInstance, serverURL, encodedAuthToken, timeout)
|
||||
}
|
||||
|
||||
func createTransportConfig(serverURL, apiEndpoint string) *ff.TransportConfig {
|
||||
@ -127,6 +129,14 @@ func splitHostAndEndpoint(urlWithoutScheme string) (host, endpoint string) {
|
||||
return
|
||||
}
|
||||
|
||||
func base64EndodePlainToken(authToken string) (encodedAuthToken string) {
|
||||
isEncoded := strings.Index(authToken, "-") < 0
|
||||
if isEncoded {
|
||||
return authToken
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString([]byte(authToken))
|
||||
}
|
||||
|
||||
// NewSystemInstanceForClient - creates a new SystemInstance
|
||||
func NewSystemInstanceForClient(clientInstance *ff.Fortify, httpClientInstance *piperHttp.Client, serverURL, authToken string, requestTimeout time.Duration) *SystemInstance {
|
||||
return &SystemInstance{
|
||||
|
@ -1285,3 +1285,16 @@ func TestMergeProjectVersionStateOfPRIntoMaster(t *testing.T) {
|
||||
assert.Equal(t, true, inactivateCalled, "Expected different value")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBase64EndodePlainToken(t *testing.T) {
|
||||
t.Run("Encoded token untouched", func(t *testing.T) {
|
||||
token := "OTUzODcwNDYtNWFjOC00NTcwLTg3NWQtYTVlYzhiZDhkM2Qy"
|
||||
encodedToken := base64EndodePlainToken(token)
|
||||
assert.Equal(t, token, encodedToken)
|
||||
})
|
||||
t.Run("Unencoded token gets encoded", func(t *testing.T) {
|
||||
token := "95387046-5ac8-4570-875d-a5ec8bd8d3d2"
|
||||
encodedToken := base64EndodePlainToken(token)
|
||||
assert.Equal(t, "OTUzODcwNDYtNWFjOC00NTcwLTg3NWQtYTVlYzhiZDhkM2Qy", encodedToken)
|
||||
})
|
||||
}
|
||||
|
@ -46,6 +46,18 @@ spec:
|
||||
- $(vaultPath)/fortify
|
||||
- $(vaultBasePath)/$(vaultPipelineName)/fortify
|
||||
- $(vaultBasePath)/GROUP-SECRETS/fortify
|
||||
- name: customScanVersion
|
||||
type: string
|
||||
description: Custom version of the Fortify project used as source.
|
||||
longDescription: |-
|
||||
Defines a custom version for the Fortify scan which deviates from the typical versioning pattern using [`version`](#version) and [`versioningModel`](#versioningModel)
|
||||
It allows to set non-numeric versions as well and supersedes the value of [`version`](#version) and [`versioningModel`](#versioningModel) which is calculated automatically.
|
||||
The parameter is also used by other scan steps (e.g. BlackDuck Detect) and thus allows a common custom version across scan tools.
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: githubToken
|
||||
description: "GitHub personal access token as per
|
||||
https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line"
|
||||
@ -144,6 +156,23 @@ spec:
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: true
|
||||
- name: version
|
||||
aliases:
|
||||
- name: fortifyProjectVersion
|
||||
deprecated: true
|
||||
type: string
|
||||
description: Version used in conjunction with [`versioningModel`](#versioningModel) to identify the Fortify project to be created and used for results aggregation.
|
||||
longDescription: |-
|
||||
Version used in conjunction with [`versioningModel`](#versioningModel) to identify the Fortify project to be created and used for results aggregation.
|
||||
This is usually determined automatically based on the information in the buildTool specific build descriptor file.
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
resourceRef:
|
||||
- name: commonPipelineEnvironment
|
||||
param: artifactVersion
|
||||
- name: buildDescriptorFile
|
||||
type: string
|
||||
conditions:
|
||||
|
Loading…
Reference in New Issue
Block a user