mirror of
https://github.com/SAP/jenkins-library.git
synced 2026-06-19 22:58:55 +02:00
feat(codeqlExecuteScan): CodeQL compliance report and check (#4335)
* CodeQL compliance report and check * fix test cases --------- Co-authored-by: Daria Kuznetsova <d.kuznetsova@sap.com>
This commit is contained in:
co-authored by
Daria Kuznetsova
parent
58e9344eb0
commit
6dad124367
+183
-130
@@ -3,9 +3,11 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/codeql"
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/orchestrator"
|
||||
@@ -26,6 +28,7 @@ type RepoInfo struct {
|
||||
repo string
|
||||
commitId string
|
||||
ref string
|
||||
owner string
|
||||
}
|
||||
|
||||
type codeqlExecuteScanUtilsBundle struct {
|
||||
@@ -48,7 +51,9 @@ func codeqlExecuteScan(config codeqlExecuteScanOptions, telemetryData *telemetry
|
||||
|
||||
utils := newCodeqlExecuteScanUtils()
|
||||
|
||||
err := runCodeqlExecuteScan(&config, telemetryData, utils)
|
||||
reports, err := runCodeqlExecuteScan(&config, telemetryData, utils)
|
||||
piperutils.PersistReportsAndLinks("codeqlExecuteScan", "./", utils, reports, nil)
|
||||
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatal("Codeql scan failed")
|
||||
}
|
||||
@@ -96,82 +101,99 @@ func getGitRepoInfo(repoUri string, repoInfo *RepoInfo) error {
|
||||
if len(matches) > 0 {
|
||||
match := matches[0]
|
||||
repoInfo.serverUrl = "https://" + match[3]
|
||||
repoInfo.repo = strings.TrimSuffix(match[4], ".git")
|
||||
repoData := strings.Split(strings.TrimSuffix(match[4], ".git"), "/")
|
||||
if len(repoData) != 2 {
|
||||
return fmt.Errorf("Invalid repository %s", repoUri)
|
||||
}
|
||||
|
||||
repoInfo.owner = repoData[0]
|
||||
repoInfo.repo = repoData[1]
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Invalid repository %s", repoUri)
|
||||
}
|
||||
|
||||
func uploadResults(config *codeqlExecuteScanOptions, utils codeqlExecuteScanUtils) error {
|
||||
if config.UploadResults {
|
||||
if len(config.GithubToken) == 0 {
|
||||
return errors.New("failed running upload-results as github token was not specified")
|
||||
func initGitInfo(config *codeqlExecuteScanOptions) RepoInfo {
|
||||
var repoInfo RepoInfo
|
||||
err := getGitRepoInfo(config.Repository, &repoInfo)
|
||||
if err != nil {
|
||||
log.Entry().Error(err)
|
||||
}
|
||||
repoInfo.ref = config.AnalyzedRef
|
||||
repoInfo.commitId = config.CommitID
|
||||
|
||||
provider, err := orchestrator.NewOrchestratorSpecificConfigProvider()
|
||||
if err != nil {
|
||||
log.Entry().Warn("No orchestrator found. We assume piper is running locally.")
|
||||
} else {
|
||||
if repoInfo.ref == "" {
|
||||
repoInfo.ref = provider.GetReference()
|
||||
}
|
||||
|
||||
if config.CommitID == "NA" {
|
||||
return errors.New("failed running upload-results as gitCommitId is not available")
|
||||
if repoInfo.commitId == "" || repoInfo.commitId == "NA" {
|
||||
repoInfo.commitId = provider.GetCommit()
|
||||
}
|
||||
|
||||
var repoInfo RepoInfo
|
||||
err := getGitRepoInfo(config.Repository, &repoInfo)
|
||||
if err != nil {
|
||||
log.Entry().Error(err)
|
||||
}
|
||||
repoInfo.ref = config.AnalyzedRef
|
||||
repoInfo.commitId = config.CommitID
|
||||
|
||||
provider, err := orchestrator.NewOrchestratorSpecificConfigProvider()
|
||||
if err != nil {
|
||||
log.Entry().Error(err)
|
||||
} else {
|
||||
if repoInfo.ref == "" {
|
||||
repoInfo.ref = provider.GetReference()
|
||||
}
|
||||
|
||||
if repoInfo.commitId == "" {
|
||||
repoInfo.commitId = provider.GetCommit()
|
||||
}
|
||||
|
||||
if repoInfo.serverUrl == "" {
|
||||
err = getGitRepoInfo(provider.GetRepoURL(), &repoInfo)
|
||||
if err != nil {
|
||||
log.Entry().Error(err)
|
||||
}
|
||||
if repoInfo.serverUrl == "" {
|
||||
err = getGitRepoInfo(provider.GetRepoURL(), &repoInfo)
|
||||
if err != nil {
|
||||
log.Entry().Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd := []string{"github", "upload-results", "--sarif=" + fmt.Sprintf("%vtarget/codeqlReport.sarif", config.ModulePath), "-a=" + config.GithubToken}
|
||||
return repoInfo
|
||||
}
|
||||
|
||||
if repoInfo.commitId != "" {
|
||||
cmd = append(cmd, "--commit="+repoInfo.commitId)
|
||||
}
|
||||
func getToken(config *codeqlExecuteScanOptions) (bool, string) {
|
||||
if len(config.GithubToken) > 0 {
|
||||
return true, config.GithubToken
|
||||
}
|
||||
|
||||
if repoInfo.serverUrl != "" {
|
||||
cmd = append(cmd, "--github-url="+repoInfo.serverUrl)
|
||||
}
|
||||
envVal, isEnvGithubToken := os.LookupEnv("GITHUB_TOKEN")
|
||||
if isEnvGithubToken {
|
||||
return true, envVal
|
||||
}
|
||||
|
||||
if repoInfo.repo != "" {
|
||||
cmd = append(cmd, "--repository="+repoInfo.repo)
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if repoInfo.ref != "" {
|
||||
cmd = append(cmd, "--ref="+repoInfo.ref)
|
||||
}
|
||||
func uploadResults(config *codeqlExecuteScanOptions, repoInfo RepoInfo, token string, utils codeqlExecuteScanUtils) error {
|
||||
cmd := []string{"github", "upload-results", "--sarif=" + filepath.Join(config.ModulePath, "target", "codeqlReport.sarif")}
|
||||
|
||||
//if no git pramas 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.
|
||||
err = execute(utils, cmd, GeneralConfig.Verbose)
|
||||
if err != nil {
|
||||
log.Entry().Error("failed to upload sarif results")
|
||||
return err
|
||||
}
|
||||
if config.GithubToken != "" {
|
||||
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 != "" {
|
||||
cmd = append(cmd, "--repository="+(repoInfo.owner+"/"+repoInfo.repo))
|
||||
}
|
||||
|
||||
if repoInfo.ref != "" {
|
||||
cmd = append(cmd, "--ref="+repoInfo.ref)
|
||||
}
|
||||
|
||||
//if no git pramas 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.
|
||||
err := execute(utils, cmd, GeneralConfig.Verbose)
|
||||
if err != nil {
|
||||
log.Entry().Error("failed to upload sarif results")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telemetry.CustomData, utils codeqlExecuteScanUtils) error {
|
||||
func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telemetry.CustomData, utils codeqlExecuteScanUtils) ([]piperutils.Path, error) {
|
||||
codeqlVersion, err := os.ReadFile("/etc/image-version")
|
||||
if err != nil {
|
||||
log.Entry().Infof("CodeQL image version: unknown")
|
||||
@@ -186,9 +208,9 @@ func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telem
|
||||
|
||||
if len(language) == 0 && len(config.Language) == 0 {
|
||||
if config.BuildTool == "custom" {
|
||||
return fmt.Errorf("as the buildTool is custom. please atleast specify the language parameter")
|
||||
return reports, fmt.Errorf("as the buildTool is custom. please specify the language parameter")
|
||||
} else {
|
||||
return fmt.Errorf("the step could not recognize the specified buildTool %s. please specify valid buildtool", config.BuildTool)
|
||||
return reports, fmt.Errorf("the step could not recognize the specified buildTool %s. please specify valid buildtool", config.BuildTool)
|
||||
}
|
||||
}
|
||||
if len(language) > 0 {
|
||||
@@ -207,130 +229,153 @@ func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telem
|
||||
err = execute(utils, cmd, GeneralConfig.Verbose)
|
||||
if err != nil {
|
||||
log.Entry().Error("failed running command codeql database create")
|
||||
return err
|
||||
return reports, err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(fmt.Sprintf("%vtarget", config.ModulePath), os.ModePerm)
|
||||
err = os.MkdirAll(filepath.Join(config.ModulePath, "target"), os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
return reports, fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
cmd = nil
|
||||
cmd = append(cmd, "database", "analyze", "--format=sarif-latest", fmt.Sprintf("--output=%vtarget/codeqlReport.sarif", config.ModulePath), config.Database)
|
||||
cmd = append(cmd, "database", "analyze", "--format=sarif-latest", fmt.Sprintf("--output=%v", filepath.Join(config.ModulePath, "target", "codeqlReport.sarif")), config.Database)
|
||||
cmd = append(cmd, getRamAndThreadsFromConfig(config)...)
|
||||
cmd = codeqlQuery(cmd, config.QuerySuite)
|
||||
err = execute(utils, cmd, GeneralConfig.Verbose)
|
||||
if err != nil {
|
||||
log.Entry().Error("failed running command codeql database analyze for sarif generation")
|
||||
return err
|
||||
return reports, err
|
||||
}
|
||||
|
||||
reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/codeqlReport.sarif", config.ModulePath)})
|
||||
reports = append(reports, piperutils.Path{Target: filepath.Join(config.ModulePath, "target", "codeqlReport.sarif")})
|
||||
|
||||
cmd = nil
|
||||
cmd = append(cmd, "database", "analyze", "--format=csv", fmt.Sprintf("--output=%vtarget/codeqlReport.csv", config.ModulePath), config.Database)
|
||||
cmd = append(cmd, "database", "analyze", "--format=csv", fmt.Sprintf("--output=%v", filepath.Join(config.ModulePath, "target", "codeqlReport.csv")), config.Database)
|
||||
cmd = append(cmd, getRamAndThreadsFromConfig(config)...)
|
||||
cmd = codeqlQuery(cmd, config.QuerySuite)
|
||||
err = execute(utils, cmd, GeneralConfig.Verbose)
|
||||
if err != nil {
|
||||
log.Entry().Error("failed running command codeql database analyze for csv generation")
|
||||
return err
|
||||
return reports, err
|
||||
}
|
||||
|
||||
reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/codeqlReport.csv", config.ModulePath)})
|
||||
err = uploadResults(config, utils)
|
||||
if err != nil {
|
||||
log.Entry().Error("failed to upload results")
|
||||
return err
|
||||
reports = append(reports, piperutils.Path{Target: filepath.Join(config.ModulePath, "target", "codeqlReport.csv")})
|
||||
|
||||
repoInfo := initGitInfo(config)
|
||||
repoUrl := fmt.Sprintf("%s/%s/%s", repoInfo.serverUrl, repoInfo.owner, repoInfo.repo)
|
||||
repoReference, err := buildRepoReference(repoUrl, repoInfo.ref)
|
||||
repoCodeqlScanUrl := fmt.Sprintf("%s/security/code-scanning?query=is:open+ref:%s", repoUrl, repoInfo.ref)
|
||||
|
||||
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 {
|
||||
hasToken, token := getToken(config)
|
||||
if !hasToken {
|
||||
return reports, errors.New("failed running upload-results as githubToken was not specified")
|
||||
}
|
||||
|
||||
err = uploadResults(config, repoInfo, token, utils)
|
||||
if err != nil {
|
||||
|
||||
return reports, err
|
||||
}
|
||||
|
||||
codeqlScanAuditInstance := codeql.NewCodeqlScanAuditInstance(config.GithubAPIURL, repoInfo.owner, repoInfo.repo, token, []string{})
|
||||
scanResults, err := codeqlScanAuditInstance.GetVulnerabilities(repoInfo.ref)
|
||||
if err != nil {
|
||||
return reports, errors.Wrap(err, "failed to get scan results")
|
||||
}
|
||||
|
||||
unaudited := (scanResults.Total - scanResults.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", repoUrl, repoInfo.ref, unaudited, config.VulnerabilityThresholdTotal)
|
||||
if config.CheckForCompliance {
|
||||
|
||||
return reports, errors.Errorf(msg)
|
||||
}
|
||||
|
||||
log.Entry().Warning(msg)
|
||||
}
|
||||
|
||||
codeqlAudit := codeql.CodeqlAudit{ToolName: "codeql", RepositoryUrl: repoUrl, CodeScanningLink: repoCodeqlScanUrl, RepositoryReferenceUrl: repoReference, ScanResults: scanResults}
|
||||
paths, err := codeql.WriteJSONReport(codeqlAudit, config.ModulePath)
|
||||
if err != nil {
|
||||
return reports, errors.Wrap(err, "failed to write json compliance report")
|
||||
}
|
||||
|
||||
reports = append(reports, paths...)
|
||||
}
|
||||
|
||||
// create toolrecord file
|
||||
toolRecordFileName, err := createToolRecordCodeql(utils, "./", *config)
|
||||
toolRecordFileName, err := createAndPersistToolRecord(utils, repoInfo, repoReference, repoUrl, repoCodeqlScanUrl)
|
||||
if err != nil {
|
||||
// do not fail until the framework is well established
|
||||
log.Entry().Warning("TR_CODEQL: Failed to create toolrecord file ...", err)
|
||||
} else {
|
||||
reports = append(reports, piperutils.Path{Target: toolRecordFileName})
|
||||
}
|
||||
|
||||
piperutils.PersistReportsAndLinks("codeqlExecuteScan", "./", utils, reports, nil)
|
||||
|
||||
return nil
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func createToolRecordCodeql(utils codeqlExecuteScanUtils, workspace string, config codeqlExecuteScanOptions) (string, error) {
|
||||
repoURL := strings.TrimSuffix(config.Repository, ".git")
|
||||
toolInstance, orgName, repoName, err := parseRepositoryURL(repoURL)
|
||||
func createAndPersistToolRecord(utils codeqlExecuteScanUtils, repoInfo RepoInfo, repoReference string, repoUrl string, repoCodeqlScanUrl string) (string, error) {
|
||||
toolRecord, err := createToolRecordCodeql(utils, repoInfo, repoReference, repoUrl, repoCodeqlScanUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
record := toolrecord.New(utils, workspace, "codeql", toolInstance)
|
||||
record.DisplayName = fmt.Sprintf("%s %s - %s %s", orgName, repoName, config.AnalyzedRef, config.CommitID)
|
||||
record.DisplayURL = fmt.Sprintf("%s/security/code-scanning?query=is:open+ref:%s", repoURL, config.AnalyzedRef)
|
||||
// Repository
|
||||
err = record.AddKeyData("repository",
|
||||
fmt.Sprintf("%s/%s", orgName, repoName),
|
||||
fmt.Sprintf("%s %s", orgName, repoName),
|
||||
config.Repository)
|
||||
|
||||
toolRecordFileName, err := persistToolRecord(toolRecord)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Repository Reference
|
||||
repoReference, err := buildRepoReference(repoURL, config.AnalyzedRef)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Warn("Failed to build repository reference")
|
||||
|
||||
return toolRecordFileName, nil
|
||||
}
|
||||
|
||||
func createToolRecordCodeql(utils codeqlExecuteScanUtils, repoInfo RepoInfo, repoUrl string, repoReference string, repoCodeqlScanUrl string) (*toolrecord.Toolrecord, error) {
|
||||
record := toolrecord.New(utils, "./", "codeql", repoInfo.serverUrl)
|
||||
|
||||
if repoInfo.serverUrl == "" {
|
||||
return record, errors.New("Repository not set")
|
||||
}
|
||||
|
||||
if repoInfo.commitId == "" || repoInfo.commitId == "NA" {
|
||||
return record, errors.New("CommitId not set")
|
||||
}
|
||||
|
||||
if repoInfo.ref == "" {
|
||||
return record, errors.New("Analyzed Reference not set")
|
||||
}
|
||||
|
||||
record.DisplayName = fmt.Sprintf("%s %s - %s %s", repoInfo.owner, repoInfo.repo, repoInfo.ref, repoInfo.commitId)
|
||||
record.DisplayURL = fmt.Sprintf("%s/security/code-scanning?query=is:open+ref:%s", repoUrl, repoInfo.ref)
|
||||
|
||||
err := record.AddKeyData("repository",
|
||||
fmt.Sprintf("%s/%s", repoInfo.owner, repoInfo.repo),
|
||||
fmt.Sprintf("%s %s", repoInfo.owner, repoInfo.repo),
|
||||
repoUrl)
|
||||
if err != nil {
|
||||
return record, err
|
||||
}
|
||||
|
||||
err = record.AddKeyData("repositoryReference",
|
||||
config.AnalyzedRef,
|
||||
fmt.Sprintf("%s - %s", repoName, config.AnalyzedRef),
|
||||
repoInfo.ref,
|
||||
fmt.Sprintf("%s - %s", repoInfo.repo, repoInfo.ref),
|
||||
repoReference)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return record, err
|
||||
}
|
||||
// Scan Results
|
||||
|
||||
err = record.AddKeyData("scanResult",
|
||||
fmt.Sprintf("%s/%s", config.AnalyzedRef, config.CommitID),
|
||||
fmt.Sprintf("%s %s - %s %s", orgName, repoName, config.AnalyzedRef, config.CommitID),
|
||||
fmt.Sprintf("%s/security/code-scanning?query=is:open+ref:%s", repoURL, config.AnalyzedRef))
|
||||
fmt.Sprintf("%s/%s", repoInfo.ref, repoInfo.commitId),
|
||||
fmt.Sprintf("%s %s - %s %s", repoInfo.owner, repoInfo.repo, repoInfo.ref, repoInfo.commitId),
|
||||
fmt.Sprintf("%s/security/code-scanning?query=is:open+ref:%s", repoUrl, repoInfo.ref))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = record.Persist()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return record.GetFileName(), nil
|
||||
}
|
||||
|
||||
func parseRepositoryURL(repository string) (toolInstance, orgName, repoName string, err error) {
|
||||
if repository == "" {
|
||||
err = errors.New("Repository param is not set")
|
||||
return
|
||||
}
|
||||
fullRepo := strings.TrimSuffix(repository, ".git")
|
||||
// regexp for toolInstance
|
||||
re := regexp.MustCompile(`^[a-zA-Z0-9]+://[a-zA-Z0-9-_.]+/`)
|
||||
matchedHost := re.FindAllString(fullRepo, -1)
|
||||
if len(matchedHost) == 0 {
|
||||
err = errors.New("Unable to parse tool instance from repository url")
|
||||
return
|
||||
}
|
||||
orgRepoNames := strings.Split(strings.TrimPrefix(fullRepo, matchedHost[0]), "/")
|
||||
if len(orgRepoNames) < 2 {
|
||||
err = errors.New("Unable to parse organization and repo names from repository url")
|
||||
return
|
||||
return record, err
|
||||
}
|
||||
|
||||
toolInstance = strings.Trim(matchedHost[0], "/")
|
||||
orgName = orgRepoNames[0]
|
||||
repoName = orgRepoNames[1]
|
||||
return
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func buildRepoReference(repository, analyzedRef string) (string, error) {
|
||||
if repository == "" || analyzedRef == "" {
|
||||
return "", errors.New("Repository or analyzedRef param is not set")
|
||||
}
|
||||
ref := strings.Split(analyzedRef, "/")
|
||||
if len(ref) < 3 {
|
||||
return "", errors.New(fmt.Sprintf("Wrong analyzedRef format: %s", analyzedRef))
|
||||
@@ -344,6 +389,14 @@ func buildRepoReference(repository, analyzedRef string) (string, error) {
|
||||
return fmt.Sprintf("%s/tree/%s", repository, ref[2]), nil
|
||||
}
|
||||
|
||||
func persistToolRecord(toolRecord *toolrecord.Toolrecord) (string, error) {
|
||||
err := toolRecord.Persist()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return toolRecord.GetFileName(), nil
|
||||
}
|
||||
|
||||
func getRamAndThreadsFromConfig(config *codeqlExecuteScanOptions) []string {
|
||||
params := make([]string, 0, 2)
|
||||
if len(config.Threads) > 0 {
|
||||
|
||||
@@ -20,19 +20,22 @@ import (
|
||||
)
|
||||
|
||||
type codeqlExecuteScanOptions struct {
|
||||
GithubToken string `json:"githubToken,omitempty"`
|
||||
BuildTool string `json:"buildTool,omitempty" validate:"possible-values=custom maven golang npm pip yarn"`
|
||||
BuildCommand string `json:"buildCommand,omitempty"`
|
||||
Language string `json:"language,omitempty"`
|
||||
ModulePath string `json:"modulePath,omitempty"`
|
||||
Database string `json:"database,omitempty"`
|
||||
QuerySuite string `json:"querySuite,omitempty"`
|
||||
UploadResults bool `json:"uploadResults,omitempty"`
|
||||
Threads string `json:"threads,omitempty"`
|
||||
Ram string `json:"ram,omitempty"`
|
||||
AnalyzedRef string `json:"analyzedRef,omitempty"`
|
||||
Repository string `json:"repository,omitempty"`
|
||||
CommitID string `json:"commitId,omitempty"`
|
||||
GithubToken string `json:"githubToken,omitempty"`
|
||||
GithubAPIURL string `json:"githubApiUrl,omitempty"`
|
||||
BuildTool string `json:"buildTool,omitempty" validate:"possible-values=custom maven golang npm pip yarn"`
|
||||
BuildCommand string `json:"buildCommand,omitempty"`
|
||||
Language string `json:"language,omitempty"`
|
||||
ModulePath string `json:"modulePath,omitempty"`
|
||||
Database string `json:"database,omitempty"`
|
||||
QuerySuite string `json:"querySuite,omitempty"`
|
||||
UploadResults bool `json:"uploadResults,omitempty"`
|
||||
Threads string `json:"threads,omitempty"`
|
||||
Ram string `json:"ram,omitempty"`
|
||||
AnalyzedRef string `json:"analyzedRef,omitempty"`
|
||||
Repository string `json:"repository,omitempty"`
|
||||
CommitID string `json:"commitId,omitempty"`
|
||||
VulnerabilityThresholdTotal int `json:"vulnerabilityThresholdTotal,omitempty"`
|
||||
CheckForCompliance bool `json:"checkForCompliance,omitempty"`
|
||||
}
|
||||
|
||||
type codeqlExecuteScanReports struct {
|
||||
@@ -173,6 +176,7 @@ and Java plus Maven.`,
|
||||
|
||||
func addCodeqlExecuteScanFlags(cmd *cobra.Command, stepConfig *codeqlExecuteScanOptions) {
|
||||
cmd.Flags().StringVar(&stepConfig.GithubToken, "githubToken", os.Getenv("PIPER_githubToken"), "GitHub personal access token in plain text. NEVER set this parameter in a file commited to a source code repository. This parameter is intended to be used from the command line or set securely via the environment variable listed below. In most pipeline use-cases, you should instead either store the token in Vault (where it can be automatically retrieved by the step from one of the paths listed below) or store it as a Jenkins secret and configure the secret's id via the `githubTokenCredentialsId` parameter.")
|
||||
cmd.Flags().StringVar(&stepConfig.GithubAPIURL, "githubApiUrl", `https://api.github.com`, "Set the GitHub API URL.")
|
||||
cmd.Flags().StringVar(&stepConfig.BuildTool, "buildTool", `maven`, "Defines the build tool which is used for building the project.")
|
||||
cmd.Flags().StringVar(&stepConfig.BuildCommand, "buildCommand", os.Getenv("PIPER_buildCommand"), "Command to build the project")
|
||||
cmd.Flags().StringVar(&stepConfig.Language, "language", os.Getenv("PIPER_language"), "The programming language used to analyze.")
|
||||
@@ -185,6 +189,8 @@ func addCodeqlExecuteScanFlags(cmd *cobra.Command, stepConfig *codeqlExecuteScan
|
||||
cmd.Flags().StringVar(&stepConfig.AnalyzedRef, "analyzedRef", os.Getenv("PIPER_analyzedRef"), "Name of the ref that was analyzed.")
|
||||
cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "URL of the GitHub instance")
|
||||
cmd.Flags().StringVar(&stepConfig.CommitID, "commitId", os.Getenv("PIPER_commitId"), "SHA of commit that was analyzed.")
|
||||
cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdTotal, "vulnerabilityThresholdTotal", 0, "Threashold for maximum number of allowed vulnerabilities.")
|
||||
cmd.Flags().BoolVar(&stepConfig.CheckForCompliance, "checkForCompliance", false, "If set to true, the piper step checks for compliance based on vulnerability threadholds. Example - If total vulnerabilites are 10 and vulnerabilityThresholdTotal is set as 0, then the steps throws an compliance error.")
|
||||
|
||||
cmd.MarkFlagRequired("buildTool")
|
||||
}
|
||||
@@ -228,6 +234,15 @@ func codeqlExecuteScanMetadata() config.StepData {
|
||||
Aliases: []config.Alias{{Name: "access_token"}},
|
||||
Default: os.Getenv("PIPER_githubToken"),
|
||||
},
|
||||
{
|
||||
Name: "githubApiUrl",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: `https://api.github.com`,
|
||||
},
|
||||
{
|
||||
Name: "buildTool",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
@@ -351,6 +366,24 @@ func codeqlExecuteScanMetadata() config.StepData {
|
||||
Aliases: []config.Alias{},
|
||||
Default: os.Getenv("PIPER_commitId"),
|
||||
},
|
||||
{
|
||||
Name: "vulnerabilityThresholdTotal",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "int",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: 0,
|
||||
},
|
||||
{
|
||||
Name: "checkForCompliance",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "bool",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []config.Container{
|
||||
|
||||
+137
-132
@@ -1,9 +1,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/SAP/jenkins-library/pkg/orchestrator"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -24,42 +26,50 @@ func TestRunCodeqlExecuteScan(t *testing.T) {
|
||||
|
||||
t.Run("Valid CodeqlExecuteScan", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{BuildTool: "maven", ModulePath: "./"}
|
||||
assert.Equal(t, nil, runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils()))
|
||||
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("No auth token passed on upload results", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{BuildTool: "maven", UploadResults: true, ModulePath: "./"}
|
||||
assert.Error(t, runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils()))
|
||||
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("GitCommitID is NA on upload results", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{BuildTool: "maven", UploadResults: true, ModulePath: "./", CommitID: "NA"}
|
||||
assert.Error(t, runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils()))
|
||||
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Upload results with token", func(t *testing.T) {
|
||||
t.Run("Upload results fails as repository not specified", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{BuildTool: "maven", ModulePath: "./", UploadResults: true, GithubToken: "test"}
|
||||
assert.Equal(t, nil, runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils()))
|
||||
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Custom buildtool", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{BuildTool: "custom", Language: "javascript", ModulePath: "./", GithubToken: "test"}
|
||||
assert.Equal(t, nil, runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils()))
|
||||
config := codeqlExecuteScanOptions{BuildTool: "custom", Language: "javascript", ModulePath: "./"}
|
||||
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Custom buildtool but no language specified", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{BuildTool: "custom", ModulePath: "./", GithubToken: "test"}
|
||||
assert.Error(t, runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils()))
|
||||
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Invalid buildtool and no language specified", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{BuildTool: "test", ModulePath: "./", GithubToken: "test"}
|
||||
assert.Error(t, runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils()))
|
||||
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Invalid buildtool but language specified", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{BuildTool: "test", Language: "javascript", ModulePath: "./", GithubToken: "test"}
|
||||
assert.Equal(t, nil, runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils()))
|
||||
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,7 +79,8 @@ func TestGetGitRepoInfo(t *testing.T) {
|
||||
err := getGitRepoInfo("https://github.hello.test/Testing/fortify.git", &repoInfo)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
assert.Equal(t, "Testing/fortify", repoInfo.repo)
|
||||
assert.Equal(t, "fortify", repoInfo.repo)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
})
|
||||
|
||||
t.Run("Valid URL2", func(t *testing.T) {
|
||||
@@ -77,14 +88,16 @@ func TestGetGitRepoInfo(t *testing.T) {
|
||||
err := getGitRepoInfo("https://github.hello.test/Testing/fortify", &repoInfo)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
assert.Equal(t, "Testing/fortify", repoInfo.repo)
|
||||
assert.Equal(t, "fortify", repoInfo.repo)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
})
|
||||
t.Run("Valid URL1 with dots", func(t *testing.T) {
|
||||
var repoInfo RepoInfo
|
||||
err := getGitRepoInfo("https://github.hello.test/Testing/com.sap.fortify.git", &repoInfo)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
assert.Equal(t, "Testing/com.sap.fortify", repoInfo.repo)
|
||||
assert.Equal(t, "com.sap.fortify", repoInfo.repo)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
})
|
||||
|
||||
t.Run("Valid URL2 with dots", func(t *testing.T) {
|
||||
@@ -92,14 +105,16 @@ func TestGetGitRepoInfo(t *testing.T) {
|
||||
err := getGitRepoInfo("https://github.hello.test/Testing/com.sap.fortify", &repoInfo)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
assert.Equal(t, "Testing/com.sap.fortify", repoInfo.repo)
|
||||
assert.Equal(t, "com.sap.fortify", repoInfo.repo)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
})
|
||||
t.Run("Valid URL1 with username and token", func(t *testing.T) {
|
||||
var repoInfo RepoInfo
|
||||
err := getGitRepoInfo("https://username:token@github.hello.test/Testing/fortify.git", &repoInfo)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
assert.Equal(t, "Testing/fortify", repoInfo.repo)
|
||||
assert.Equal(t, "fortify", repoInfo.repo)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
})
|
||||
|
||||
t.Run("Valid URL2 with username and token", func(t *testing.T) {
|
||||
@@ -107,7 +122,8 @@ func TestGetGitRepoInfo(t *testing.T) {
|
||||
err := getGitRepoInfo("https://username:token@github.hello.test/Testing/fortify", &repoInfo)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
assert.Equal(t, "Testing/fortify", repoInfo.repo)
|
||||
assert.Equal(t, "fortify", repoInfo.repo)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
})
|
||||
|
||||
t.Run("Invalid URL as no org/owner passed", func(t *testing.T) {
|
||||
@@ -121,58 +137,78 @@ func TestGetGitRepoInfo(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseRepositoryURL(t *testing.T) {
|
||||
t.Run("Valid repository", func(t *testing.T) {
|
||||
repository := "https://github.hello.test/Testing/fortify.git"
|
||||
toolInstance, orgName, repoName, err := parseRepositoryURL(repository)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.hello.test", toolInstance)
|
||||
assert.Equal(t, "Testing", orgName)
|
||||
assert.Equal(t, "fortify", repoName)
|
||||
func TestInitGitInfo(t *testing.T) {
|
||||
t.Run("Valid URL1", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{Repository: "https://github.hello.test/Testing/codeql.git", AnalyzedRef: "refs/head/branch", CommitID: "abcd1234"}
|
||||
repoInfo := initGitInfo(&config)
|
||||
assert.Equal(t, "abcd1234", repoInfo.commitId)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
assert.Equal(t, "codeql", repoInfo.repo)
|
||||
assert.Equal(t, "refs/head/branch", repoInfo.ref)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
})
|
||||
t.Run("valid repository 2", func(t *testing.T) {
|
||||
repository := "https://github.hello.test/Testing/fortify"
|
||||
toolInstance, orgName, repoName, err := parseRepositoryURL(repository)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.hello.test", toolInstance)
|
||||
assert.Equal(t, "Testing", orgName)
|
||||
assert.Equal(t, "fortify", repoName)
|
||||
|
||||
t.Run("Valid URL2", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{Repository: "https://github.hello.test/Testing/codeql", AnalyzedRef: "refs/head/branch", CommitID: "abcd1234"}
|
||||
repoInfo := initGitInfo(&config)
|
||||
assert.Equal(t, "abcd1234", repoInfo.commitId)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
assert.Equal(t, "codeql", repoInfo.repo)
|
||||
assert.Equal(t, "refs/head/branch", repoInfo.ref)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
})
|
||||
t.Run("Invalid repository without repo name", func(t *testing.T) {
|
||||
repository := "https://github.hello.test/Testing"
|
||||
toolInstance, orgName, repoName, err := parseRepositoryURL(repository)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "Unable to parse organization and repo names")
|
||||
assert.Equal(t, "", toolInstance)
|
||||
assert.Equal(t, "", orgName)
|
||||
assert.Equal(t, "", repoName)
|
||||
|
||||
t.Run("Valid url with dots URL1", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{Repository: "https://github.hello.test/Testing/com.sap.codeql.git", AnalyzedRef: "refs/head/branch", CommitID: "abcd1234"}
|
||||
repoInfo := initGitInfo(&config)
|
||||
assert.Equal(t, "abcd1234", repoInfo.commitId)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
assert.Equal(t, "com.sap.codeql", repoInfo.repo)
|
||||
assert.Equal(t, "refs/head/branch", repoInfo.ref)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
})
|
||||
t.Run("Invalid repository without organization name", func(t *testing.T) {
|
||||
repository := "https://github.hello.test/fortify"
|
||||
toolInstance, orgName, repoName, err := parseRepositoryURL(repository)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "Unable to parse organization and repo names")
|
||||
assert.Equal(t, "", toolInstance)
|
||||
assert.Equal(t, "", orgName)
|
||||
assert.Equal(t, "", repoName)
|
||||
|
||||
t.Run("Valid url with dots URL2", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{Repository: "https://github.hello.test/Testing/com.sap.codeql", AnalyzedRef: "refs/head/branch", CommitID: "abcd1234"}
|
||||
repoInfo := initGitInfo(&config)
|
||||
assert.Equal(t, "abcd1234", repoInfo.commitId)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
assert.Equal(t, "com.sap.codeql", repoInfo.repo)
|
||||
assert.Equal(t, "refs/head/branch", repoInfo.ref)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
})
|
||||
t.Run("Invalid repository without tool instance", func(t *testing.T) {
|
||||
repository := "/Testing/fortify"
|
||||
toolInstance, orgName, repoName, err := parseRepositoryURL(repository)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "Unable to parse tool instance")
|
||||
assert.Equal(t, "", toolInstance)
|
||||
assert.Equal(t, "", orgName)
|
||||
assert.Equal(t, "", repoName)
|
||||
|
||||
t.Run("Valid url with username and token URL1", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{Repository: "https://username:token@github.hello.test/Testing/codeql.git", AnalyzedRef: "refs/head/branch", CommitID: "abcd1234"}
|
||||
repoInfo := initGitInfo(&config)
|
||||
assert.Equal(t, "abcd1234", repoInfo.commitId)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
assert.Equal(t, "codeql", repoInfo.repo)
|
||||
assert.Equal(t, "refs/head/branch", repoInfo.ref)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
})
|
||||
t.Run("Empty repository", func(t *testing.T) {
|
||||
repository := ""
|
||||
toolInstance, orgName, repoName, err := parseRepositoryURL(repository)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "Repository param is not set")
|
||||
assert.Equal(t, "", toolInstance)
|
||||
assert.Equal(t, "", orgName)
|
||||
assert.Equal(t, "", repoName)
|
||||
|
||||
t.Run("Valid url with username and token URL2", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{Repository: "https://username:token@github.hello.test/Testing/codeql", AnalyzedRef: "refs/head/branch", CommitID: "abcd1234"}
|
||||
repoInfo := initGitInfo(&config)
|
||||
assert.Equal(t, "abcd1234", repoInfo.commitId)
|
||||
assert.Equal(t, "Testing", repoInfo.owner)
|
||||
assert.Equal(t, "codeql", repoInfo.repo)
|
||||
assert.Equal(t, "refs/head/branch", repoInfo.ref)
|
||||
assert.Equal(t, "https://github.hello.test", repoInfo.serverUrl)
|
||||
})
|
||||
|
||||
t.Run("Invalid URL with no org/reponame", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{Repository: "https://github.hello.test", AnalyzedRef: "refs/head/branch", CommitID: "abcd1234"}
|
||||
repoInfo := initGitInfo(&config)
|
||||
_, err := orchestrator.NewOrchestratorSpecificConfigProvider()
|
||||
assert.Equal(t, "abcd1234", repoInfo.commitId)
|
||||
assert.Equal(t, "refs/head/branch", repoInfo.ref)
|
||||
if err != nil {
|
||||
assert.Equal(t, "", repoInfo.owner)
|
||||
assert.Equal(t, "", repoInfo.repo)
|
||||
assert.Equal(t, "", repoInfo.serverUrl)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -207,87 +243,56 @@ func TestBuildRepoReference(t *testing.T) {
|
||||
assert.ErrorContains(t, err, "Wrong analyzedRef format")
|
||||
assert.Equal(t, "", ref)
|
||||
})
|
||||
t.Run("Empty repository", func(t *testing.T) {
|
||||
repository := ""
|
||||
analyzedRef := "refs/pull/merge"
|
||||
ref, err := buildRepoReference(repository, analyzedRef)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "Repository or analyzedRef param is not set")
|
||||
assert.Equal(t, "", ref)
|
||||
})
|
||||
t.Run("Empty analyzedRef", func(t *testing.T) {
|
||||
repository := "https://github.hello.test/Testing/fortify"
|
||||
analyzedRef := ""
|
||||
ref, err := buildRepoReference(repository, analyzedRef)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "Repository or analyzedRef param is not set")
|
||||
assert.Equal(t, "", ref)
|
||||
})
|
||||
}
|
||||
|
||||
func getRepoReferences(repoInfo RepoInfo) (string, string, string) {
|
||||
repoUrl := fmt.Sprintf("%s/%s/%s", repoInfo.serverUrl, repoInfo.owner, repoInfo.repo)
|
||||
repoReference, _ := buildRepoReference(repoUrl, repoInfo.ref)
|
||||
repoCodeqlScanUrl := fmt.Sprintf("%s/security/code-scanning?query=is:open+ref:%s", repoUrl, repoInfo.ref)
|
||||
return repoUrl, repoReference, repoCodeqlScanUrl
|
||||
}
|
||||
func TestCreateToolRecordCodeql(t *testing.T) {
|
||||
t.Run("Valid toolrun file", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{
|
||||
Repository: "https://github.hello.test/Testing/fortify.git",
|
||||
AnalyzedRef: "refs/head/branch",
|
||||
CommitID: "test",
|
||||
}
|
||||
fileName, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), "test", config)
|
||||
repoInfo := RepoInfo{serverUrl: "https://github.hello.test", commitId: "test", ref: "refs/head/branch", owner: "Testing", repo: "fortify"}
|
||||
repoUrl, repoReference, repoCodeqlScanUrl := getRepoReferences(repoInfo)
|
||||
toolRecord, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), repoInfo, repoUrl, repoReference, repoCodeqlScanUrl)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, fileName, "toolrun_codeql")
|
||||
assert.Equal(t, toolRecord.ToolName, "codeql")
|
||||
assert.Equal(t, toolRecord.ToolInstance, "https://github.hello.test")
|
||||
assert.Equal(t, toolRecord.DisplayName, "Testing fortify - refs/head/branch test")
|
||||
assert.Equal(t, toolRecord.DisplayURL, "https://github.hello.test/Testing/fortify/security/code-scanning?query=is:open+ref:refs/head/branch")
|
||||
})
|
||||
t.Run("Empty repository URL", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{
|
||||
Repository: "",
|
||||
AnalyzedRef: "refs/head/branch",
|
||||
CommitID: "test",
|
||||
}
|
||||
fileName, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), "", config)
|
||||
repoInfo := RepoInfo{serverUrl: "", commitId: "test", ref: "refs/head/branch", owner: "Testing", repo: "fortify"}
|
||||
repoUrl, repoReference, repoCodeqlScanUrl := getRepoReferences(repoInfo)
|
||||
_, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), repoInfo, repoUrl, repoReference, repoCodeqlScanUrl)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "Repository param is not set")
|
||||
assert.Empty(t, fileName)
|
||||
})
|
||||
t.Run("Invalid repository URL", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{
|
||||
Repository: "https://github.hello.test/Testing",
|
||||
AnalyzedRef: "refs/head/branch",
|
||||
CommitID: "test",
|
||||
}
|
||||
fileName, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), "test", config)
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, "^Unable to parse [a-z ]+ from repository url$", err.Error())
|
||||
assert.Empty(t, fileName)
|
||||
})
|
||||
t.Run("Empty workspace", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{
|
||||
Repository: "https://github.hello.test/Testing/fortify.git",
|
||||
AnalyzedRef: "refs/head/branch",
|
||||
CommitID: "test",
|
||||
}
|
||||
fileName, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), "", config)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "TR_PERSIST: empty workspace")
|
||||
assert.Empty(t, fileName)
|
||||
assert.ErrorContains(t, err, "Repository not set")
|
||||
})
|
||||
|
||||
t.Run("Empty analyzedRef", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{
|
||||
Repository: "https://github.hello.test/Testing/fortify.git",
|
||||
AnalyzedRef: "",
|
||||
CommitID: "test",
|
||||
}
|
||||
fileName, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), "test", config)
|
||||
repoInfo := RepoInfo{serverUrl: "https://github.hello.test", commitId: "test", ref: "", owner: "Testing", repo: "fortify"}
|
||||
repoUrl, repoReference, repoCodeqlScanUrl := getRepoReferences(repoInfo)
|
||||
_, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), repoInfo, repoUrl, repoReference, repoCodeqlScanUrl)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "TR_ADD_KEY: empty keyvalue")
|
||||
assert.Empty(t, fileName, "toolrun_codeql")
|
||||
assert.ErrorContains(t, err, "Analyzed Reference not set")
|
||||
})
|
||||
|
||||
t.Run("Empty CommitId", func(t *testing.T) {
|
||||
repoInfo := RepoInfo{serverUrl: "https://github.hello.test", commitId: "", ref: "refs/head/branch", owner: "Testing", repo: "fortify"}
|
||||
repoUrl, repoReference, repoCodeqlScanUrl := getRepoReferences(repoInfo)
|
||||
_, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), repoInfo, repoUrl, repoReference, repoCodeqlScanUrl)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "CommitId not set")
|
||||
})
|
||||
t.Run("Invalid analyzedRef", func(t *testing.T) {
|
||||
config := codeqlExecuteScanOptions{
|
||||
Repository: "https://github.hello.test/Testing/fortify.git",
|
||||
AnalyzedRef: "refs/head",
|
||||
CommitID: "test",
|
||||
}
|
||||
fileName, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), "test", config)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, fileName, "toolrun_codeql")
|
||||
repoInfo := RepoInfo{serverUrl: "https://github.hello.test", commitId: "", ref: "refs/branch", owner: "Testing", repo: "fortify"}
|
||||
repoUrl, repoReference, repoCodeqlScanUrl := getRepoReferences(repoInfo)
|
||||
_, err := createToolRecordCodeql(newCodeqlExecuteScanTestsUtils(), repoInfo, repoUrl, repoReference, repoCodeqlScanUrl)
|
||||
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package codeql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
sapgithub "github.com/SAP/jenkins-library/pkg/github"
|
||||
"github.com/google/go-github/v45/github"
|
||||
)
|
||||
|
||||
type CodeqlScanAudit interface {
|
||||
GetVulnerabilities(analyzedRef string, state string) error
|
||||
}
|
||||
|
||||
type githubCodeqlScanningService interface {
|
||||
ListAlertsForRepo(ctx context.Context, owner, repo string, opts *github.AlertListOptions) ([]*github.Alert, *github.Response, error)
|
||||
}
|
||||
|
||||
const auditStateOpen = "open"
|
||||
|
||||
func NewCodeqlScanAuditInstance(apiURL, owner, repository, token string, trustedCerts []string) CodeqlScanAuditInstance {
|
||||
return CodeqlScanAuditInstance{apiURL: apiURL, owner: owner, repository: repository, token: token, trustedCerts: trustedCerts}
|
||||
}
|
||||
|
||||
type CodeqlScanAuditInstance struct {
|
||||
apiURL string
|
||||
owner string
|
||||
repository string
|
||||
token string
|
||||
trustedCerts []string
|
||||
alertListoptions github.AlertListOptions
|
||||
}
|
||||
|
||||
func (codeqlScanAudit *CodeqlScanAuditInstance) GetVulnerabilities(analyzedRef string) (CodeqlScanning, error) {
|
||||
ctx, client, err := sapgithub.NewClient(codeqlScanAudit.token, codeqlScanAudit.apiURL, "", codeqlScanAudit.trustedCerts)
|
||||
if err != nil {
|
||||
return CodeqlScanning{}, err
|
||||
}
|
||||
|
||||
return getVulnerabilitiesFromClient(ctx, client.CodeScanning, analyzedRef, codeqlScanAudit)
|
||||
}
|
||||
|
||||
func getVulnerabilitiesFromClient(ctx context.Context, codeScanning githubCodeqlScanningService, analyzedRef string, codeqlScanAudit *CodeqlScanAuditInstance) (CodeqlScanning, error) {
|
||||
alertOptions := github.AlertListOptions{
|
||||
State: "",
|
||||
Ref: analyzedRef,
|
||||
ListOptions: github.ListOptions{},
|
||||
}
|
||||
|
||||
alerts, _, err := codeScanning.ListAlertsForRepo(ctx, codeqlScanAudit.owner, codeqlScanAudit.repository, &alertOptions)
|
||||
if err != nil {
|
||||
return CodeqlScanning{}, err
|
||||
}
|
||||
|
||||
openStateCount := 0
|
||||
for _, alert := range alerts {
|
||||
if *alert.State == auditStateOpen {
|
||||
openStateCount = openStateCount + 1
|
||||
}
|
||||
}
|
||||
|
||||
codeqlScanning := CodeqlScanning{}
|
||||
codeqlScanning.Total = len(alerts)
|
||||
codeqlScanning.Audited = (codeqlScanning.Total - openStateCount)
|
||||
return codeqlScanning, nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package codeql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-github/v45/github"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type githubCodeqlScanningMock struct {
|
||||
}
|
||||
|
||||
func (g *githubCodeqlScanningMock) ListAlertsForRepo(ctx context.Context, owner, repo string, opts *github.AlertListOptions) ([]*github.Alert, *github.Response, error) {
|
||||
openState := "open"
|
||||
closedState := "closed"
|
||||
alerts := []*github.Alert{{State: &openState}, {State: &openState}, {State: &closedState}}
|
||||
return alerts, nil, nil
|
||||
}
|
||||
|
||||
type githubCodeqlScanningErrorMock struct {
|
||||
}
|
||||
|
||||
func (g *githubCodeqlScanningErrorMock) ListAlertsForRepo(ctx context.Context, owner, repo string, opts *github.AlertListOptions) ([]*github.Alert, *github.Response, error) {
|
||||
return []*github.Alert{}, nil, errors.New("Some error")
|
||||
}
|
||||
|
||||
func TestGetVulnerabilitiesFromClient(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Parallel()
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
ghCodeqlScanningMock := githubCodeqlScanningMock{}
|
||||
codeqlScanAuditInstance := NewCodeqlScanAuditInstance("", "", "", "", []string{})
|
||||
codeScanning, err := getVulnerabilitiesFromClient(ctx, &ghCodeqlScanningMock, "ref", &codeqlScanAuditInstance)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, codeScanning.Total)
|
||||
assert.Equal(t, 1, codeScanning.Audited)
|
||||
})
|
||||
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
ghCodeqlScanningErrorMock := githubCodeqlScanningErrorMock{}
|
||||
codeqlScanAuditInstance := NewCodeqlScanAuditInstance("", "", "", "", []string{})
|
||||
_, err := getVulnerabilitiesFromClient(ctx, &ghCodeqlScanningErrorMock, "ref", &codeqlScanAuditInstance)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package codeql
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type CodeqlAudit struct {
|
||||
ToolName string `json:"toolName"`
|
||||
RepositoryUrl string `json:"repositoryUrl"`
|
||||
RepositoryReferenceUrl string `json:"repositoryReferenceUrl"` //URL of PR or Branch where scan was performed
|
||||
CodeScanningLink string `json:"codeScanningLink"`
|
||||
ScanResults CodeqlScanning `json:"scanResults"`
|
||||
}
|
||||
|
||||
type CodeqlScanning struct {
|
||||
Total int `json:"total"`
|
||||
Audited int `json:"audited"`
|
||||
}
|
||||
|
||||
func WriteJSONReport(jsonReport CodeqlAudit, modulePath string) ([]piperutils.Path, error) {
|
||||
utils := piperutils.Files{}
|
||||
reportPaths := []piperutils.Path{}
|
||||
|
||||
reportsDirectory := filepath.Join(modulePath, "codeql")
|
||||
jsonComplianceReportPath := filepath.Join(reportsDirectory, "piper_codeql_report.json")
|
||||
if err := utils.MkdirAll(reportsDirectory, 0777); err != nil {
|
||||
return reportPaths, errors.Wrapf(err, "failed to create report directory")
|
||||
}
|
||||
|
||||
file, _ := json.Marshal(jsonReport)
|
||||
if err := utils.FileWrite(jsonComplianceReportPath, file, 0666); err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return reportPaths, errors.Wrapf(err, "failed to write codeql json compliance report")
|
||||
}
|
||||
|
||||
reportPaths = append(reportPaths, piperutils.Path{Name: "Codeql JSON Compliance Report", Target: jsonComplianceReportPath})
|
||||
|
||||
return reportPaths, nil
|
||||
}
|
||||
@@ -39,6 +39,15 @@ spec:
|
||||
- type: vaultSecret
|
||||
default: github
|
||||
name: githubVaultSecretName
|
||||
- name: githubApiUrl
|
||||
description: "Set the GitHub API URL."
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
type: string
|
||||
default: "https://api.github.com"
|
||||
- name: buildTool
|
||||
type: string
|
||||
description: Defines the build tool which is used for building the project.
|
||||
@@ -142,6 +151,22 @@ spec:
|
||||
- name: commonPipelineEnvironment
|
||||
param: git/remoteCommitId
|
||||
type: string
|
||||
- name: vulnerabilityThresholdTotal
|
||||
description: "Threashold for maximum number of allowed vulnerabilities."
|
||||
type: int
|
||||
default: 0
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: checkForCompliance
|
||||
description: "If set to true, the piper step checks for compliance based on vulnerability threadholds. Example - If total vulnerabilites are 10 and vulnerabilityThresholdTotal is set as 0, then the steps throws an compliance error."
|
||||
type: bool
|
||||
default: false
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
containers:
|
||||
- image: ""
|
||||
outputs:
|
||||
|
||||
Reference in New Issue
Block a user