1
0
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:
Sven Merk 2021-02-26 13:43:03 +01:00 committed by GitHub
parent 3f8a1f141b
commit d2eb2877e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 9 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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