mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-16 05:16:08 +02:00
085a8c003b
* added quotes for mvn settings path * added logs * removed logs, added excape symbol for spaces * set quotes * removed replacing * changed quotes * fixed tests * removed extra log --------- Co-authored-by: sumeet patil <sumeet.patil@sap.com>
502 lines
17 KiB
Go
502 lines
17 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/SAP/jenkins-library/pkg/codeql"
|
|
"github.com/SAP/jenkins-library/pkg/command"
|
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
|
"github.com/SAP/jenkins-library/pkg/log"
|
|
"github.com/SAP/jenkins-library/pkg/maven"
|
|
"github.com/SAP/jenkins-library/pkg/piperutils"
|
|
"github.com/SAP/jenkins-library/pkg/telemetry"
|
|
"github.com/google/shlex"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type codeqlExecuteScanUtils interface {
|
|
command.ExecRunner
|
|
|
|
piperutils.FileUtils
|
|
|
|
DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
|
|
}
|
|
|
|
type codeqlExecuteScanUtilsBundle struct {
|
|
*command.Command
|
|
*piperutils.Files
|
|
*piperhttp.Client
|
|
}
|
|
|
|
func newCodeqlExecuteScanUtils() codeqlExecuteScanUtils {
|
|
utils := codeqlExecuteScanUtilsBundle{
|
|
Command: &command.Command{},
|
|
Files: &piperutils.Files{},
|
|
Client: &piperhttp.Client{},
|
|
}
|
|
|
|
utils.Stdout(log.Writer())
|
|
utils.Stderr(log.Writer())
|
|
return &utils
|
|
}
|
|
|
|
func codeqlExecuteScan(config codeqlExecuteScanOptions, telemetryData *telemetry.CustomData, influx *codeqlExecuteScanInflux) {
|
|
utils := newCodeqlExecuteScanUtils()
|
|
|
|
influx.step_data.fields.codeql = false
|
|
|
|
reports, err := runCodeqlExecuteScan(&config, telemetryData, utils, influx)
|
|
piperutils.PersistReportsAndLinks("codeqlExecuteScan", "./", utils, reports, nil)
|
|
|
|
if err != nil {
|
|
log.Entry().WithError(err).Fatal("Codeql scan failed")
|
|
}
|
|
influx.step_data.fields.codeql = true
|
|
}
|
|
|
|
func appendCodeqlQuerySuite(utils codeqlExecuteScanUtils, cmd []string, querySuite, transformString string) []string {
|
|
if len(querySuite) > 0 {
|
|
if len(transformString) > 0 {
|
|
querySuite = transformQuerySuite(utils, querySuite, transformString)
|
|
if len(querySuite) == 0 {
|
|
return cmd
|
|
}
|
|
}
|
|
cmd = append(cmd, querySuite)
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
func transformQuerySuite(utils codeqlExecuteScanUtils, querySuite, transformString string) string {
|
|
var bufferOut, bufferErr bytes.Buffer
|
|
utils.Stdout(&bufferOut)
|
|
defer utils.Stdout(log.Writer())
|
|
utils.Stderr(&bufferErr)
|
|
defer utils.Stderr(log.Writer())
|
|
if err := utils.RunExecutable("sh", []string{"-c", fmt.Sprintf("echo %s | sed -E \"%s\"", querySuite, transformString)}...); err != nil {
|
|
log.Entry().WithError(err).Error("failed to transform querySuite")
|
|
e := bufferErr.String()
|
|
log.Entry().Error(e)
|
|
return querySuite
|
|
}
|
|
return strings.TrimSpace(bufferOut.String())
|
|
}
|
|
|
|
func execute(utils codeqlExecuteScanUtils, cmd []string, isVerbose bool) error {
|
|
if isVerbose {
|
|
cmd = append(cmd, "-v")
|
|
}
|
|
|
|
return utils.RunExecutable("codeql", cmd...)
|
|
}
|
|
|
|
func getLangFromBuildTool(buildTool string) string {
|
|
switch buildTool {
|
|
case "maven":
|
|
return "java"
|
|
case "pip":
|
|
return "python"
|
|
case "npm":
|
|
return "javascript"
|
|
case "yarn":
|
|
return "javascript"
|
|
case "golang":
|
|
return "go"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func getToken(config *codeqlExecuteScanOptions) (bool, string) {
|
|
if len(config.GithubToken) > 0 {
|
|
return true, config.GithubToken
|
|
}
|
|
|
|
envVal, isEnvGithubToken := os.LookupEnv("GITHUB_TOKEN")
|
|
if isEnvGithubToken {
|
|
return true, envVal
|
|
}
|
|
|
|
return false, ""
|
|
}
|
|
|
|
func printCodeqlImageVersion() {
|
|
codeqlVersion, err := os.ReadFile("/etc/image-version")
|
|
if err != nil {
|
|
log.Entry().Infof("CodeQL image version: unknown")
|
|
} else {
|
|
log.Entry().Infof("CodeQL image version: %s", string(codeqlVersion))
|
|
}
|
|
}
|
|
|
|
func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telemetry.CustomData, utils codeqlExecuteScanUtils, influx *codeqlExecuteScanInflux) ([]piperutils.Path, error) {
|
|
printCodeqlImageVersion()
|
|
|
|
var reports []piperutils.Path
|
|
|
|
dbCreateCustomFlags := codeql.ParseCustomFlags(config.DatabaseCreateFlags)
|
|
err := runDatabaseCreate(config, dbCreateCustomFlags, utils)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Error("failed to create codeql database")
|
|
return reports, err
|
|
}
|
|
|
|
err = os.MkdirAll(filepath.Join(config.ModulePath, "target"), os.ModePerm)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Error("failed to create output directory for reports")
|
|
return reports, err
|
|
}
|
|
|
|
dbAnalyzeCustomFlags := codeql.ParseCustomFlags(config.DatabaseAnalyzeFlags)
|
|
scanReports, err := runDatabaseAnalyze(config, dbAnalyzeCustomFlags, utils)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Error("failed to analyze codeql database")
|
|
return reports, err
|
|
}
|
|
reports = append(reports, scanReports...)
|
|
|
|
if len(config.CustomCommand) > 0 {
|
|
err = runCustomCommand(utils, config.CustomCommand)
|
|
if err != nil {
|
|
return reports, err
|
|
}
|
|
}
|
|
|
|
repoInfo, err := codeql.GetRepoInfo(config.Repository, config.AnalyzedRef, config.CommitID,
|
|
config.TargetGithubRepoURL, config.TargetGithubBranchName)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Error("failed to get repository info")
|
|
return reports, err
|
|
}
|
|
|
|
if len(config.TargetGithubRepoURL) > 0 {
|
|
err = uploadProjectToGitHub(config, repoInfo)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Error("failed to upload project to Github")
|
|
return reports, err
|
|
}
|
|
}
|
|
|
|
var scanResults []codeql.CodeqlFindings
|
|
if !config.UploadResults {
|
|
log.Entry().Warn("The sarif results will not be uploaded to the repository and compliance report will not be generated as uploadResults is set to false.")
|
|
} else {
|
|
log.Entry().Infof("The sarif results will be uploaded to the repository %s", repoInfo.FullUrl)
|
|
|
|
hasToken, token := getToken(config)
|
|
if !hasToken {
|
|
return reports, fmt.Errorf("failed running upload-results as githubToken was not specified")
|
|
}
|
|
|
|
err = uploadSarifResults(config, token, repoInfo, utils)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Error("failed to upload sarif results")
|
|
return reports, err
|
|
}
|
|
|
|
codeqlScanAuditInstance := codeql.NewCodeqlScanAuditInstance(repoInfo.ServerUrl, repoInfo.Owner, repoInfo.Repo, token, []string{})
|
|
scanResults, err = codeqlScanAuditInstance.GetVulnerabilities(repoInfo.AnalyzedRef)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Error("failed to get vulnerabilities")
|
|
return reports, err
|
|
}
|
|
|
|
codeqlAudit := codeql.CodeqlAudit{
|
|
ToolName: "codeql",
|
|
RepositoryUrl: repoInfo.FullUrl,
|
|
CodeScanningLink: repoInfo.ScanUrl,
|
|
RepositoryReferenceUrl: repoInfo.FullRef,
|
|
QuerySuite: config.QuerySuite,
|
|
ScanResults: scanResults,
|
|
}
|
|
paths, err := codeql.WriteJSONReport(codeqlAudit, config.ModulePath)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Error("failed to write json compliance report")
|
|
return reports, err
|
|
}
|
|
reports = append(reports, paths...)
|
|
|
|
if config.CheckForCompliance {
|
|
err = checkForCompliance(scanResults, config, repoInfo)
|
|
if err != nil {
|
|
return reports, err
|
|
}
|
|
}
|
|
}
|
|
|
|
addDataToInfluxDB(repoInfo, config.QuerySuite, scanResults, influx)
|
|
|
|
toolRecordFileName, err := codeql.CreateAndPersistToolRecord(utils, repoInfo, config.ModulePath)
|
|
if err != nil {
|
|
log.Entry().Warning("TR_CODEQL: Failed to create toolrecord file ...", err)
|
|
} else {
|
|
reports = append(reports, piperutils.Path{Target: toolRecordFileName})
|
|
}
|
|
|
|
return reports, nil
|
|
}
|
|
|
|
func runDatabaseCreate(config *codeqlExecuteScanOptions, customFlags map[string]string, utils codeqlExecuteScanUtils) error {
|
|
cmd, err := prepareCmdForDatabaseCreate(customFlags, config, utils)
|
|
if err != nil {
|
|
log.Entry().Error("failed to prepare command for codeql database create")
|
|
return err
|
|
}
|
|
if err = execute(utils, cmd, GeneralConfig.Verbose); err != nil {
|
|
log.Entry().Error("failed running command codeql database create")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runDatabaseAnalyze(config *codeqlExecuteScanOptions, customFlags map[string]string, utils codeqlExecuteScanUtils) ([]piperutils.Path, error) {
|
|
sarifReport, err := executeAnalysis("sarif-latest", "codeqlReport.sarif", customFlags, config, utils)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
csvReport, err := executeAnalysis("csv", "codeqlReport.csv", customFlags, config, utils)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(sarifReport, csvReport...), nil
|
|
}
|
|
|
|
func runGithubUploadResults(config *codeqlExecuteScanOptions, repoInfo *codeql.RepoInfo, token string, utils codeqlExecuteScanUtils) (string, error) {
|
|
cmd := prepareCmdForUploadResults(config, repoInfo, token)
|
|
|
|
var bufferOut, bufferErr bytes.Buffer
|
|
utils.Stdout(&bufferOut)
|
|
defer utils.Stdout(log.Writer())
|
|
utils.Stderr(&bufferErr)
|
|
defer utils.Stderr(log.Writer())
|
|
|
|
if err := execute(utils, cmd, GeneralConfig.Verbose); err != nil {
|
|
e := bufferErr.String()
|
|
log.Entry().Error(e)
|
|
if strings.Contains(e, "Unauthorized") {
|
|
log.Entry().Error("Either your Github Token is invalid or you use both Vault and Jenkins credentials where your Vault credentials are invalid, to use your Jenkins credentials try setting 'skipVault:true'")
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
url := strings.TrimSpace(bufferOut.String())
|
|
return url, nil
|
|
}
|
|
|
|
func executeAnalysis(format, reportName string, customFlags map[string]string, config *codeqlExecuteScanOptions, utils codeqlExecuteScanUtils) ([]piperutils.Path, error) {
|
|
moduleTargetPath := filepath.Join(config.ModulePath, "target")
|
|
report := filepath.Join(moduleTargetPath, reportName)
|
|
cmd, err := prepareCmdForDatabaseAnalyze(utils, customFlags, config, format, report)
|
|
if err != nil {
|
|
log.Entry().Errorf("failed to prepare command for codeql database analyze (format=%s)", format)
|
|
return nil, err
|
|
}
|
|
if err = execute(utils, cmd, GeneralConfig.Verbose); err != nil {
|
|
log.Entry().Errorf("failed running command codeql database analyze for %s generation", format)
|
|
return nil, err
|
|
}
|
|
return []piperutils.Path{
|
|
{Target: report},
|
|
}, nil
|
|
}
|
|
|
|
func prepareCmdForDatabaseCreate(customFlags map[string]string, config *codeqlExecuteScanOptions, utils codeqlExecuteScanUtils) ([]string, error) {
|
|
cmd := []string{"database", "create", config.Database}
|
|
cmd = codeql.AppendFlagIfNotSetByUser(cmd, []string{"--overwrite", "--no-overwrite"}, []string{"--overwrite"}, customFlags)
|
|
cmd = codeql.AppendFlagIfNotSetByUser(cmd, []string{"--source-root", "-s"}, []string{"--source-root", "."}, customFlags)
|
|
cmd = codeql.AppendFlagIfNotSetByUser(cmd, []string{"--working-dir"}, []string{"--working-dir", config.ModulePath}, customFlags)
|
|
|
|
if !codeql.IsFlagSetByUser(customFlags, []string{"--language", "-l"}) {
|
|
language := getLangFromBuildTool(config.BuildTool)
|
|
if len(language) == 0 && len(config.Language) == 0 {
|
|
if config.BuildTool == "custom" {
|
|
return nil, fmt.Errorf("as the buildTool is custom. please specify the language parameter")
|
|
} else {
|
|
return nil, fmt.Errorf("the step could not recognize the specified buildTool %s. please specify valid buildtool", config.BuildTool)
|
|
}
|
|
}
|
|
if len(language) > 0 {
|
|
cmd = append(cmd, "--language="+language)
|
|
} else {
|
|
cmd = append(cmd, "--language="+config.Language)
|
|
}
|
|
}
|
|
|
|
cmd = codeql.AppendThreadsAndRam(cmd, config.Threads, config.Ram, customFlags)
|
|
|
|
if len(config.BuildCommand) > 0 && !codeql.IsFlagSetByUser(customFlags, []string{"--command", "-c"}) {
|
|
buildCmd := config.BuildCommand
|
|
buildCmd = buildCmd + getMavenSettings(buildCmd, config, utils)
|
|
cmd = append(cmd, "--command="+buildCmd)
|
|
}
|
|
|
|
if codeql.IsFlagSetByUser(customFlags, []string{"--command", "-c"}) {
|
|
updateCmdFlag(config, customFlags, utils)
|
|
}
|
|
cmd = codeql.AppendCustomFlags(cmd, customFlags)
|
|
|
|
return cmd, nil
|
|
}
|
|
|
|
func prepareCmdForDatabaseAnalyze(utils codeqlExecuteScanUtils, customFlags map[string]string, config *codeqlExecuteScanOptions, format, reportName string) ([]string, error) {
|
|
cmd := []string{"database", "analyze", "--format=" + format, "--output=" + reportName, config.Database}
|
|
cmd = codeql.AppendThreadsAndRam(cmd, config.Threads, config.Ram, customFlags)
|
|
cmd = codeql.AppendCustomFlags(cmd, customFlags)
|
|
cmd = appendCodeqlQuerySuite(utils, cmd, config.QuerySuite, config.TransformQuerySuite)
|
|
return cmd, nil
|
|
}
|
|
|
|
func prepareCmdForUploadResults(config *codeqlExecuteScanOptions, repoInfo *codeql.RepoInfo, token string) []string {
|
|
cmd := []string{"github", "upload-results", "--sarif=" + filepath.Join(config.ModulePath, "target", "codeqlReport.sarif")}
|
|
|
|
//if no git params are passed(commitId, reference, serverUrl, repository), then codeql tries to auto populate it based on git information of the checkout repository.
|
|
//It also depends on the orchestrator. Some orchestrator keep git information and some not.
|
|
|
|
if token != "" {
|
|
cmd = append(cmd, "-a="+token)
|
|
}
|
|
|
|
if repoInfo.CommitId != "" {
|
|
cmd = append(cmd, "--commit="+repoInfo.CommitId)
|
|
}
|
|
|
|
if repoInfo.ServerUrl != "" {
|
|
cmd = append(cmd, "--github-url="+repoInfo.ServerUrl)
|
|
}
|
|
|
|
if repoInfo.Repo != "" && repoInfo.Owner != "" {
|
|
cmd = append(cmd, "--repository="+(repoInfo.Owner+"/"+repoInfo.Repo))
|
|
}
|
|
|
|
if repoInfo.AnalyzedRef != "" {
|
|
cmd = append(cmd, "--ref="+repoInfo.AnalyzedRef)
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func uploadSarifResults(config *codeqlExecuteScanOptions, token string, repoInfo *codeql.RepoInfo, utils codeqlExecuteScanUtils) error {
|
|
sarifUrl, err := runGithubUploadResults(config, repoInfo, token, utils)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
codeqlSarifUploader := codeql.NewCodeqlSarifUploaderInstance(sarifUrl, token)
|
|
err = codeql.WaitSarifUploaded(config.SarifCheckMaxRetries, config.SarifCheckRetryInterval, &codeqlSarifUploader)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to upload sarif")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func uploadProjectToGitHub(config *codeqlExecuteScanOptions, repoInfo *codeql.RepoInfo) error {
|
|
log.Entry().Infof("DB sources for %s will be uploaded to target GitHub repo: %s", config.Repository, repoInfo.FullUrl)
|
|
|
|
hasToken, token := getToken(config)
|
|
if !hasToken {
|
|
return fmt.Errorf("failed running upload db sources to GitHub as githubToken was not specified")
|
|
}
|
|
repoUploader, err := codeql.NewGitUploaderInstance(
|
|
token,
|
|
repoInfo.AnalyzedRef,
|
|
config.Database,
|
|
repoInfo.CommitId,
|
|
config.Repository,
|
|
config.TargetGithubRepoURL,
|
|
)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Error("failed to create github uploader")
|
|
return err
|
|
}
|
|
targetCommitId, err := repoUploader.UploadProjectToGithub()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed uploading db sources from non-GitHub SCM to GitHub")
|
|
}
|
|
repoInfo.CommitId = targetCommitId
|
|
log.Entry().Info("DB sources were successfully uploaded to target GitHub repo")
|
|
|
|
return nil
|
|
}
|
|
|
|
func runCustomCommand(utils codeqlExecuteScanUtils, command string) error {
|
|
log.Entry().Infof("custom command will be run: %s", command)
|
|
cmd, err := shlex.Split(command)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Errorf("failed to parse custom command %s", command)
|
|
return err
|
|
}
|
|
log.Entry().Infof("Parsed command '%s' with %d arguments: ['%s']", cmd[0], len(cmd[1:]), strings.Join(cmd[1:], "', '"))
|
|
|
|
err = utils.RunExecutable(cmd[0], cmd[1:]...)
|
|
if err != nil {
|
|
log.Entry().WithError(err).Errorf("failed to run command %s", command)
|
|
return err
|
|
}
|
|
log.Entry().Info("Success.")
|
|
return nil
|
|
}
|
|
|
|
func checkForCompliance(scanResults []codeql.CodeqlFindings, config *codeqlExecuteScanOptions, repoInfo *codeql.RepoInfo) error {
|
|
for _, scanResult := range scanResults {
|
|
if scanResult.ClassificationName == codeql.AuditAll {
|
|
unaudited := scanResult.Total - scanResult.Audited
|
|
if unaudited > config.VulnerabilityThresholdTotal {
|
|
msg := fmt.Sprintf("Your repository %v with ref %v is not compliant. Total unaudited issues are %v which is greater than the VulnerabilityThresholdTotal count %v",
|
|
repoInfo.FullUrl, repoInfo.AnalyzedRef, unaudited, config.VulnerabilityThresholdTotal)
|
|
return errors.Errorf(msg)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func addDataToInfluxDB(repoInfo *codeql.RepoInfo, querySuite string, scanResults []codeql.CodeqlFindings, influx *codeqlExecuteScanInflux) {
|
|
influx.codeql_data.fields.repositoryURL = repoInfo.FullUrl
|
|
influx.codeql_data.fields.repositoryReferenceURL = repoInfo.FullRef
|
|
influx.codeql_data.fields.codeScanningLink = repoInfo.ScanUrl
|
|
influx.codeql_data.fields.querySuite = querySuite
|
|
|
|
for _, sr := range scanResults {
|
|
if sr.ClassificationName == codeql.AuditAll {
|
|
influx.codeql_data.fields.auditAllAudited = sr.Audited
|
|
influx.codeql_data.fields.auditAllTotal = sr.Total
|
|
}
|
|
if sr.ClassificationName == codeql.Optional {
|
|
influx.codeql_data.fields.optionalAudited = sr.Audited
|
|
influx.codeql_data.fields.optionalTotal = sr.Total
|
|
}
|
|
}
|
|
}
|
|
|
|
func getMavenSettings(buildCmd string, config *codeqlExecuteScanOptions, utils codeqlExecuteScanUtils) string {
|
|
params := ""
|
|
if len(buildCmd) > 0 && config.BuildTool == "maven" && !strings.Contains(buildCmd, "--global-settings") && !strings.Contains(buildCmd, "--settings") {
|
|
mvnParams, err := maven.DownloadAndGetMavenParameters(config.GlobalSettingsFile, config.ProjectSettingsFile, utils)
|
|
if err != nil {
|
|
log.Entry().Error("failed to download and get maven parameters: ", err)
|
|
return params
|
|
}
|
|
for i := 1; i < len(mvnParams); i += 2 {
|
|
params = fmt.Sprintf("%s \"%s=%s\"", params, mvnParams[i-1], mvnParams[i])
|
|
}
|
|
}
|
|
return params
|
|
}
|
|
|
|
func updateCmdFlag(config *codeqlExecuteScanOptions, customFlags map[string]string, utils codeqlExecuteScanUtils) {
|
|
var buildCmd string
|
|
if customFlags["--command"] != "" {
|
|
buildCmd = customFlags["--command"]
|
|
} else {
|
|
buildCmd = customFlags["-c"]
|
|
}
|
|
buildCmd += getMavenSettings(buildCmd, config, utils)
|
|
customFlags["--command"] = buildCmd
|
|
delete(customFlags, "-c")
|
|
}
|