2019-12-13 11:55:45 +02:00
package cmd
import (
2022-08-02 08:26:26 +02:00
"context"
2021-07-23 09:36:16 +02:00
"encoding/json"
2019-12-13 11:55:45 +02:00
"fmt"
2020-11-03 12:08:23 +02:00
"io"
"net/http"
2021-07-23 09:36:16 +02:00
"path/filepath"
2022-02-08 12:55:01 +02:00
"regexp"
2021-08-12 15:58:33 +02:00
"sort"
2021-09-07 17:52:55 +02:00
"strconv"
2020-10-01 13:34:51 +02:00
"strings"
2021-08-12 15:58:33 +02:00
"time"
2020-10-01 13:34:51 +02:00
2021-08-12 15:58:33 +02:00
bd "github.com/SAP/jenkins-library/pkg/blackduck"
2022-03-17 16:32:48 +02:00
piperGithub "github.com/SAP/jenkins-library/pkg/github"
2020-07-30 10:35:46 +02:00
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/maven"
2022-03-17 16:32:48 +02:00
"github.com/SAP/jenkins-library/pkg/reporting"
"github.com/SAP/jenkins-library/pkg/versioning"
2021-08-12 15:58:33 +02:00
"github.com/pkg/errors"
2019-12-13 11:55:45 +02:00
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log"
2020-07-30 10:35:46 +02:00
"github.com/SAP/jenkins-library/pkg/piperutils"
2020-02-04 11:46:43 +02:00
"github.com/SAP/jenkins-library/pkg/telemetry"
2021-07-23 08:48:48 +02:00
"github.com/SAP/jenkins-library/pkg/toolrecord"
2022-08-02 08:26:26 +02:00
"github.com/google/go-github/v45/github"
2019-12-13 11:55:45 +02:00
)
2020-11-03 12:08:23 +02:00
type detectUtils interface {
2022-03-23 11:02:00 +02:00
piperutils . FileUtils
2020-11-03 12:08:23 +02:00
2021-09-07 17:52:55 +02:00
GetExitCode ( ) int
2022-02-08 12:55:01 +02:00
GetOsEnv ( ) [ ] string
2020-11-03 12:08:23 +02:00
Stdout ( out io . Writer )
Stderr ( err io . Writer )
SetDir ( dir string )
SetEnv ( env [ ] string )
RunExecutable ( e string , p ... string ) error
RunShell ( shell , script string ) error
DownloadFile ( url , filename string , header http . Header , cookies [ ] * http . Cookie ) error
2022-03-17 16:32:48 +02:00
2022-08-02 08:26:26 +02:00
GetIssueService ( ) * github . IssuesService
GetSearchService ( ) * github . SearchService
2020-10-27 15:29:22 +02:00
}
2020-11-03 12:08:23 +02:00
type detectUtilsBundle struct {
* command . Command
* piperutils . Files
* piperhttp . Client
2022-08-02 08:26:26 +02:00
issues * github . IssuesService
search * github . SearchService
}
func ( d * detectUtilsBundle ) GetIssueService ( ) * github . IssuesService {
return d . issues
2020-11-03 12:08:23 +02:00
}
2022-08-02 08:26:26 +02:00
func ( d * detectUtilsBundle ) GetSearchService ( ) * github . SearchService {
return d . search
2022-03-17 16:32:48 +02:00
}
2021-08-12 15:58:33 +02:00
type blackduckSystem struct {
Client bd . Client
}
2022-08-02 08:26:26 +02:00
func newDetectUtils ( client * github . Client ) detectUtils {
2020-11-03 12:08:23 +02:00
utils := detectUtilsBundle {
Command : & command . Command {
ErrorCategoryMapping : map [ string ] [ ] string {
log . ErrorCompliance . String ( ) : {
"FAILURE_POLICY_VIOLATION - Detect found policy violations." ,
} ,
log . ErrorConfiguration . String ( ) : {
"FAILURE_CONFIGURATION - Detect was unable to start due to issues with it's configuration." ,
2021-09-07 17:52:55 +02:00
"FAILURE_DETECTOR - Detect had one or more detector failures while extracting dependencies. Check that all projects build and your environment is configured correctly." ,
"FAILURE_SCAN - Detect was unable to run the signature scanner against your source. Check your configuration." ,
} ,
log . ErrorInfrastructure . String ( ) : {
"FAILURE_PROXY_CONNECTIVITY - Detect was unable to use the configured proxy. Check your configuration and connection." ,
"FAILURE_BLACKDUCK_CONNECTIVITY - Detect was unable to connect to Black Duck. Check your configuration and connection." ,
"FAILURE_POLARIS_CONNECTIVITY - Detect was unable to connect to Polaris. Check your configuration and connection." ,
} ,
log . ErrorService . String ( ) : {
"FAILURE_TIMEOUT - Detect could not wait for actions to be completed on Black Duck. Check your Black Duck server or increase your timeout." ,
"FAILURE_DETECTOR_REQUIRED - Detect did not run all of the required detectors. Fix detector issues or disable required detectors." ,
"FAILURE_BLACKDUCK_VERSION_NOT_SUPPORTED - Detect attempted an operation that was not supported by your version of Black Duck. Ensure your Black Duck is compatible with this version of detect." ,
"FAILURE_BLACKDUCK_FEATURE_ERROR - Detect encountered an error while attempting an operation on Black Duck. Ensure your Black Duck is compatible with this version of detect." ,
"FAILURE_GENERAL_ERROR - Detect encountered a known error, details of the error are provided." ,
"FAILURE_UNKNOWN_ERROR - Detect encountered an unknown error." ,
2020-11-03 12:08:23 +02:00
} ,
2020-10-06 14:22:35 +02:00
} ,
2020-10-06 11:55:05 +02:00
} ,
2020-11-03 12:08:23 +02:00
Files : & piperutils . Files { } ,
Client : & piperhttp . Client { } ,
2020-10-06 11:55:05 +02:00
}
2022-08-02 08:26:26 +02:00
if client != nil {
utils . issues = client . Issues
utils . search = client . Search
}
2020-11-03 12:08:23 +02:00
utils . Stdout ( log . Writer ( ) )
utils . Stderr ( log . Writer ( ) )
return & utils
}
2019-12-13 11:55:45 +02:00
2021-08-12 15:58:33 +02:00
func newBlackduckSystem ( config detectExecuteScanOptions ) * blackduckSystem {
sys := blackduckSystem {
Client : bd . NewClient ( config . Token , config . ServerURL , & piperhttp . Client { } ) ,
}
return & sys
}
func detectExecuteScan ( config detectExecuteScanOptions , _ * telemetry . CustomData , influx * detectExecuteScanInflux ) {
influx . step_data . fields . detect = false
2019-12-13 11:55:45 +02:00
2022-08-02 08:26:26 +02:00
ctx , client , err := piperGithub . NewClient ( config . GithubToken , config . GithubAPIURL , "" , config . CustomTLSCertificateLinks )
2019-12-13 11:55:45 +02:00
if err != nil {
2022-08-02 08:26:26 +02:00
log . Entry ( ) . WithError ( err ) . Warning ( "Failed to get GitHub client" )
}
utils := newDetectUtils ( client )
if err := runDetect ( ctx , config , utils , influx ) ; err != nil {
2019-12-13 11:55:45 +02:00
log . Entry ( ) .
WithError ( err ) .
Fatal ( "failed to execute detect scan" )
}
2021-07-23 08:48:48 +02:00
2021-08-12 15:58:33 +02:00
influx . step_data . fields . detect = true
2019-12-13 11:55:45 +02:00
}
2022-08-02 08:26:26 +02:00
func runDetect ( ctx context . Context , config detectExecuteScanOptions , utils detectUtils , influx * detectExecuteScanInflux ) error {
2020-07-30 10:35:46 +02:00
// detect execution details, see https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/88440888/Sample+Synopsys+Detect+Scan+Configuration+Scenarios+for+Black+Duck
2021-01-29 11:17:02 +02:00
err := getDetectScript ( config , utils )
2020-10-27 15:29:22 +02:00
if err != nil {
return fmt . Errorf ( "failed to download 'detect.sh' script: %w" , err )
}
defer func ( ) {
2020-11-03 12:08:23 +02:00
err := utils . FileRemove ( "detect.sh" )
2020-10-27 15:29:22 +02:00
if err != nil {
log . Entry ( ) . Warnf ( "failed to delete 'detect.sh' script: %v" , err )
}
} ( )
2022-08-02 08:26:26 +02:00
err = utils . Chmod ( "detect.sh" , 0 o700 )
2020-07-30 10:35:46 +02:00
if err != nil {
return err
}
2020-11-03 12:08:23 +02:00
if config . InstallArtifacts {
2020-11-10 18:14:55 +02:00
err := maven . InstallMavenArtifacts ( & maven . EvaluateOptions {
2020-11-03 12:08:23 +02:00
M2Path : config . M2Path ,
ProjectSettingsFile : config . ProjectSettingsFile ,
GlobalSettingsFile : config . GlobalSettingsFile ,
2020-11-10 18:14:55 +02:00
} , utils )
2020-11-03 12:08:23 +02:00
if err != nil {
return err
}
}
2020-07-30 10:35:46 +02:00
args := [ ] string { "./detect.sh" }
2020-11-03 12:08:23 +02:00
args , err = addDetectArgs ( args , config , utils )
2020-07-30 10:35:46 +02:00
if err != nil {
return err
}
script := strings . Join ( args , " " )
envs := [ ] string { "BLACKDUCK_SKIP_PHONE_HOME=true" }
2021-04-01 11:24:25 +02:00
envs = append ( envs , config . CustomEnvironmentVariables ... )
2020-07-30 10:35:46 +02:00
2020-11-03 12:08:23 +02:00
utils . SetDir ( "." )
utils . SetEnv ( envs )
2020-07-30 10:35:46 +02:00
2021-07-23 09:36:16 +02:00
err = utils . RunShell ( "/bin/bash" , script )
2021-09-20 11:28:16 +02:00
blackduckSystem := newBlackduckSystem ( config )
2022-08-02 08:26:26 +02:00
reportingErr := postScanChecksAndReporting ( ctx , config , influx , utils , blackduckSystem )
2021-08-12 15:58:33 +02:00
if reportingErr != nil {
2021-09-20 11:28:16 +02:00
if strings . Contains ( reportingErr . Error ( ) , "License Policy Violations found" ) {
log . Entry ( ) . Errorf ( "License Policy Violations found" )
log . SetErrorCategory ( log . ErrorCompliance )
2022-02-07 16:29:32 +02:00
if err == nil && ! piperutils . ContainsStringPart ( config . FailOn , "NONE" ) {
2021-09-20 11:28:16 +02:00
err = errors . New ( "License Policy Violations found" )
}
} else {
log . Entry ( ) . Warnf ( "Failed to generate reports: %v" , reportingErr )
2021-09-07 17:52:55 +02:00
}
2021-09-20 11:28:16 +02:00
}
if err != nil {
2021-09-07 17:52:55 +02:00
// Setting error category based on exit code
mapErrorCategory ( utils . GetExitCode ( ) )
2022-07-12 11:43:24 +02:00
if log . GetErrorCategory ( ) == log . ErrorCompliance && ! config . FailOnSevereVulnerabilities {
err = nil
log . Entry ( ) . Infof ( "policy violation(s) found - step will only create data but not fail due to setting failOnSevereVulnerabilities: false" )
} else {
// Error code mapping with more human readable text
err = errors . Wrapf ( err , exitCodeMapping ( utils . GetExitCode ( ) ) )
}
2021-09-20 11:28:16 +02:00
}
// create Toolrecord file
2022-08-09 10:57:02 +02:00
toolRecordFileName , toolRecordErr := createToolRecordDetect ( utils , "./" , config , blackduckSystem )
2021-09-20 11:28:16 +02:00
if toolRecordErr != nil {
// do not fail until the framework is well established
log . Entry ( ) . Warning ( "TR_DETECT: Failed to create toolrecord file " + toolRecordFileName , err )
2021-09-07 17:52:55 +02:00
}
2021-07-23 09:36:16 +02:00
return err
2020-07-30 10:35:46 +02:00
}
2021-09-07 17:52:55 +02:00
// Get proper error category
func mapErrorCategory ( exitCodeKey int ) {
switch exitCodeKey {
2021-09-21 22:36:12 +02:00
case 0 :
2022-08-02 08:26:26 +02:00
// In case detect exits successfully, we rely on the function 'postScanChecksAndReporting' to determine the error category
// hence this method doesnt need to set an error category or go to 'default' case
2021-09-21 22:36:12 +02:00
break
2021-09-07 17:52:55 +02:00
case 1 :
log . SetErrorCategory ( log . ErrorInfrastructure )
case 2 :
log . SetErrorCategory ( log . ErrorService )
case 3 :
log . SetErrorCategory ( log . ErrorCompliance )
case 4 :
log . SetErrorCategory ( log . ErrorInfrastructure )
case 5 :
log . SetErrorCategory ( log . ErrorConfiguration )
case 6 :
log . SetErrorCategory ( log . ErrorConfiguration )
case 7 :
log . SetErrorCategory ( log . ErrorConfiguration )
case 9 :
log . SetErrorCategory ( log . ErrorService )
case 10 :
log . SetErrorCategory ( log . ErrorService )
case 11 :
log . SetErrorCategory ( log . ErrorService )
case 12 :
log . SetErrorCategory ( log . ErrorInfrastructure )
case 99 :
log . SetErrorCategory ( log . ErrorService )
case 100 :
log . SetErrorCategory ( log . ErrorUndefined )
default :
log . SetErrorCategory ( log . ErrorUndefined )
}
}
// Exit codes/error code mapping
func exitCodeMapping ( exitCodeKey int ) string {
exitCodes := map [ int ] string {
2021-09-20 11:28:16 +02:00
0 : "Detect Scan completed successfully" ,
2021-09-07 17:52:55 +02:00
1 : "FAILURE_BLACKDUCK_CONNECTIVITY => Detect was unable to connect to Black Duck. Check your configuration and connection." ,
2 : "FAILURE_TIMEOUT => Detect could not wait for actions to be completed on Black Duck. Check your Black Duck server or increase your timeout." ,
3 : "FAILURE_POLICY_VIOLATION => Detect found policy violations." ,
4 : "FAILURE_PROXY_CONNECTIVITY => Detect was unable to use the configured proxy. Check your configuration and connection." ,
5 : "FAILURE_DETECTOR => Detect had one or more detector failures while extracting dependencies. Check that all projects build and your environment is configured correctly." ,
6 : "FAILURE_SCAN => Detect was unable to run the signature scanner against your source. Check your configuration." ,
7 : "FAILURE_CONFIGURATION => Detect was unable to start because of a configuration issue. Check and fix your configuration." ,
9 : "FAILURE_DETECTOR_REQUIRED => Detect did not run all of the required detectors. Fix detector issues or disable required detectors." ,
10 : "FAILURE_BLACKDUCK_VERSION_NOT_SUPPORTED => Detect attempted an operation that was not supported by your version of Black Duck. Ensure your Black Duck is compatible with this version of detect." ,
11 : "FAILURE_BLACKDUCK_FEATURE_ERROR => Detect encountered an error while attempting an operation on Black Duck. Ensure your Black Duck is compatible with this version of detect." ,
12 : "FAILURE_POLARIS_CONNECTIVITY => Detect was unable to connect to Polaris. Check your configuration and connection." ,
99 : "FAILURE_GENERAL_ERROR => Detect encountered a known error, details of the error are provided." ,
100 : "FAILURE_UNKNOWN_ERROR => Detect encountered an unknown error." ,
}
if _ , isKeyExists := exitCodes [ exitCodeKey ] ; isKeyExists {
return exitCodes [ exitCodeKey ]
}
return "[" + strconv . Itoa ( exitCodeKey ) + "]: Not known exit code key"
}
2021-01-29 11:17:02 +02:00
func getDetectScript ( config detectExecuteScanOptions , utils detectUtils ) error {
if config . ScanOnChanges {
2022-02-08 12:55:01 +02:00
log . Entry ( ) . Infof ( "Using Detect Rescan script" )
2021-01-29 11:17:02 +02:00
return utils . DownloadFile ( "https://raw.githubusercontent.com/blackducksoftware/detect_rescan/master/detect_rescan.sh" , "detect.sh" , nil , nil )
}
2022-02-08 12:55:01 +02:00
env := utils . GetOsEnv ( )
env = append ( env , config . CustomEnvironmentVariables ... )
if piperutils . ContainsStringPart ( env , "DETECT_LATEST_RELEASE_VERSION" ) {
releaseVersion := ""
for _ , i := range env {
if strings . Contains ( i , "DETECT_LATEST_RELEASE_VERSION" ) {
releaseVersion = strings . Split ( i , "=" ) [ 1 ]
}
}
log . Entry ( ) . Infof ( "Using detect script Version %v " , releaseVersion )
detect6 , _ := regexp . MatchString ( "6\\.\\d\\.\\d" , releaseVersion )
if detect6 {
log . Entry ( ) . Infof ( "Downloading Detect 6.x" )
return utils . DownloadFile ( "https://detect.synopsys.com/detect.sh" , "detect.sh" , nil , nil )
}
}
log . Entry ( ) . Infof ( "Downloading Detect7" )
return utils . DownloadFile ( "https://detect.synopsys.com/detect7.sh" , "detect.sh" , nil , nil )
2021-01-29 11:17:02 +02:00
}
2020-11-03 12:08:23 +02:00
func addDetectArgs ( args [ ] string , config detectExecuteScanOptions , utils detectUtils ) ( [ ] string , error ) {
2021-08-12 15:58:33 +02:00
detectVersionName := getVersionName ( config )
2022-08-02 08:26:26 +02:00
// Split on spaces, the scanPropeties, so that each property is available as a single string
// instead of all properties being part of a single string
2021-03-26 14:06:13 +02:00
config . ScanProperties = piperutils . SplitAndTrim ( config . ScanProperties , " " )
2021-01-29 11:17:02 +02:00
if config . ScanOnChanges {
args = append ( args , "--report" )
2021-03-26 14:06:13 +02:00
config . Unmap = false
}
2022-09-26 14:08:12 +02:00
if config . MinScanInterval > 0 {
//Unmap doesnt work well with min-scan-interval and should be removed
config . Unmap = false
args = append ( args , fmt . Sprintf ( "--detect.blackduck.signature.scanner.arguments='--min-scan-interval=%d'" , config . MinScanInterval ) )
}
2021-03-26 14:06:13 +02:00
if config . Unmap {
2021-03-31 10:53:49 +02:00
if ! piperutils . ContainsString ( config . ScanProperties , "--detect.project.codelocation.unmap=true" ) {
2022-07-21 09:04:21 +02:00
args = append ( args , "--detect.project.codelocation.unmap=true" )
2021-03-31 10:53:49 +02:00
}
2021-03-26 14:06:13 +02:00
config . ScanProperties , _ = piperutils . RemoveAll ( config . ScanProperties , "--detect.project.codelocation.unmap=false" )
} else {
2022-08-02 08:26:26 +02:00
// When unmap is set to false, any occurances of unmap=true from scanProperties must be removed
2021-03-26 14:06:13 +02:00
config . ScanProperties , _ = piperutils . RemoveAll ( config . ScanProperties , "--detect.project.codelocation.unmap=true" )
2021-01-29 11:17:02 +02:00
}
2020-02-04 11:46:43 +02:00
args = append ( args , config . ScanProperties ... )
2019-12-13 11:55:45 +02:00
2020-02-04 11:46:43 +02:00
args = append ( args , fmt . Sprintf ( "--blackduck.url=%v" , config . ServerURL ) )
2020-10-01 13:34:51 +02:00
args = append ( args , fmt . Sprintf ( "--blackduck.api.token=%v" , config . Token ) )
2020-07-28 10:48:19 +02:00
// ProjectNames, VersionName, GroupName etc can contain spaces and need to be escaped using double quotes in CLI
// Hence the string need to be surrounded by \"
2021-02-02 16:43:12 +02:00
args = append ( args , fmt . Sprintf ( "\"--detect.project.name='%v'\"" , config . ProjectName ) )
args = append ( args , fmt . Sprintf ( "\"--detect.project.version.name='%v'\"" , detectVersionName ) )
2020-07-28 10:48:19 +02:00
// Groups parameter is added only when there is atleast one non-empty groupname provided
if len ( config . Groups ) > 0 && len ( config . Groups [ 0 ] ) > 0 {
2021-02-02 16:43:12 +02:00
args = append ( args , fmt . Sprintf ( "\"--detect.project.user.groups='%v'\"" , strings . Join ( config . Groups , "," ) ) )
2020-07-28 10:48:19 +02:00
}
// Atleast 1, non-empty category to fail on must be provided
if len ( config . FailOn ) > 0 && len ( config . FailOn [ 0 ] ) > 0 {
args = append ( args , fmt . Sprintf ( "--detect.policy.check.fail.on.severities=%v" , strings . Join ( config . FailOn , "," ) ) )
}
2019-12-13 11:55:45 +02:00
2021-04-08 11:04:49 +02:00
codelocation := config . CodeLocation
if len ( codelocation ) == 0 && len ( config . ProjectName ) > 0 {
codelocation = fmt . Sprintf ( "%v/%v" , config . ProjectName , detectVersionName )
2019-12-13 11:55:45 +02:00
}
2021-04-08 11:04:49 +02:00
args = append ( args , fmt . Sprintf ( "\"--detect.code.location.name='%v'\"" , codelocation ) )
2019-12-13 11:55:45 +02:00
2021-01-21 15:57:00 +02:00
if len ( config . ScanPaths ) > 0 && len ( config . ScanPaths [ 0 ] ) > 0 {
2020-02-04 11:46:43 +02:00
args = append ( args , fmt . Sprintf ( "--detect.blackduck.signature.scanner.paths=%v" , strings . Join ( config . ScanPaths , "," ) ) )
2019-12-13 11:55:45 +02:00
}
2021-01-21 15:57:00 +02:00
if len ( config . DependencyPath ) > 0 {
args = append ( args , fmt . Sprintf ( "--detect.source.path=%v" , config . DependencyPath ) )
} else {
2022-07-21 09:04:21 +02:00
args = append ( args , "--detect.source.path='.'" )
2021-01-21 15:57:00 +02:00
}
if len ( config . IncludedPackageManagers ) > 0 {
args = append ( args , fmt . Sprintf ( "--detect.included.detector.types=%v" , strings . ToUpper ( strings . Join ( config . IncludedPackageManagers , "," ) ) ) )
}
if len ( config . ExcludedPackageManagers ) > 0 {
args = append ( args , fmt . Sprintf ( "--detect.excluded.detector.types=%v" , strings . ToUpper ( strings . Join ( config . ExcludedPackageManagers , "," ) ) ) )
}
if len ( config . MavenExcludedScopes ) > 0 {
args = append ( args , fmt . Sprintf ( "--detect.maven.excluded.scopes=%v" , strings . ToLower ( strings . Join ( config . MavenExcludedScopes , "," ) ) ) )
}
if len ( config . DetectTools ) > 0 {
args = append ( args , fmt . Sprintf ( "--detect.tools=%v" , strings . Join ( config . DetectTools , "," ) ) )
2019-12-13 11:55:45 +02:00
}
2020-07-30 10:35:46 +02:00
2020-11-10 18:14:55 +02:00
mavenArgs , err := maven . DownloadAndGetMavenParameters ( config . GlobalSettingsFile , config . ProjectSettingsFile , utils )
2020-07-30 10:35:46 +02:00
if err != nil {
return nil , err
}
if len ( config . M2Path ) > 0 {
2020-11-03 12:08:23 +02:00
absolutePath , err := utils . Abs ( config . M2Path )
2020-07-30 10:35:46 +02:00
if err != nil {
return nil , err
}
mavenArgs = append ( mavenArgs , fmt . Sprintf ( "-Dmaven.repo.local=%v" , absolutePath ) )
}
if len ( mavenArgs ) > 0 {
args = append ( args , fmt . Sprintf ( "\"--detect.maven.build.command='%v'\"" , strings . Join ( mavenArgs , " " ) ) )
}
return args , nil
2019-12-13 11:55:45 +02:00
}
2021-07-23 08:48:48 +02:00
2021-08-12 15:58:33 +02:00
func getVersionName ( config detectExecuteScanOptions ) string {
detectVersionName := config . CustomScanVersion
if len ( detectVersionName ) > 0 {
log . Entry ( ) . Infof ( "Using custom version: %v" , detectVersionName )
} else {
detectVersionName = versioning . ApplyVersioningModel ( config . VersioningModel , config . Version )
}
return detectVersionName
}
2021-09-07 17:17:03 +02:00
func createVulnerabilityReport ( config detectExecuteScanOptions , vulns * bd . Vulnerabilities , influx * detectExecuteScanInflux , sys * blackduckSystem ) reporting . ScanReport {
versionName := getVersionName ( config )
versionUrl , _ := sys . Client . GetProjectVersionLink ( config . ProjectName , versionName )
2021-08-12 15:58:33 +02:00
scanReport := reporting . ScanReport {
2022-03-17 16:32:48 +02:00
ReportTitle : "BlackDuck Security Vulnerability Report" ,
2021-08-12 15:58:33 +02:00
Subheaders : [ ] reporting . Subheader {
{ Description : "BlackDuck Project Name " , Details : config . ProjectName } ,
2021-09-07 17:17:03 +02:00
{ Description : "BlackDuck Project Version " , Details : fmt . Sprintf ( "<a href='%v'>%v</a>" , versionUrl , versionName ) } ,
2021-08-12 15:58:33 +02:00
} ,
Overview : [ ] reporting . OverviewRow {
{ Description : "Total number of vulnerabilities " , Details : fmt . Sprint ( influx . detect_data . fields . vulnerabilities ) } ,
{ Description : "Total number of Critical/High vulnerabilties " , Details : fmt . Sprint ( influx . detect_data . fields . major_vulnerabilities ) } ,
} ,
SuccessfulScan : influx . detect_data . fields . major_vulnerabilities == 0 ,
ReportTime : time . Now ( ) ,
}
detailTable := reporting . ScanDetailTable {
NoRowsMessage : "No publicly known vulnerabilities detected" ,
Headers : [ ] string {
"Vulnerability Name" ,
"Severity" ,
"Overall Score" ,
"Base Score" ,
"Component Name" ,
"Component Version" ,
"Description" ,
"Status" ,
} ,
WithCounter : true ,
CounterHeader : "Entry#" ,
}
vulnItems := vulns . Items
sort . Slice ( vulnItems , func ( i , j int ) bool {
return vulnItems [ i ] . OverallScore > vulnItems [ j ] . OverallScore
} )
for _ , vuln := range vulnItems {
row := reporting . ScanRow { }
row . AddColumn ( vuln . VulnerabilityWithRemediation . VulnerabilityName , 0 )
row . AddColumn ( vuln . VulnerabilityWithRemediation . Severity , 0 )
var scoreStyle reporting . ColumnStyle = reporting . Yellow
if isMajorVulnerability ( vuln ) {
scoreStyle = reporting . Red
}
if ! isActiveVulnerability ( vuln ) {
scoreStyle = reporting . Grey
}
row . AddColumn ( vuln . VulnerabilityWithRemediation . OverallScore , scoreStyle )
row . AddColumn ( vuln . VulnerabilityWithRemediation . BaseScore , 0 )
row . AddColumn ( vuln . Name , 0 )
row . AddColumn ( vuln . Version , 0 )
row . AddColumn ( vuln . VulnerabilityWithRemediation . Description , 0 )
row . AddColumn ( vuln . VulnerabilityWithRemediation . RemediationStatus , 0 )
detailTable . Rows = append ( detailTable . Rows , row )
}
scanReport . DetailTable = detailTable
return scanReport
}
2022-03-17 16:32:48 +02:00
func isActiveVulnerability ( v bd . Vulnerability ) bool {
2022-07-11 10:50:31 +02:00
if v . Ignored {
return false
}
2022-03-17 16:32:48 +02:00
switch v . VulnerabilityWithRemediation . RemediationStatus {
case "NEW" :
return true
case "REMEDIATION_REQUIRED" :
return true
case "NEEDS_REVIEW" :
return true
default :
return false
}
}
2021-08-12 15:58:33 +02:00
2022-03-17 16:32:48 +02:00
func isMajorVulnerability ( v bd . Vulnerability ) bool {
2022-07-11 10:50:31 +02:00
if v . Ignored {
return false
}
2022-03-17 16:32:48 +02:00
switch v . VulnerabilityWithRemediation . Severity {
case "CRITICAL" :
return true
case "HIGH" :
return true
default :
return false
2021-08-12 15:58:33 +02:00
}
2022-03-17 16:32:48 +02:00
}
2021-08-12 15:58:33 +02:00
2022-08-02 08:26:26 +02:00
func postScanChecksAndReporting ( ctx context . Context , config detectExecuteScanOptions , influx * detectExecuteScanInflux , utils detectUtils , sys * blackduckSystem ) error {
2022-03-17 16:32:48 +02:00
errorsOccured := [ ] string { }
2022-08-11 13:12:14 +02:00
vulns , components , err := getVulnsAndComponents ( config , influx , sys )
2022-03-17 16:32:48 +02:00
if err != nil {
return errors . Wrap ( err , "failed to fetch vulnerabilities" )
}
if config . CreateResultIssue && len ( config . GithubToken ) > 0 && len ( config . GithubAPIURL ) > 0 && len ( config . Owner ) > 0 && len ( config . Repository ) > 0 {
log . Entry ( ) . Debugf ( "Creating result issues for %v alert(s)" , len ( vulns . Items ) )
issueDetails := make ( [ ] reporting . IssueDetail , len ( vulns . Items ) )
piperutils . CopyAtoB ( vulns . Items , issueDetails )
2022-08-02 08:26:26 +02:00
gh := reporting . GitHub {
Owner : & config . Owner ,
Repository : & config . Repository ,
Assignees : & config . Assignees ,
IssueService : utils . GetIssueService ( ) ,
SearchService : utils . GetSearchService ( ) ,
}
if err := gh . UploadMultipleReports ( ctx , & issueDetails ) ; err != nil {
2022-03-17 16:32:48 +02:00
errorsOccured = append ( errorsOccured , fmt . Sprint ( err ) )
2021-08-12 15:58:33 +02:00
}
}
2022-03-17 16:32:48 +02:00
2022-08-11 13:12:14 +02:00
sarif := bd . CreateSarifResultFile ( vulns , components )
2022-03-17 16:32:48 +02:00
paths , err := bd . WriteSarifFile ( sarif , utils )
if err != nil {
errorsOccured = append ( errorsOccured , fmt . Sprint ( err ) )
2021-08-12 15:58:33 +02:00
}
2022-03-17 16:32:48 +02:00
scanReport := createVulnerabilityReport ( config , vulns , influx , sys )
vulnerabilityReportPaths , err := bd . WriteVulnerabilityReports ( scanReport , utils )
if err != nil {
errorsOccured = append ( errorsOccured , fmt . Sprint ( err ) )
}
paths = append ( paths , vulnerabilityReportPaths ... )
policyStatus , err := getPolicyStatus ( config , influx , sys )
2022-07-21 09:04:21 +02:00
if err != nil {
errorsOccured = append ( errorsOccured , fmt . Sprint ( err ) )
}
2022-03-17 16:32:48 +02:00
policyReport := createPolicyStatusReport ( config , policyStatus , influx , sys )
policyReportPaths , err := writePolicyStatusReports ( policyReport , config , utils )
if err != nil {
errorsOccured = append ( errorsOccured , fmt . Sprint ( err ) )
}
paths = append ( paths , policyReportPaths ... )
2022-08-09 10:57:02 +02:00
piperutils . PersistReportsAndLinks ( "detectExecuteScan" , "" , utils , paths , nil )
2022-03-17 16:32:48 +02:00
if err != nil {
errorsOccured = append ( errorsOccured , fmt . Sprint ( err ) )
}
err , violationCount := writeIpPolicyJson ( config , utils , paths , sys )
if err != nil {
errorsOccured = append ( errorsOccured , fmt . Sprint ( err ) )
}
if violationCount > 0 {
log . SetErrorCategory ( log . ErrorCompliance )
2022-07-21 09:04:21 +02:00
errorsOccured = append ( errorsOccured , "License Policy Violations found" )
2022-03-17 16:32:48 +02:00
}
if len ( errorsOccured ) > 0 {
return fmt . Errorf ( strings . Join ( errorsOccured , ": " ) )
}
return nil
}
func getVulnsAndComponents ( config detectExecuteScanOptions , influx * detectExecuteScanInflux , sys * blackduckSystem ) ( * bd . Vulnerabilities , * bd . Components , error ) {
detectVersionName := getVersionName ( config )
2022-10-10 10:06:20 +02:00
components , err := sys . Client . GetComponents ( config . ProjectName , detectVersionName )
2022-03-17 16:32:48 +02:00
if err != nil {
return nil , nil , err
}
2022-10-10 10:06:20 +02:00
// create component lookup map to interconnect vulnerability and component
keyFormat := "%v/%v"
componentLookup := map [ string ] * bd . Component { }
for _ , comp := range components . Items {
componentLookup [ fmt . Sprintf ( keyFormat , comp . Name , comp . Version ) ] = & comp
}
vulns , err := sys . Client . GetVulnerabilities ( config . ProjectName , detectVersionName )
if err != nil {
return nil , components , err
}
2022-03-17 16:32:48 +02:00
majorVulns := 0
activeVulns := 0
2022-10-10 10:06:20 +02:00
for index , vuln := range vulns . Items {
2022-03-17 16:32:48 +02:00
if isActiveVulnerability ( vuln ) {
activeVulns ++
if isMajorVulnerability ( vuln ) {
majorVulns ++
}
}
2022-10-10 10:06:20 +02:00
component := componentLookup [ fmt . Sprintf ( keyFormat , vuln . Name , vuln . Version ) ]
if component != nil && len ( component . Name ) > 0 {
vulns . Items [ index ] . Component = component
} else {
vulns . Items [ index ] . Component = & bd . Component { Name : vuln . Name , Version : vuln . Version }
}
2022-03-17 16:32:48 +02:00
}
influx . detect_data . fields . vulnerabilities = activeVulns
influx . detect_data . fields . major_vulnerabilities = majorVulns
influx . detect_data . fields . minor_vulnerabilities = activeVulns - majorVulns
influx . detect_data . fields . components = components . TotalCount
return vulns , components , nil
2021-08-12 15:58:33 +02:00
}
2021-09-07 17:17:03 +02:00
func getPolicyStatus ( config detectExecuteScanOptions , influx * detectExecuteScanInflux , sys * blackduckSystem ) ( * bd . PolicyStatus , error ) {
policyStatus , err := sys . Client . GetPolicyStatus ( config . ProjectName , getVersionName ( config ) )
if err != nil {
return nil , err
}
totalViolations := 0
for _ , level := range policyStatus . SeverityLevels {
totalViolations += level . Value
}
influx . detect_data . fields . policy_violations = totalViolations
return policyStatus , nil
}
func createPolicyStatusReport ( config detectExecuteScanOptions , policyStatus * bd . PolicyStatus , influx * detectExecuteScanInflux , sys * blackduckSystem ) reporting . ScanReport {
versionName := getVersionName ( config )
versionUrl , _ := sys . Client . GetProjectVersionLink ( config . ProjectName , versionName )
policyReport := reporting . ScanReport {
2022-03-17 16:32:48 +02:00
ReportTitle : "BlackDuck Policy Violations Report" ,
2021-09-07 17:17:03 +02:00
Subheaders : [ ] reporting . Subheader {
{ Description : "BlackDuck project name " , Details : config . ProjectName } ,
{ Description : "BlackDuck project version name" , Details : fmt . Sprintf ( "<a href='%v'>%v</a>" , versionUrl , versionName ) } ,
} ,
Overview : [ ] reporting . OverviewRow {
{ Description : "Overall Policy Violation Status" , Details : policyStatus . OverallStatus } ,
{ Description : "Total Number of Policy Vioaltions" , Details : fmt . Sprint ( influx . detect_data . fields . policy_violations ) } ,
} ,
SuccessfulScan : influx . detect_data . fields . policy_violations > 0 ,
ReportTime : time . Now ( ) ,
}
detailTable := reporting . ScanDetailTable {
Headers : [ ] string {
"Policy Severity Level" , "Number of Components in Violation" ,
} ,
WithCounter : false ,
}
for _ , level := range policyStatus . SeverityLevels {
row := reporting . ScanRow { }
row . AddColumn ( level . Name , 0 )
row . AddColumn ( level . Value , 0 )
detailTable . Rows = append ( detailTable . Rows , row )
}
policyReport . DetailTable = detailTable
return policyReport
}
func writePolicyStatusReports ( scanReport reporting . ScanReport , config detectExecuteScanOptions , utils detectUtils ) ( [ ] piperutils . Path , error ) {
reportPaths := [ ] piperutils . Path { }
htmlReport , _ := scanReport . ToHTML ( )
htmlReportPath := "piper_detect_policy_violation_report.html"
2022-08-02 08:26:26 +02:00
if err := utils . FileWrite ( htmlReportPath , htmlReport , 0 o666 ) ; err != nil {
2021-09-07 17:17:03 +02:00
log . SetErrorCategory ( log . ErrorConfiguration )
return reportPaths , errors . Wrapf ( err , "failed to write html report" )
}
reportPaths = append ( reportPaths , piperutils . Path { Name : "BlackDuck Policy Violation Report" , Target : htmlReportPath } )
jsonReport , _ := scanReport . ToJSON ( )
if exists , _ := utils . DirExists ( reporting . StepReportDirectory ) ; ! exists {
2022-08-02 08:26:26 +02:00
err := utils . MkdirAll ( reporting . StepReportDirectory , 0 o777 )
2021-09-07 17:17:03 +02:00
if err != nil {
return reportPaths , errors . Wrap ( err , "failed to create reporting directory" )
}
}
2022-08-02 08:26:26 +02:00
if err := utils . FileWrite ( filepath . Join ( reporting . StepReportDirectory , fmt . Sprintf ( "detectExecuteScan_policy_%v.json" , fmt . Sprintf ( "%v" , time . Now ( ) ) ) ) , jsonReport , 0 o666 ) ; err != nil {
2021-09-07 17:17:03 +02:00
return reportPaths , errors . Wrapf ( err , "failed to write json report" )
}
return reportPaths , nil
}
2021-09-20 11:28:16 +02:00
func writeIpPolicyJson ( config detectExecuteScanOptions , utils detectUtils , paths [ ] piperutils . Path , sys * blackduckSystem ) ( error , int ) {
2021-09-07 17:17:03 +02:00
components , err := sys . Client . GetComponentsWithLicensePolicyRule ( config . ProjectName , getVersionName ( config ) )
if err != nil {
2022-07-21 09:04:21 +02:00
return errors . Wrapf ( err , "failed to get License Policy Violations" ) , 0
2021-09-07 17:17:03 +02:00
}
violationCount := getActivePolicyViolations ( components )
violations := struct {
PolicyViolations int ` json:"policyViolations" `
Reports [ ] string ` json:"reports" `
} {
PolicyViolations : violationCount ,
Reports : [ ] string { } ,
}
for _ , path := range paths {
violations . Reports = append ( violations . Reports , path . Target )
}
if files , err := utils . Glob ( "**/*BlackDuck_RiskReport.pdf" ) ; err == nil && len ( files ) > 0 {
// there should only be one RiskReport thus only taking the first one
_ , reportFile := filepath . Split ( files [ 0 ] )
violations . Reports = append ( violations . Reports , reportFile )
}
violationContent , err := json . Marshal ( violations )
if err != nil {
2021-09-20 11:28:16 +02:00
return fmt . Errorf ( "failed to marshal policy violation data: %w" , err ) , violationCount
2021-09-07 17:17:03 +02:00
}
2022-08-02 08:26:26 +02:00
err = utils . FileWrite ( "blackduck-ip.json" , violationContent , 0 o666 )
2021-09-07 17:17:03 +02:00
if err != nil {
2021-09-20 11:28:16 +02:00
return fmt . Errorf ( "failed to write policy violation report: %w" , err ) , violationCount
2021-09-07 17:17:03 +02:00
}
2021-09-20 11:28:16 +02:00
return nil , violationCount
2021-09-07 17:17:03 +02:00
}
func getActivePolicyViolations ( components * bd . Components ) int {
if components . TotalCount == 0 {
return 0
}
activeViolations := 0
for _ , component := range components . Items {
if isActivePolicyViolation ( component . PolicyStatus ) {
activeViolations ++
}
}
return activeViolations
}
func isActivePolicyViolation ( status string ) bool {
2022-07-21 09:04:21 +02:00
return status == "IN_VIOLATION"
2021-09-07 17:17:03 +02:00
}
2021-09-09 10:50:33 +02:00
// create toolrecord file for detectExecute
2022-08-09 10:57:02 +02:00
func createToolRecordDetect ( utils detectUtils , workspace string , config detectExecuteScanOptions , sys * blackduckSystem ) ( string , error ) {
record := toolrecord . New ( utils , workspace , "detectExecute" , config . ServerURL )
2021-09-09 10:50:33 +02:00
project , err := sys . Client . GetProject ( config . ProjectName )
if err != nil {
return "" , fmt . Errorf ( "TR_DETECT: GetProject failed %v" , err )
}
metadata := project . Metadata
projectURL := metadata . Href
if projectURL == "" {
return "" , fmt . Errorf ( "TR_DETECT: no project URL" )
}
// project UUID comes as last part of the URL
parts := strings . Split ( projectURL , "/" )
projectId := parts [ len ( parts ) - 1 ]
if projectId == "" {
return "" , fmt . Errorf ( "TR_DETECT: no project id in %v" , projectURL )
}
err = record . AddKeyData ( "project" ,
2021-07-23 08:48:48 +02:00
projectId ,
config . ProjectName ,
2021-09-09 10:50:33 +02:00
projectURL )
2021-07-23 08:48:48 +02:00
if err != nil {
return "" , err
}
2022-07-21 09:04:21 +02:00
_ = record . AddContext ( "DetectTools" , config . DetectTools )
2021-07-23 08:48:48 +02:00
err = record . Persist ( )
if err != nil {
return "" , err
}
return record . GetFileName ( ) , nil
}