2019-03-04 16:02:01 +02:00
import com.sap.piper.DescriptorUtils
2019-03-06 16:08:42 +02:00
import com.sap.piper.GenerateDocumentation
2019-02-28 14:01:30 +02:00
import com.sap.piper.JsonUtils
import com.sap.piper.Utils
import com.sap.piper.integration.WhitesourceOrgAdminRepository
import com.sap.piper.integration.WhitesourceRepository
import com.sap.piper.ConfigurationHelper
import com.sap.piper.WhitesourceConfigurationHelper
import com.sap.piper.mta.MtaMultiplexer
import groovy.text.GStringTemplateEngine
import groovy.transform.Field
import groovy.text.SimpleTemplateEngine
import static com . sap . piper . Prerequisites . checkScript
2019-03-06 14:07:55 +02:00
@Field String STEP_NAME = getClass ( ) . getName ( )
2019-02-28 14:01:30 +02:00
@Field Set GENERAL_CONFIG_KEYS = [
2019-03-14 16:14:09 +02:00
'whitesource' ,
2019-03-06 16:08:42 +02:00
/ * *
* Jenkins credentials ID referring to the organization admin ' s token .
2019-03-21 14:25:22 +02:00
* @parentConfigKey whitesource
2019-03-06 16:08:42 +02:00
* /
2019-03-05 14:59:27 +02:00
'orgAdminUserTokenCredentialsId' ,
2019-03-06 16:08:42 +02:00
/ * *
* WhiteSource token identifying your organization .
2019-03-21 14:25:22 +02:00
* @parentConfigKey whitesource
2019-03-06 16:08:42 +02:00
* /
2019-03-05 14:59:27 +02:00
'orgToken' ,
2019-03-06 16:08:42 +02:00
/ * *
* Name of the WhiteSource product to be created and used for results aggregation .
2019-03-21 14:25:22 +02:00
* @parentConfigKey whitesource
2019-03-06 16:08:42 +02:00
* /
2019-03-05 14:59:27 +02:00
'productName' ,
2019-03-06 16:08:42 +02:00
/ * *
* Version of the WhiteSource product to be created and used for results aggregation , usually determined automatically .
2019-03-21 14:25:22 +02:00
* @parentConfigKey whitesource
2019-03-06 16:08:42 +02:00
* /
2019-03-05 14:59:27 +02:00
'productVersion' ,
2019-03-06 16:08:42 +02:00
/ * *
* Token of the WhiteSource product to be created and used for results aggregation , usually determined automatically .
2019-03-21 14:25:22 +02:00
* @parentConfigKey whitesource
2019-03-06 16:08:42 +02:00
* /
2019-03-05 14:59:27 +02:00
'productToken' ,
2019-03-06 16:08:42 +02:00
/ * *
* List of WhiteSource projects to be included in the assessment part of the step , usually determined automatically .
2019-03-21 14:25:22 +02:00
* @parentConfigKey whitesource
2019-03-06 16:08:42 +02:00
* /
2019-03-05 14:59:27 +02:00
'projectNames' ,
2019-03-06 16:08:42 +02:00
/ * *
2019-03-21 14:25:22 +02:00
* URL used for downloading the Java Runtime Environment ( JRE ) required to run the WhiteSource Unified Agent .
* @parentConfigKey whitesource
2019-03-06 16:08:42 +02:00
* /
2019-03-21 14:25:22 +02:00
'jreDownloadUrl' ,
2019-03-06 16:08:42 +02:00
/ * *
* URL to the WhiteSource server API used for communication , defaults to ` https: //saas.whitesourcesoftware.com/api`.
2019-03-21 14:25:22 +02:00
* @parentConfigKey whitesource
2019-03-06 16:08:42 +02:00
* /
2019-03-05 14:59:27 +02:00
'serviceUrl' ,
2019-03-06 16:08:42 +02:00
/ * *
* Jenkins credentials ID referring to the product admin ' s token .
2019-03-21 14:25:22 +02:00
* @parentConfigKey whitesource
2019-03-06 16:08:42 +02:00
* /
2019-03-05 14:59:27 +02:00
'userTokenCredentialsId' ,
2019-03-21 14:25:22 +02:00
/ * *
* Type of development stack used to implement the solution .
2019-04-29 11:43:26 +02:00
* @possibleValues ` golang ` , ` maven ` , ` mta ` , ` npm ` , ` pip ` , ` sbt `
2019-03-21 14:25:22 +02:00
* /
'scanType' ,
2019-03-06 16:08:42 +02:00
/ * *
* Whether verbose output should be produced .
* @possibleValues ` true ` , ` false `
* /
2019-03-05 14:59:27 +02:00
'verbose'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS + [
2019-03-26 18:07:15 +02:00
/ * *
2019-04-04 16:05:26 +02:00
* Install command that can be used to populate the default docker image for some scenarios .
2019-03-26 18:07:15 +02:00
* /
2019-04-04 16:05:26 +02:00
'installCommand' ,
2019-03-06 16:08:42 +02:00
/ * *
* URL used to download the latest version of the WhiteSource Unified Agent .
* /
2019-02-28 14:01:30 +02:00
'agentDownloadUrl' ,
2019-03-06 16:08:42 +02:00
/ * *
* Locally used name for the Unified Agent jar file after download .
* /
2019-02-28 14:01:30 +02:00
'agentFileName' ,
2019-03-06 16:08:42 +02:00
/ * *
* Additional parameters passed to the Unified Agent command line .
* /
2019-02-28 14:01:30 +02:00
'agentParameters' ,
2019-03-06 16:08:42 +02:00
/ * *
* List of build descriptors and therefore modules to exclude from the scan and assessment activities .
* /
2019-02-28 14:01:30 +02:00
'buildDescriptorExcludeList' ,
2019-03-06 16:08:42 +02:00
/ * *
* Explicit path to the build descriptor file .
* /
2019-02-28 14:01:30 +02:00
'buildDescriptorFile' ,
2019-03-06 16:08:42 +02:00
/ * *
* Explicit path to the WhiteSource Unified Agent configuration file .
* /
2019-02-28 14:01:30 +02:00
'configFilePath' ,
2019-03-06 16:08:42 +02:00
/ * *
* Whether to create the related WhiteSource product on the fly based on the supplied pipeline configuration .
* /
'createProductFromPipeline' ,
/ * *
* The list of email addresses to assign as product admins for newly created WhiteSource products .
* /
'emailAddressesOfInitialProductAdmins' ,
/ * *
* Docker image to be used for scanning .
* /
2019-02-28 14:01:30 +02:00
'dockerImage' ,
2019-03-06 16:08:42 +02:00
/ * *
* Docker workspace to be used for scanning .
* /
2019-02-28 14:01:30 +02:00
'dockerWorkspace' ,
2019-03-06 16:08:42 +02:00
/ * *
* Whether license compliance is considered and reported as part of the assessment .
* @possibleValues ` true ` , ` false `
* /
2019-02-28 14:01:30 +02:00
'licensingVulnerabilities' ,
2019-03-06 16:08:42 +02:00
/ * *
* Limit of parallel jobs being run at once in case of ` scanType: 'mta' ` based scenarios , defaults to ` 15 ` .
* /
2019-02-28 14:01:30 +02:00
'parallelLimit' ,
2019-03-06 16:08:42 +02:00
/ * *
* Whether assessment is being done at all , defaults to ` true ` .
* @possibleValues ` true ` , ` false `
* /
2019-02-28 14:01:30 +02:00
'reporting' ,
2019-03-06 16:08:42 +02:00
/ * *
* Whether security compliance is considered and reported as part of the assessment .
* @possibleValues ` true ` , ` false `
* /
2019-02-28 14:01:30 +02:00
'securityVulnerabilities' ,
2019-03-06 16:08:42 +02:00
/ * *
* Limit of tollerable CVSS v3 score upon assessment and in consequence fails the build , defaults to ` - 1 ` .
* @possibleValues ` - 1 ` to switch failing off , any ` positive integer between 0 and 10 ` to fail on issues with the specified limit or above
* /
2019-03-05 14:59:27 +02:00
'cvssSeverityLimit' ,
2019-03-06 16:08:42 +02:00
/ * *
* List of stashes to be unstashed into the workspace before performing the scan .
* /
2019-02-28 14:01:30 +02:00
'stashContent' ,
2019-03-06 16:08:42 +02:00
/ * *
* Timeout in seconds until a HTTP call is forcefully terminated .
* /
2019-02-28 14:01:30 +02:00
'timeout' ,
2019-03-06 16:08:42 +02:00
/ * *
* Name of the file the vulnerability report is written to .
* /
2019-02-28 14:01:30 +02:00
'vulnerabilityReportFileName' ,
2019-03-06 16:08:42 +02:00
/ * *
* Title of vulnerability report written during the assessment phase .
* /
2019-03-13 13:10:23 +02:00
'vulnerabilityReportTitle'
2019-02-28 14:01:30 +02:00
]
2019-03-05 14:59:27 +02:00
2019-02-28 14:01:30 +02:00
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
2019-03-06 15:03:00 +02:00
@Field Map CONFIG_KEY_COMPATIBILITY = [
2019-03-14 16:14:09 +02:00
productName : 'whitesourceProductName' ,
productToken : 'whitesourceProductToken' ,
projectNames : 'whitesourceProjectNames' ,
userTokenCredentialsId : 'whitesourceUserTokenCredentialsId' ,
serviceUrl : 'whitesourceServiceUrl' ,
agentDownloadUrl : 'fileAgentDownloadUrl' ,
agentParameters : 'fileAgentParameters' ,
whitesource : [
orgAdminUserTokenCredentialsId : 'orgAdminUserTokenCredentialsId' ,
orgToken : 'orgToken' ,
productName : 'productName' ,
productToken : 'productToken' ,
projectNames : 'projectNames' ,
serviceUrl : 'serviceUrl' ,
2019-03-15 14:11:34 +02:00
configFilePath : 'configFilePath' ,
2019-03-14 16:14:09 +02:00
userTokenCredentialsId : 'userTokenCredentialsId' ,
agentDownloadUrl : 'agentDownloadUrl' ,
agentFileName : 'agentFileName' ,
agentParameters : 'agentParameters' ,
buildDescriptorExcludeList : 'buildDescriptorExcludeList' ,
buildDescriptorFile : 'buildDescriptorFile' ,
createProductFromPipeline : 'createProductFromPipeline' ,
emailAddressesOfInitialProductAdmins : 'emailAddressesOfInitialProductAdmins' ,
jreDownloadUrl : 'jreDownloadUrl' ,
licensingVulnerabilities : 'licensingVulnerabilities' ,
parallelLimit : 'parallelLimit' ,
reporting : 'reporting' ,
securityVulnerabilities : 'securityVulnerabilities' ,
cvssSeverityLimit : 'cvssSeverityLimit' ,
timeout : 'timeout' ,
vulnerabilityReportFileName : 'vulnerabilityReportFileName' ,
2019-03-26 17:52:57 +02:00
vulnerabilityReportTitle : 'vulnerabilityReportTitle' ,
2019-04-04 16:05:26 +02:00
installCommand : 'installCommand'
2019-03-14 16:14:09 +02:00
]
2019-03-06 15:03:00 +02:00
]
2019-03-06 16:08:42 +02:00
/ * *
2019-03-26 15:22:02 +02:00
* BETA
*
2019-03-07 14:16:03 +02:00
* With this step [ WhiteSource ] ( https: //www.whitesourcesoftware.com) security and license compliance scans can be executed and assessed.
2019-03-06 16:08:42 +02:00
*
2019-03-08 13:33:31 +02:00
* WhiteSource is a Software as a Service offering based on a so called unified agent that locally determines the dependency
2019-03-06 16:08:42 +02:00
* tree of a node . js , Java , Python , Ruby , or Scala based solution and sends it to the WhiteSource server for a policy based license compliance
2019-03-08 13:33:31 +02:00
* check and additional Free and Open Source Software Publicly Known Vulnerabilities detection .
2019-03-06 16:08:42 +02:00
*
* ! ! ! note "Docker Images"
2019-03-26 15:22:02 +02:00
* The underlying Docker images are public and specific to the solution ' s programming language ( s ) and therefore may have to be exchanged
* to fit to and support the relevant scenario . The default Python environment used is i . e . Python 3 based .
*
* ! ! ! warn "Restrictions"
* Currently the step does contain hardened scan configurations for ` scanType ` ` 'pip' ` and ` 'go' ` . Other environments are still being elaborated ,
* so please thoroughly check your results and do not take them for granted by default .
* Also not all environments have been thoroughly tested already therefore you might need to tweak around with the default containers used or
* create your own ones to adequately support your scenario . To do so please modify ` dockerImage ` and ` dockerWorkspace ` parameters .
* The step expects an environment containing the programming language related compiler / interpreter as well as the related build tool . For a list
* of the supported build tools per environment please refer to the [ WhiteSource Unified Agent Documentation ] ( https: //whitesource.atlassian.net/wiki/spaces/WD/pages/33718339/Unified+Agent).
2019-03-06 16:08:42 +02:00
* /
@GenerateDocumentation
2019-02-28 14:01:30 +02:00
void call ( Map parameters = [ : ] ) {
handlePipelineStepErrors ( stepName: STEP_NAME , stepParameters: parameters ) {
def script = checkScript ( this , parameters ) ? : this
def utils = parameters . juStabUtils ? : new Utils ( )
2019-03-04 16:02:01 +02:00
def descriptorUtils = parameters . descriptorUtilsStub ? : new DescriptorUtils ( )
2019-02-28 14:01:30 +02:00
def statusCode = 1
// load default & individual configuration
Map config = ConfigurationHelper . newInstance ( this )
2019-03-07 13:14:25 +02:00
. loadStepDefaults ( CONFIG_KEY_COMPATIBILITY )
2019-03-06 15:03:00 +02:00
. mixinGeneralConfig ( script . commonPipelineEnvironment , GENERAL_CONFIG_KEYS , CONFIG_KEY_COMPATIBILITY )
. mixinStepConfig ( script . commonPipelineEnvironment , STEP_CONFIG_KEYS , CONFIG_KEY_COMPATIBILITY )
. mixinStageConfig ( script . commonPipelineEnvironment , parameters . stageName ? : env . STAGE_NAME , STEP_CONFIG_KEYS , CONFIG_KEY_COMPATIBILITY )
2019-03-04 12:51:43 +02:00
. mixin ( [
2019-03-04 15:40:17 +02:00
style : libraryResource ( 'piper-os.css' )
2019-03-04 12:51:43 +02:00
] )
2019-03-06 16:33:19 +02:00
. mixin ( parameters , PARAMETER_KEYS , CONFIG_KEY_COMPATIBILITY )
2019-02-28 14:01:30 +02:00
. dependingOn ( 'scanType' ) . mixin ( 'buildDescriptorFile' )
. dependingOn ( 'scanType' ) . mixin ( 'dockerImage' )
. dependingOn ( 'scanType' ) . mixin ( 'dockerWorkspace' )
. dependingOn ( 'scanType' ) . mixin ( 'stashContent' )
2019-03-15 14:11:34 +02:00
. dependingOn ( 'scanType' ) . mixin ( 'whitesource/configFilePath' )
2019-04-04 16:05:26 +02:00
. dependingOn ( 'scanType' ) . mixin ( 'whitesource/installCommand' )
2019-03-14 16:14:09 +02:00
. withMandatoryProperty ( 'whitesource/serviceUrl' )
. withMandatoryProperty ( 'whitesource/orgToken' )
. withMandatoryProperty ( 'whitesource/userTokenCredentialsId' )
. withMandatoryProperty ( 'whitesource/productName' )
2019-02-28 14:01:30 +02:00
. use ( )
2019-04-04 16:05:26 +02:00
config . whitesource . cvssSeverityLimit = config . whitesource . cvssSeverityLimit = = null ? : Integer . valueOf ( config . whitesource . cvssSeverityLimit )
2019-02-28 14:01:30 +02:00
config . stashContent = utils . unstashAll ( config . stashContent )
2019-03-14 16:14:09 +02:00
config . whitesource [ 'projectNames' ] = ( config . whitesource [ 'projectNames' ] instanceof List ) ? config . whitesource [ 'projectNames' ] : config . whitesource [ 'projectNames' ] ? . tokenize ( ',' )
parameters . whitesource = parameters . whitesource ? : [ : ]
parameters . whitesource [ 'projectNames' ] = config . whitesource [ 'projectNames' ]
2019-02-28 14:01:30 +02:00
script . commonPipelineEnvironment . setInfluxStepData ( 'whitesource' , false )
utils . pushToSWA ( [
step: STEP_NAME ,
stepParamKey1: 'scanType' ,
stepParam1: config . scanType
] , config )
echo "Parameters: scanType: ${config.scanType}"
def whitesourceRepository = parameters . whitesourceRepositoryStub ? : new WhitesourceRepository ( this , config )
def whitesourceOrgAdminRepository = parameters . whitesourceOrgAdminRepositoryStub ? : new WhitesourceOrgAdminRepository ( this , config )
2019-03-14 16:14:09 +02:00
if ( config . whitesource . orgAdminUserTokenCredentialsId ) {
2019-03-11 11:46:22 +02:00
statusCode = triggerWhitesourceScanWithOrgAdminUserKey ( script , config , utils , descriptorUtils , parameters , whitesourceRepository , whitesourceOrgAdminRepository )
} else {
statusCode = triggerWhitesourceScanWithUserKey ( script , config , utils , descriptorUtils , parameters , whitesourceRepository , whitesourceOrgAdminRepository )
}
2019-02-28 14:01:30 +02:00
checkStatus ( statusCode , config )
script . commonPipelineEnvironment . setInfluxStepData ( 'whitesource' , true )
}
}
2019-03-11 11:46:22 +02:00
private def triggerWhitesourceScanWithOrgAdminUserKey ( script , config , utils , descriptorUtils , parameters , repository , orgAdminRepository ) {
withCredentials ( [ script . string (
2019-03-14 16:14:09 +02:00
credentialsId: config . whitesource . orgAdminUserTokenCredentialsId ,
2019-03-11 11:46:22 +02:00
variable: 'orgAdminUserKey'
) ] ) {
2019-03-14 16:14:09 +02:00
config . whitesource . orgAdminUserKey = orgAdminUserKey
2019-03-11 11:46:22 +02:00
triggerWhitesourceScanWithUserKey ( script , config , utils , descriptorUtils , parameters , repository , orgAdminRepository )
}
}
2019-03-04 16:02:01 +02:00
private def triggerWhitesourceScanWithUserKey ( script , config , utils , descriptorUtils , parameters , repository , orgAdminRepository ) {
2019-02-28 14:01:30 +02:00
withCredentials ( [ string (
2019-03-14 16:14:09 +02:00
credentialsId: config . whitesource . userTokenCredentialsId ,
2019-02-28 14:01:30 +02:00
variable: 'userKey'
) ] ) {
2019-03-14 16:14:09 +02:00
config . whitesource . userKey = userKey
2019-02-28 14:01:30 +02:00
def statusCode = 1
2019-03-14 16:14:09 +02:00
echo "Triggering Whitesource scan on product '${config.whitesource.productName}'${config.whitesource.productToken ? ' with token \'' + config.whitesource.productToken + '\'' : ''} using product admin credentials with ID '${config.whitesource.userTokenCredentialsId}'${config.whitesource.orgAdminUserTokenCredentialsId ? ' and organization admin credentials with ID \'' + config.whitesource.orgAdminUserTokenCredentialsId + '\'' : ''}"
2019-03-11 16:27:06 +02:00
2019-03-14 16:14:09 +02:00
if ( ! config . whitesource . productToken ) {
2019-03-11 16:27:06 +02:00
def metaInfo = orgAdminRepository . fetchProductMetaInfo ( )
def key = "token"
2019-03-15 14:11:34 +02:00
if ( ( null = = metaInfo | | ! metaInfo [ key ] ) & & config . whitesource . createProductFromPipeline ) {
2019-03-11 16:27:06 +02:00
metaInfo = orgAdminRepository . createProduct ( )
key = "productToken"
} else if ( null = = metaInfo | | ! metaInfo [ key ] ) {
2019-03-14 16:14:09 +02:00
error "[WhiteSource] Could not fetch/find requested product '${config.whitesource.productName}' and automatic creation has been disabled"
2019-03-11 16:27:06 +02:00
}
echo "Meta Info: ${metaInfo}"
2019-03-14 16:14:09 +02:00
config . whitesource . productToken = metaInfo [ key ]
2019-03-11 16:27:06 +02:00
}
2019-02-28 14:01:30 +02:00
switch ( config . scanType ) {
case 'mta' :
def scanJobs = [ : ]
def mtaParameters = [ : ] + parameters + [ reporting: false ]
// harmonize buildDescriptorExcludeList
config . buildDescriptorExcludeList = config . buildDescriptorExcludeList instanceof List ? config . buildDescriptorExcludeList : config . buildDescriptorExcludeList ? . replaceAll ( ', ' , ',' ) . replaceAll ( ' ,' , ',' ) . tokenize ( ',' )
// create job for each pom.xml with scanType: 'maven'
scanJobs . putAll ( MtaMultiplexer . createJobs (
this , mtaParameters , config . buildDescriptorExcludeList , 'Whitesource' , 'pom.xml' , 'maven'
) { options - > return whitesourceExecuteScan ( options ) } )
// create job for each pom.xml with scanType: 'maven'
scanJobs . putAll ( MtaMultiplexer . createJobs (
this , mtaParameters , config . buildDescriptorExcludeList , 'Whitesource' , 'package.json' , 'npm'
) { options - > whitesourceExecuteScan ( options ) } )
// create job for each setup.py with scanType: 'pip'
scanJobs . putAll ( MtaMultiplexer . createJobs (
this , mtaParameters , config . buildDescriptorExcludeList , 'Whitesource' , 'setup.py' , 'pip'
) { options - > whitesourceExecuteScan ( options ) } )
// execute scan jobs
2019-03-14 16:14:09 +02:00
if ( config . whitesource . parallelLimit > 0 & & config . whitesource . parallelLimit < scanJobs . keySet ( ) . size ( ) ) {
2019-02-28 14:01:30 +02:00
// block wise
def scanJobsAll = scanJobs
scanJobs = [ failFast: false ]
for ( int i = 1 ; i < = scanJobsAll . keySet ( ) . size ( ) ; i + + ) {
def index = i - 1
def key = scanJobsAll . keySet ( ) [ index ]
scanJobs [ key ] = scanJobsAll [ key ]
2019-03-14 16:14:09 +02:00
if ( i % config . whitesource . parallelLimit = = 0 | | i = = scanJobsAll . keySet ( ) . size ( ) ) {
2019-02-28 14:01:30 +02:00
parallel scanJobs
scanJobs = [ failFast: false ]
}
}
} else {
// in parallel
scanJobs + = [ failFast: false ]
parallel scanJobs
}
statusCode = 0
break
default :
def path = config . buildDescriptorFile . substring ( 0 , config . buildDescriptorFile . lastIndexOf ( '/' ) + 1 )
2019-03-22 14:08:48 +02:00
resolveProjectIdentifiers ( script , descriptorUtils , config )
2019-03-21 14:25:22 +02:00
2019-03-25 10:42:25 +02:00
def projectName = "${config.whitesource.projectName}${config.whitesource.productVersion?' - ':''}${config.whitesource.productVersion?:''}" . toString ( )
2019-03-21 14:25:22 +02:00
if ( ! config . whitesource [ 'projectNames' ] . contains ( projectName ) )
config . whitesource [ 'projectNames' ] . add ( projectName )
2019-02-28 14:01:30 +02:00
WhitesourceConfigurationHelper . extendUAConfigurationFile ( script , utils , config , path )
dockerExecute ( script: script , dockerImage: config . dockerImage , dockerWorkspace: config . dockerWorkspace , stashContent: config . stashContent ) {
2019-03-14 16:14:09 +02:00
if ( config . whitesource . agentDownloadUrl ) {
def agentDownloadUrl = new GStringTemplateEngine ( ) . createTemplate ( config . whitesource . agentDownloadUrl ) . make ( [ config: config ] ) . toString ( )
2019-02-28 14:01:30 +02:00
//if agentDownloadUrl empty, rely on dockerImage to contain unifiedAgent correctly set up and available
2019-03-14 16:14:09 +02:00
sh "curl ${script.env.HTTP_PROXY ? '--proxy ' + script.env.HTTP_PROXY + ' ' : ''}--location --output ${config.whitesource.agentFileName} ${agentDownloadUrl}" . toString ( )
2019-02-28 14:01:30 +02:00
}
def javaCmd = 'java'
2019-03-14 16:14:09 +02:00
if ( config . whitesource . jreDownloadUrl ) {
2019-02-28 14:01:30 +02:00
//if jreDownloadUrl empty, rely on dockerImage to contain java correctly set up and available on the path
2019-03-14 16:14:09 +02:00
sh "curl ${script.env.HTTP_PROXY ? '--proxy ' + script.env.HTTP_PROXY + ' ' : ''}--location --output jvm.tar.gz ${config.whitesource.jreDownloadUrl} && tar --strip-components=1 -xzf jvm.tar.gz" . toString ( )
2019-02-28 14:01:30 +02:00
javaCmd = './bin/java'
}
2019-04-04 16:05:26 +02:00
if ( config . whitesource . installCommand )
sh new GStringTemplateEngine ( ) . createTemplate ( config . whitesource . installCommand ) . make ( [ config: config ] ) . toString ( )
2019-03-26 17:52:57 +02:00
2019-03-15 14:11:34 +02:00
def options = [ "-jar ${config.whitesource.agentFileName} -c \'${config.whitesource.configFilePath}\'" ]
2019-03-14 16:14:09 +02:00
if ( config . whitesource . orgToken ) options . push ( "-apiKey '${config.whitesource.orgToken}'" )
if ( config . whitesource . userKey ) options . push ( "-userKey '${config.whitesource.userKey}'" )
if ( config . whitesource . productName ) options . push ( "-product '${config.whitesource.productName}'" )
2019-02-28 14:01:30 +02:00
2019-03-14 16:14:09 +02:00
statusCode = sh ( script: "${javaCmd} ${options.join(' ')} ${config.whitesource.agentParameters}" , returnStatus: true )
2019-02-28 14:01:30 +02:00
2019-03-14 16:14:09 +02:00
if ( config . whitesource . agentDownloadUrl ) {
sh "rm -f ${config.whitesource.agentFileName}"
2019-03-05 15:34:57 +02:00
}
2019-03-14 16:14:09 +02:00
if ( config . whitesource . jreDownloadUrl ) {
2019-03-05 15:34:57 +02:00
sh "rm -rf ./bin ./conf ./legal ./lib ./man"
2019-03-27 15:10:29 +02:00
sh "rm -f jvm.tar.gz"
2019-03-05 00:09:40 +02:00
}
2019-03-28 00:46:43 +02:00
// archive whitesource result files for UA
2019-02-28 14:01:30 +02:00
archiveArtifacts artifacts: "whitesource/*.*" , allowEmptyArchive: true
2019-03-27 15:11:54 +02:00
// archive whitesource debug files, if available
archiveArtifacts artifacts: "**/ws-l*" , allowEmptyArchive: true
2019-02-28 14:01:30 +02:00
}
break
}
if ( config . reporting ) {
2019-03-11 16:27:06 +02:00
analyseWhitesourceResults ( config , repository )
2019-02-28 14:01:30 +02:00
}
return statusCode
}
}
2019-03-22 14:08:48 +02:00
private resolveProjectIdentifiers ( script , descriptorUtils , config ) {
if ( ! config . whitesource . projectName | | ! config . whitesource . productVersion ) {
def gav
switch ( config . scanType ) {
case 'npm' :
gav = descriptorUtils . getNpmGAV ( config . buildDescriptorFile )
break
case 'sbt' :
gav = descriptorUtils . getSbtGAV ( config . buildDescriptorFile )
break
case 'pip' :
gav = descriptorUtils . getPipGAV ( config . buildDescriptorFile )
break
case 'golang' :
2019-03-25 15:32:36 +02:00
gav = descriptorUtils . getGoGAV ( config . buildDescriptorFile , new URI ( script . commonPipelineEnvironment . getGitHttpsUrl ( ) ) )
2019-03-22 14:08:48 +02:00
break
case 'dlang' :
break
case 'maven' :
gav = descriptorUtils . getMavenGAV ( config . buildDescriptorFile )
break
}
2019-03-22 16:04:19 +02:00
if ( ! config . whitesource . projectName )
2019-03-22 14:08:48 +02:00
config . whitesource . projectName = "${gav.group?:''}${gav.group?'.':''}${gav.artifact}"
2019-03-25 09:41:30 +02:00
def versionFragments = gav . version ? . tokenize ( '.' )
2019-03-25 11:38:00 +02:00
def version = versionFragments . size ( ) > 0 ? versionFragments . head ( ) : null
2019-03-22 16:04:19 +02:00
if ( version & & ! config . whitesource . productVersion )
config . whitesource . productVersion = version
2019-03-22 14:08:48 +02:00
}
}
2019-03-11 16:27:06 +02:00
void analyseWhitesourceResults ( Map config , WhitesourceRepository repository ) {
2019-02-28 14:01:30 +02:00
def pdfName = "whitesource-riskReport.pdf"
2019-03-04 16:45:30 +02:00
repository . fetchReportForProduct ( pdfName )
2019-02-28 14:01:30 +02:00
archiveArtifacts artifacts: pdfName
2019-03-04 16:45:30 +02:00
echo "A summary of the Whitesource findings was stored as artifact under the name ${pdfName}"
2019-02-28 14:01:30 +02:00
2019-03-14 16:14:09 +02:00
if ( config . whitesource . licensingVulnerabilities ) {
2019-03-05 14:59:27 +02:00
def violationCount = fetchViolationCount ( config , repository )
checkViolationStatus ( violationCount )
}
2019-02-28 14:01:30 +02:00
2019-03-14 16:14:09 +02:00
if ( config . whitesource . securityVulnerabilities )
config . whitesource . severeVulnerabilities = checkSecurityViolations ( config , repository )
2019-02-28 14:01:30 +02:00
}
int fetchViolationCount ( Map config , WhitesourceRepository repository ) {
int violationCount = 0
2019-03-14 16:14:09 +02:00
if ( config . whitesource ? . projectNames ) {
2019-02-28 14:01:30 +02:00
def projectsMeta = repository . fetchProjectsMetaInfo ( )
for ( int i = 0 ; i < projectsMeta . size ( ) ; i + + ) {
def project = projectsMeta [ i ]
def responseAlertsProject = repository . fetchProjectLicenseAlerts ( project . token )
violationCount + = responseAlertsProject . alerts . size ( )
}
} else {
def responseAlerts = repository . fetchProductLicenseAlerts ( )
violationCount + = responseAlerts . alerts . size ( )
}
return violationCount
}
void checkViolationStatus ( int violationCount ) {
if ( violationCount = = 0 ) {
2019-03-06 15:03:00 +02:00
echo "[${STEP_NAME}] No policy violations found"
2019-02-28 14:01:30 +02:00
} else {
2019-03-05 14:59:27 +02:00
error "[${STEP_NAME}] Whitesource found ${violationCount} policy violations for your product"
2019-02-28 14:01:30 +02:00
}
}
int checkSecurityViolations ( Map config , WhitesourceRepository repository ) {
2019-03-14 16:14:09 +02:00
def projectsMetaInformation = repository . fetchProjectsMetaInfo ( )
def vulnerabilities = repository . fetchVulnerabilities ( projectsMetaInformation )
2019-02-28 14:01:30 +02:00
def severeVulnerabilities = 0
2019-03-14 16:14:09 +02:00
vulnerabilities . each {
2019-02-28 14:01:30 +02:00
item - >
2019-03-14 16:14:09 +02:00
if ( ( item . vulnerability . score > = config . whitesource . cvssSeverityLimit | | item . vulnerability . cvss3_score > = config . whitesource . cvssSeverityLimit ) & & config . whitesource . cvssSeverityLimit > = 0 )
2019-02-28 14:01:30 +02:00
severeVulnerabilities + +
}
2019-03-21 14:25:22 +02:00
writeFile ( file: "${config.vulnerabilityReportFileName}.json" , text: new JsonUtils ( ) . groovyObjectToPrettyJsonString ( vulnerabilities ) )
2019-03-14 16:14:09 +02:00
writeFile ( file: "${config.vulnerabilityReportFileName}.html" , text: getReportHtml ( config , vulnerabilities , severeVulnerabilities ) )
2019-02-28 14:01:30 +02:00
archiveArtifacts ( artifacts: "${config.vulnerabilityReportFileName}.*" )
2019-03-14 16:14:09 +02:00
if ( vulnerabilities . size ( ) - severeVulnerabilities > 0 )
echo "[${STEP_NAME}] WARNING: ${vulnerabilities.size() - severeVulnerabilities} Open Source Software Security vulnerabilities with CVSS score below ${config.whitesource.cvssSeverityLimit} detected."
if ( vulnerabilities . size ( ) = = 0 )
2019-02-28 14:01:30 +02:00
echo "[${STEP_NAME}] No Open Source Software Security vulnerabilities detected."
return severeVulnerabilities
}
// ExitCodes: https://whitesource.atlassian.net/wiki/spaces/WD/pages/34209870/NPM+Plugin#NPMPlugin-ExitCode
void checkStatus ( int statusCode , config ) {
def errorMessage = ""
2019-03-14 16:14:09 +02:00
if ( config . whitesource . securityVulnerabilities & & config . whitesource . severeVulnerabilities > 0 )
errorMessage + = "${config.whitesource.severeVulnerabilities} Open Source Software Security vulnerabilities with CVSS score greater or equal ${config.whitesource.cvssSeverityLimit} detected. - "
if ( config . whitesource . licensingVulnerabilities )
2019-02-28 14:01:30 +02:00
switch ( statusCode ) {
case 0 :
break
case 255 :
errorMessage + = "The scan resulted in an error"
break
case 254 :
errorMessage + = "Whitesource found one or multiple policy violations"
break
case 253 :
errorMessage + = "The local scan client failed to execute the scan"
break
case 252 :
errorMessage + = "There was a failure in the connection to the WhiteSource servers"
break
case 251 :
errorMessage + = "The server failed to analyze the scan"
break
2019-03-05 14:59:27 +02:00
case 250 :
errorMessage + = "Pre-step failure"
break
2019-02-28 14:01:30 +02:00
default :
errorMessage + = "Whitesource scan failed with unknown error code '${statusCode}'"
}
if ( errorMessage )
error "[${STEP_NAME}] " + errorMessage
}
def getReportHtml ( config , vulnerabilityList , numSevereVulns ) {
2019-04-03 12:31:40 +02:00
def now = new Date ( ) . format ( 'MMM dd, yyyy - HH:mm:ss z' , TimeZone . getTimeZone ( 'UTC' ) )
2019-02-28 14:01:30 +02:00
def vulnerabilityTable = ''
if ( vulnerabilityList . size ( ) = = 0 ) {
vulnerabilityTable + = '' '
< tr >
< td colspan = 12 > No publicly known vulnerabilities detected < / td >
< / tr > '' '
} else {
for ( int i = 0 ; i < vulnerabilityList . size ( ) ; i + + ) {
def item = vulnerabilityList [ i ]
def score = item . vulnerability . cvss3_score > 0 ? item . vulnerability . cvss3_score : item . vulnerability . score
def topFix = item . vulnerability . topFix ? "${item.vulnerability.topFix?.message}<br>${item.vulnerability.topFix?.fixResolution}<br><a href=\"${item.vulnerability.topFix?.url}\">${item.vulnerability.topFix?.url}</a>}" : ''
vulnerabilityTable + = "" "
< tr >
< td > $ { i + 1 } < / td >
< td > $ { item . date } < / td >
< td > < a href = \ "${item.vulnerability.url}\" > $ { item . vulnerability . name } < /a></ td >
2019-03-06 14:10:49 +02:00
< td class = \ "${score < config.cvssSeverityLimit ? 'warn' : 'notok'}\" > $ { score } < / td >
2019-02-28 14:01:30 +02:00
< td > $ { item . vulnerability . cvss3_score > 0 ? 'v3' : 'v2' } < / td >
< td > $ { item . project } < / td >
< td > $ { item . library . filename } < / td >
< td > $ { item . library . groupId } < / td >
< td > $ { item . library . artifactId } < / td >
< td > $ { item . library . version } < / td >
< td > $ { item . vulnerability . description } < / td >
< td > $ { topFix } < / td >
< / tr > "" "
}
}
return SimpleTemplateEngine . newInstance ( ) . createTemplate ( libraryResource ( 'com.sap.piper/templates/whitesourceVulnerabilities.html' ) ) . make (
[
now : now ,
2019-03-14 16:14:09 +02:00
reportTitle : config . whitesource . vulnerabilityReportTitle ,
2019-02-28 14:01:30 +02:00
style : config . style ,
2019-03-15 14:21:56 +02:00
cvssSeverityLimit : config . whitesource . cvssSeverityLimit ,
2019-02-28 14:01:30 +02:00
totalSevereVulnerabilities : numSevereVulns ,
totalVulnerabilities : vulnerabilityList . size ( ) ,
vulnerabilityTable : vulnerabilityTable ,
2019-03-14 16:14:09 +02:00
whitesourceProductName : config . whitesource . productName ,
whitesourceProjectNames : config . whitesource . projectNames
2019-02-28 14:01:30 +02:00
] ) . toString ( )
}