2019-03-04 16:02:01 +02:00
import com.sap.piper.DescriptorUtils
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-05 14:59:27 +02:00
'orgAdminUserTokenCredentialsId' ,
'orgToken' ,
'productName' ,
'productVersion' ,
'productToken' ,
'projectNames' ,
'scanType' ,
'serviceUrl' ,
'internalServiceUrl' ,
'userTokenCredentialsId' ,
'verbose'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS + [
2019-02-28 14:01:30 +02:00
'agentDownloadUrl' ,
'agentFileName' ,
'agentParameters' ,
'artifactUrl' ,
'buildDescriptorExcludeList' ,
'buildDescriptorFile' ,
'configFilePath' ,
'dockerImage' ,
'dockerWorkspace' ,
'jreDownloadUrl' ,
'licensingVulnerabilities' ,
'parallelLimit' ,
'reporting' ,
'securityVulnerabilities' ,
2019-03-05 14:59:27 +02:00
'cvssSeverityLimit' ,
2019-02-28 14:01:30 +02:00
'stashContent' ,
'timeout' ,
'vulnerabilityReportFileName' ,
2019-03-06 15:03:00 +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 = [
whitesource : [
'orgAdminUserTokenCredentialsId' : 'orgAdminUserTokenCredentialsId' ,
'orgToken' : 'orgToken' ,
'productName' : 'productName' ,
'productVersion' : 'productVersion' ,
'productToken' : 'productToken' ,
'projectNames' : 'projectNames' ,
'scanType' : 'scanType' ,
'serviceUrl' : 'serviceUrl' ,
'userTokenCredentialsId' : 'userTokenCredentialsId'
] ,
whitesourceUserTokenCredentialsId : 'userTokenCredentialsId' ,
whitesourceProductName : 'productName' ,
whitesourceProjectNames : 'projectNames' ,
whitesourceProductToken : 'productToken'
]
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 )
. loadStepDefaults ( )
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-02-28 14:01:30 +02:00
. mixin ( parameters , PARAMETER_KEYS )
. dependingOn ( 'scanType' ) . mixin ( 'buildDescriptorFile' )
. dependingOn ( 'scanType' ) . mixin ( 'configFilePath' )
. dependingOn ( 'scanType' ) . mixin ( 'dockerImage' )
. dependingOn ( 'scanType' ) . mixin ( 'dockerWorkspace' )
. dependingOn ( 'scanType' ) . mixin ( 'stashContent' )
2019-03-05 15:40:36 +02:00
. withMandatoryProperty ( 'orgToken' )
2019-02-28 14:01:30 +02:00
. withMandatoryProperty ( 'userTokenCredentialsId' )
. withMandatoryProperty ( 'productName' )
. use ( )
2019-03-06 15:03:00 +02:00
config . cvssSeverityLimit = config . cvssSeverityLimit = = null ? - 1 : Integer . valueOf ( config . cvssSeverityLimit )
2019-02-28 14:01:30 +02:00
config . stashContent = utils . unstashAll ( config . stashContent )
config . projectNames = ( config . projectNames instanceof List ) ? config . projectNames : config . projectNames ? . tokenize ( ',' )
parameters . projectNames = config . projectNames
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-04 16:02:01 +02:00
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-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 (
credentialsId: config . userTokenCredentialsId ,
variable: 'userKey'
) ] ) {
config . userKey = userKey
def statusCode = 1
echo "Triggering Whitesource scan on product '${config.productName}' with token '${config.productToken}' using credentials with ID '${config.userTokenCredentialsId}'"
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
if ( config . parallelLimit > 0 & & config . parallelLimit < scanJobs . keySet ( ) . size ( ) ) {
// 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 ]
if ( i % config . parallelLimit = = 0 | | i = = scanJobsAll . keySet ( ) . size ( ) ) {
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 )
def gav
switch ( config . scanType ) {
case 'npm' :
2019-03-04 16:02:01 +02:00
gav = descriptorUtils . getNpmGAV ( config . buildDescriptorFile )
2019-02-28 14:01:30 +02:00
config . projectName = gav . group + "." + gav . artifact
config . productVersion = gav . version
break
case 'sbt' :
2019-03-04 16:02:01 +02:00
gav = descriptorUtils . getSbtGAV ( config . buildDescriptorFile )
2019-02-28 14:01:30 +02:00
config . projectName = gav . group + "." + gav . artifact
config . productVersion = gav . version
break
case 'pip' :
2019-03-04 16:02:01 +02:00
gav = descriptorUtils . getPipGAV ( config . buildDescriptorFile )
2019-02-28 14:01:30 +02:00
config . projectName = gav . artifact
config . productVersion = gav . version
break
default :
2019-03-04 16:02:01 +02:00
gav = descriptorUtils . getMavenGAV ( config . buildDescriptorFile )
2019-02-28 14:01:30 +02:00
config . projectName = gav . group + "." + gav . artifact
config . productVersion = gav . version
break
}
config . projectNames . add ( "${config.projectName} - ${config.productVersion}" . toString ( ) )
WhitesourceConfigurationHelper . extendUAConfigurationFile ( script , utils , config , path )
dockerExecute ( script: script , dockerImage: config . dockerImage , dockerWorkspace: config . dockerWorkspace , stashContent: config . stashContent ) {
if ( config . agentDownloadUrl ) {
def agentDownloadUrl = new GStringTemplateEngine ( ) . createTemplate ( config . agentDownloadUrl ) . make ( [ config: config ] ) . toString ( )
//if agentDownloadUrl empty, rely on dockerImage to contain unifiedAgent correctly set up and available
sh "curl ${script.env.HTTP_PROXY ? '--proxy ' + script.env.HTTP_PROXY + ' ' : ''}--location --output ${config.agentFileName} ${agentDownloadUrl}" . toString ( )
}
def javaCmd = 'java'
if ( config . jreDownloadUrl ) {
//if jreDownloadUrl empty, rely on dockerImage to contain java correctly set up and available on the path
sh "curl ${script.env.HTTP_PROXY ? '--proxy ' + script.env.HTTP_PROXY + ' ' : ''}--location --output jvm.tar.gz ${config.jreDownloadUrl} && tar --strip-components=1 -xzf jvm.tar.gz" . toString ( )
javaCmd = './bin/java'
}
def options = [ "-jar ${config.agentFileName} -c \'${config.configFilePath}\'" ]
if ( config . orgToken ) options . push ( "-apiKey '${config.orgToken}'" )
if ( config . userKey ) options . push ( "-userKey '${config.userKey}'" )
if ( config . productName ) options . push ( "-product '${config.productName}'" )
statusCode = sh ( script: "${javaCmd} ${options.join(' ')} ${config.agentParameters}" , returnStatus: true )
2019-03-05 15:34:57 +02:00
if ( config . agentDownloadUrl ) {
sh "rm -f ${config.agentFileName}"
}
2019-03-05 00:09:40 +02:00
if ( config . jreDownloadUrl ) {
2019-03-05 15:34:57 +02:00
sh "rm -rf ./bin ./conf ./legal ./lib ./man"
2019-03-05 00:09:40 +02:00
}
2019-02-28 14:01:30 +02:00
// archive whitesource result files
archiveArtifacts artifacts: "whitesource/*.*" , allowEmptyArchive: true
}
break
}
if ( config . reporting ) {
2019-03-04 16:47:19 +02:00
analyseWhitesourceResults ( config , repository , orgAdminRepository )
2019-02-28 14:01:30 +02:00
}
return statusCode
}
}
2019-03-04 16:47:19 +02:00
void analyseWhitesourceResults ( Map config , WhitesourceRepository repository , WhitesourceOrgAdminRepository orgAdminRepository ) {
2019-02-28 14:01:30 +02:00
if ( ! config . productToken ) {
def metaInfo = orgAdminRepository . fetchProductMetaInfo ( )
2019-03-06 14:12:23 +02:00
def key = "token"
if ( ! metaInfo & & config . createProductFromPipeline ) {
metaInfo = orgAdminRepository . createProduct ( )
key = "productToken"
} else if ( ! metaInfo ) {
error "[WhiteSource] Could not fetch/find requested product '${config.productName}' and automatic creation has been disabled"
}
echo "Meta Info: ${metaInfo}"
config . productToken = metaInfo [ key ]
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-05 14:59:27 +02:00
if ( config . licensingVulnerabilities ) {
def violationCount = fetchViolationCount ( config , repository )
checkViolationStatus ( violationCount )
}
2019-02-28 14:01:30 +02:00
if ( config . securityVulnerabilities )
config . severeVulnerabilities = checkSecurityViolations ( config , repository )
}
int fetchViolationCount ( Map config , WhitesourceRepository repository ) {
int violationCount = 0
if ( config . projectNames ) {
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 ) {
def whitesourceProjectsMetaInformation = repository . fetchProjectsMetaInfo ( )
def whitesourceVulnerabilities = repository . fetchVulnerabilities ( whitesourceProjectsMetaInformation )
def severeVulnerabilities = 0
whitesourceVulnerabilities . each {
item - >
2019-03-05 14:59:27 +02:00
if ( ( item . vulnerability . score > = config . cvssSeverityLimit | | item . vulnerability . cvss3_score > = config . cvssSeverityLimit ) & & config . cvssSeverityLimit > = 0 )
2019-02-28 14:01:30 +02:00
severeVulnerabilities + +
}
writeFile ( file: "${config.vulnerabilityReportFileName}.json" , text: new JsonUtils ( ) . getPrettyJsonString ( whitesourceVulnerabilities ) )
writeFile ( file: "${config.vulnerabilityReportFileName}.html" , text: getReportHtml ( config , whitesourceVulnerabilities , severeVulnerabilities ) )
archiveArtifacts ( artifacts: "${config.vulnerabilityReportFileName}.*" )
if ( whitesourceVulnerabilities . size ( ) - severeVulnerabilities > 0 )
2019-03-06 14:10:31 +02:00
echo "[${STEP_NAME}] WARNING: ${whitesourceVulnerabilities.size() - severeVulnerabilities} Open Source Software Security vulnerabilities with CVSS score below ${config.cvssSeverityLimit} detected."
2019-02-28 14:01:30 +02:00
if ( whitesourceVulnerabilities . size ( ) = = 0 )
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 = ""
if ( config . securityVulnerabilities & & config . severeVulnerabilities > 0 )
2019-03-06 14:11:05 +02:00
errorMessage + = "${config.severeVulnerabilities} Open Source Software Security vulnerabilities with CVSS score greater or equal ${config.cvssSeverityLimit} detected. - "
2019-02-28 14:01:30 +02:00
if ( config . licensingVulnerabilities )
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 ) {
def now = new Date ( ) . format ( 'MMM dd, yyyy - HH:mm:ss' )
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 ,
reportTitle : config . vulnerabilityReportTitle ,
style : config . style ,
totalSevereVulnerabilities : numSevereVulns ,
totalVulnerabilities : vulnerabilityList . size ( ) ,
vulnerabilityTable : vulnerabilityTable ,
2019-03-04 12:51:43 +02:00
whitesourceProductName : config . productName ,
whitesourceProjectNames : config . projectNames
2019-02-28 14:01:30 +02:00
] ) . toString ( )
}