1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-30 05:59:39 +02:00

fix(codeqlExecuteScan): added waiting for the SARIF file upload (#4409)

* added waiting for the sarif file uploaded & tests

* increased polling time, added timeout for waiting response from server & tests

* fixed handling error while waiting sarif uploaded

* added params for checking sarif uploaded & refactor

* added test logs

* fixed logs and test

* added returning missed error

* changed params descriptions and server response error processing processing

* fixed retrying logic

* increased polling timeout params & refactored
This commit is contained in:
Daria Kuznetsova 2023-06-20 14:50:28 +02:00 committed by GitHub
parent 8b36ae70e7
commit 6eb4c2e72d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 244 additions and 22 deletions

View File

@ -1,11 +1,13 @@
package cmd
import (
"bytes"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/SAP/jenkins-library/pkg/codeql"
"github.com/SAP/jenkins-library/pkg/command"
@ -36,6 +38,9 @@ type codeqlExecuteScanUtilsBundle struct {
*piperutils.Files
}
const sarifUploadComplete = "complete"
const sarifUploadFailed = "failed"
func newCodeqlExecuteScanUtils() codeqlExecuteScanUtils {
utils := codeqlExecuteScanUtilsBundle{
Command: &command.Command{},
@ -160,7 +165,7 @@ func getToken(config *codeqlExecuteScanOptions) (bool, string) {
return false, ""
}
func uploadResults(config *codeqlExecuteScanOptions, repoInfo RepoInfo, token string, utils codeqlExecuteScanUtils) error {
func uploadResults(config *codeqlExecuteScanOptions, repoInfo RepoInfo, token string, utils codeqlExecuteScanUtils) (string, error) {
cmd := []string{"github", "upload-results", "--sarif=" + filepath.Join(config.ModulePath, "target", "codeqlReport.sarif")}
if config.GithubToken != "" {
@ -185,13 +190,49 @@ func uploadResults(config *codeqlExecuteScanOptions, repoInfo RepoInfo, token st
//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.
var buffer bytes.Buffer
utils.Stdout(&buffer)
err := execute(utils, cmd, GeneralConfig.Verbose)
if err != nil {
log.Entry().Error("failed to upload sarif results")
return err
return "", err
}
utils.Stdout(log.Writer())
return nil
url := buffer.String()
return strings.TrimSpace(url), nil
}
func waitSarifUploaded(config *codeqlExecuteScanOptions, codeqlSarifUploader codeql.CodeqlSarifUploader) error {
maxRetries := config.SarifCheckMaxRetries
retryInterval := time.Duration(config.SarifCheckRetryInterval) * time.Second
log.Entry().Info("waiting for the SARIF to upload")
i := 1
for {
sarifStatus, err := codeqlSarifUploader.GetSarifStatus()
if err != nil {
return err
}
log.Entry().Infof("the SARIF processing status: %s", sarifStatus.ProcessingStatus)
if sarifStatus.ProcessingStatus == sarifUploadComplete {
return nil
}
if sarifStatus.ProcessingStatus == sarifUploadFailed {
for e := range sarifStatus.Errors {
log.Entry().Error(e)
}
return errors.New("failed to upload sarif file")
}
if i <= maxRetries {
log.Entry().Infof("still waiting for the SARIF to upload: retrying in %d seconds... (retry %d/%d)", config.SarifCheckRetryInterval, i, maxRetries)
time.Sleep(retryInterval)
i++
continue
}
return errors.New("failed to check sarif uploading status: max retries reached")
}
}
func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telemetry.CustomData, utils codeqlExecuteScanUtils) ([]piperutils.Path, error) {
@ -275,11 +316,15 @@ func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telem
return reports, errors.New("failed running upload-results as githubToken was not specified")
}
err = uploadResults(config, repoInfo, token, utils)
sarifUrl, err := uploadResults(config, repoInfo, token, utils)
if err != nil {
return reports, err
}
codeqlSarifUploader := codeql.NewCodeqlSarifUploaderInstance(sarifUrl, token)
err = waitSarifUploaded(config, &codeqlSarifUploader)
if err != nil {
return reports, errors.Wrap(err, "failed to upload sarif")
}
if config.CheckForCompliance {
codeqlScanAuditInstance := codeql.NewCodeqlScanAuditInstance(repoInfo.serverUrl, repoInfo.owner, repoInfo.repo, token, []string{})
@ -294,7 +339,7 @@ func runCodeqlExecuteScan(config *codeqlExecuteScanOptions, telemetryData *telem
return reports, errors.Wrap(err, "failed to write json compliance report")
}
unaudited := (scanResults.Total - scanResults.Audited)
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)
return reports, errors.Errorf(msg)

View File

@ -28,6 +28,8 @@ type codeqlExecuteScanOptions struct {
Database string `json:"database,omitempty"`
QuerySuite string `json:"querySuite,omitempty"`
UploadResults bool `json:"uploadResults,omitempty"`
SarifCheckMaxRetries int `json:"sarifCheckMaxRetries,omitempty"`
SarifCheckRetryInterval int `json:"sarifCheckRetryInterval,omitempty"`
Threads string `json:"threads,omitempty"`
Ram string `json:"ram,omitempty"`
AnalyzedRef string `json:"analyzedRef,omitempty"`
@ -183,6 +185,8 @@ func addCodeqlExecuteScanFlags(cmd *cobra.Command, stepConfig *codeqlExecuteScan
cmd.Flags().StringVar(&stepConfig.Database, "database", `codeqlDB`, "Path to the CodeQL database to create. This directory will be created, and must not already exist.")
cmd.Flags().StringVar(&stepConfig.QuerySuite, "querySuite", os.Getenv("PIPER_querySuite"), "The name of a CodeQL query suite. If omitted, the default query suite for the language of the database being analyzed will be used.")
cmd.Flags().BoolVar(&stepConfig.UploadResults, "uploadResults", false, "Allows you to upload codeql SARIF results to your github project. You will need to set githubToken for this.")
cmd.Flags().IntVar(&stepConfig.SarifCheckMaxRetries, "sarifCheckMaxRetries", 10, "Maximum number of retries when waiting for the server to finish processing the SARIF upload. Only relevant, if checkForCompliance is enabled.")
cmd.Flags().IntVar(&stepConfig.SarifCheckRetryInterval, "sarifCheckRetryInterval", 30, "")
cmd.Flags().StringVar(&stepConfig.Threads, "threads", `0`, "Use this many threads for the codeql operations.")
cmd.Flags().StringVar(&stepConfig.Ram, "ram", os.Getenv("PIPER_ram"), "Use this much ram (MB) for the codeql operations.")
cmd.Flags().StringVar(&stepConfig.AnalyzedRef, "analyzedRef", os.Getenv("PIPER_analyzedRef"), "Name of the ref that was analyzed.")
@ -296,6 +300,24 @@ func codeqlExecuteScanMetadata() config.StepData {
Aliases: []config.Alias{},
Default: false,
},
{
Name: "sarifCheckMaxRetries",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "int",
Mandatory: false,
Aliases: []config.Alias{},
Default: 10,
},
{
Name: "sarifCheckRetryInterval",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "int",
Mandatory: false,
Aliases: []config.Alias{},
Default: 30,
},
{
Name: "threads",
ResourceRef: []config.ResourceReference{},

View File

@ -6,9 +6,12 @@ package cmd
import (
"fmt"
"testing"
"time"
"github.com/SAP/jenkins-library/pkg/codeql"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/orchestrator"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
@ -45,12 +48,6 @@ func TestRunCodeqlExecuteScan(t *testing.T) {
assert.Error(t, err)
})
t.Run("Check for compliace fails as repository not specified", func(t *testing.T) {
config := codeqlExecuteScanOptions{BuildTool: "maven", ModulePath: "./", UploadResults: true, GithubToken: "test", CheckForCompliance: true}
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
assert.Error(t, err)
})
t.Run("Custom buildtool", func(t *testing.T) {
config := codeqlExecuteScanOptions{BuildTool: "custom", Language: "javascript", ModulePath: "./"}
_, err := runCodeqlExecuteScan(&config, nil, newCodeqlExecuteScanTestsUtils())
@ -339,3 +336,87 @@ func TestCreateToolRecordCodeql(t *testing.T) {
assert.Error(t, err)
})
}
func TestWaitSarifUploaded(t *testing.T) {
t.Parallel()
config := codeqlExecuteScanOptions{SarifCheckRetryInterval: 1, SarifCheckMaxRetries: 5}
t.Run("Fast complete upload", func(t *testing.T) {
codeqlScanAuditMock := CodeqlSarifUploaderMock{counter: 0}
timerStart := time.Now()
err := waitSarifUploaded(&config, &codeqlScanAuditMock)
assert.Less(t, time.Now().Sub(timerStart), time.Second)
assert.NoError(t, err)
})
t.Run("Long completed upload", func(t *testing.T) {
codeqlScanAuditMock := CodeqlSarifUploaderMock{counter: 2}
timerStart := time.Now()
err := waitSarifUploaded(&config, &codeqlScanAuditMock)
assert.GreaterOrEqual(t, time.Now().Sub(timerStart), time.Second*2)
assert.NoError(t, err)
})
t.Run("Failed upload", func(t *testing.T) {
codeqlScanAuditMock := CodeqlSarifUploaderMock{counter: -1}
err := waitSarifUploaded(&config, &codeqlScanAuditMock)
assert.Error(t, err)
assert.ErrorContains(t, err, "failed to upload sarif file")
})
t.Run("Error while checking sarif uploading", func(t *testing.T) {
codeqlScanAuditErrorMock := CodeqlSarifUploaderErrorMock{counter: -1}
err := waitSarifUploaded(&config, &codeqlScanAuditErrorMock)
assert.Error(t, err)
assert.ErrorContains(t, err, "test error")
})
t.Run("Completed upload after getting errors from server", func(t *testing.T) {
codeqlScanAuditErrorMock := CodeqlSarifUploaderErrorMock{counter: 3}
err := waitSarifUploaded(&config, &codeqlScanAuditErrorMock)
assert.NoError(t, err)
})
t.Run("Max retries reached", func(t *testing.T) {
codeqlScanAuditErrorMock := CodeqlSarifUploaderErrorMock{counter: 6}
err := waitSarifUploaded(&config, &codeqlScanAuditErrorMock)
assert.Error(t, err)
assert.ErrorContains(t, err, "max retries reached")
})
}
type CodeqlSarifUploaderMock struct {
counter int
}
func (c *CodeqlSarifUploaderMock) GetSarifStatus() (codeql.SarifFileInfo, error) {
if c.counter == 0 {
return codeql.SarifFileInfo{
ProcessingStatus: "complete",
Errors: nil,
}, nil
}
if c.counter == -1 {
return codeql.SarifFileInfo{
ProcessingStatus: "failed",
Errors: []string{"upload error"},
}, nil
}
c.counter--
return codeql.SarifFileInfo{
ProcessingStatus: "pending",
Errors: nil,
}, nil
}
type CodeqlSarifUploaderErrorMock struct {
counter int
}
func (c *CodeqlSarifUploaderErrorMock) GetSarifStatus() (codeql.SarifFileInfo, error) {
if c.counter == -1 {
return codeql.SarifFileInfo{}, errors.New("test error")
}
if c.counter == 0 {
return codeql.SarifFileInfo{
ProcessingStatus: "complete",
Errors: nil,
}, nil
}
c.counter--
return codeql.SarifFileInfo{ProcessingStatus: "Service unavailable"}, nil
}

View File

@ -56,12 +56,6 @@ func (g *githubCodeqlScanningMock) ListAlertsForRepo(ctx context.Context, owner,
return alerts, &response, nil
}
func (g *githubCodeqlScanningMock) ListAnalysesForRepo(ctx context.Context, owner, repo string, opts *github.AnalysesListOptions) ([]*github.ScanningAnalysis, *github.Response, error) {
resultsCount := 3
analysis := []*github.ScanningAnalysis{{ResultsCount: &resultsCount}}
return analysis, nil, nil
}
type githubCodeqlScanningErrorMock struct {
}
@ -69,10 +63,6 @@ func (g *githubCodeqlScanningErrorMock) ListAlertsForRepo(ctx context.Context, o
return []*github.Alert{}, nil, errors.New("Some error")
}
func (g *githubCodeqlScanningErrorMock) ListAnalysesForRepo(ctx context.Context, owner, repo string, opts *github.AnalysesListOptions) ([]*github.ScanningAnalysis, *github.Response, error) {
return []*github.ScanningAnalysis{}, nil, errors.New("Some error")
}
func TestGetVulnerabilitiesFromClient(t *testing.T) {
ctx := context.Background()
t.Parallel()

View File

@ -0,0 +1,68 @@
package codeql
import (
"encoding/json"
"io"
"net/http"
)
type CodeqlSarifUploader interface {
GetSarifStatus() (SarifFileInfo, error)
}
func NewCodeqlSarifUploaderInstance(url, token string) CodeqlSarifUploaderInstance {
return CodeqlSarifUploaderInstance{
url: url,
token: token,
}
}
type CodeqlSarifUploaderInstance struct {
url string
token string
}
func (codeqlSarifUploader *CodeqlSarifUploaderInstance) GetSarifStatus() (SarifFileInfo, error) {
return getSarifUploadingStatus(codeqlSarifUploader.url, codeqlSarifUploader.token)
}
type SarifFileInfo struct {
ProcessingStatus string `json:"processing_status"`
Errors []string `json:"errors"`
}
const internalServerError = "Internal server error"
func getSarifUploadingStatus(sarifURL, token string) (SarifFileInfo, error) {
client := http.Client{}
req, err := http.NewRequest("GET", sarifURL, nil)
if err != nil {
return SarifFileInfo{}, err
}
req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("Accept", "application/vnd.github+json")
req.Header.Add("X-GitHub-Api-Version", "2022-11-28")
resp, err := client.Do(req)
if err != nil {
return SarifFileInfo{}, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusServiceUnavailable || resp.StatusCode == http.StatusBadGateway ||
resp.StatusCode == http.StatusGatewayTimeout {
return SarifFileInfo{ProcessingStatus: internalServerError}, nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return SarifFileInfo{}, err
}
sarifInfo := SarifFileInfo{}
err = json.Unmarshal(body, &sarifInfo)
if err != nil {
return SarifFileInfo{}, err
}
return sarifInfo, nil
}

View File

@ -104,6 +104,22 @@ spec:
- STAGES
- STEPS
default: false
- name: sarifCheckMaxRetries
type: int
description: "Maximum number of retries when waiting for the server to finish processing the SARIF upload. Only relevant, if checkForCompliance is enabled."
scope:
- PARAMETERS
- STAGES
- STEPS
default: 10
- name: sarifCheckRetryInterval
type: int
descriptoin: "Interval in seconds between retries when waiting for the server to finish processing the SARIF upload. Only relevant, if checkForCompliance is enabled."
scope:
- PARAMETERS
- STAGES
- STEPS
default: 30
- name: threads
type: string
description: "Use this many threads for the codeql operations."