Skip to content

Commit

Permalink
Add Xray Source Control Service (#451)
Browse files Browse the repository at this point in the history
  • Loading branch information
EyalDelarea authored Sep 12, 2023
1 parent f4caba9 commit 29324f6
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 29 deletions.
2 changes: 2 additions & 0 deletions scanpullrequest/scanallpullrequests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ func getMockClient(t *testing.T, frogbotMessages *[]string, mockParams ...MockPa
client.EXPECT().DeletePullRequestReviewComments(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
// Return private repositories visibility
client.EXPECT().GetRepositoryInfo(context.Background(), gomock.Any(), gomock.Any()).Return(vcsclient.RepositoryInfo{RepositoryVisibility: vcsclient.Private}, nil).AnyTimes()
// Return latest commit info for XSC context.
client.EXPECT().GetLatestCommit(context.Background(), params.repoOwner, params.repoName, gomock.Any()).Return(vcsclient.CommitInfo{}, nil).AnyTimes()
}
return client
}
Expand Down
59 changes: 31 additions & 28 deletions scanrepository/scanrepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ type ScanRepositoryCmd struct {
dryRun bool
// When dryRun is enabled, dryRunRepoPath specifies the repository local path to clone
dryRunRepoPath string
// The details of the current scan
details *utils.ScanDetails
// The scanDetails of the current scan
scanDetails *utils.ScanDetails
// The base working directory
baseWd string
// The git client the command performs git operations with
Expand All @@ -58,6 +58,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixRepository(repository *utils.Repository,
if err = cfp.setCommandPrerequisites(repository, branch, client); err != nil {
return
}
cfp.scanDetails.SetXscGitInfoContext(branch, repository.Project, client)
if err = cfp.scanAndFixBranch(repository); err != nil {
return
}
Expand All @@ -79,7 +80,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixBranch(repository *utils.Repository) (er
err = errors.Join(err, restoreBaseDir(), fileutils.RemoveTempDir(clonedRepoDir))
}()
for i := range repository.Projects {
cfp.details.Project = &repository.Projects[i]
cfp.scanDetails.Project = &repository.Projects[i]
cfp.projectTech = ""
if err = cfp.scanAndFixProject(repository); err != nil {
return
Expand All @@ -89,37 +90,39 @@ func (cfp *ScanRepositoryCmd) scanAndFixBranch(repository *utils.Repository) (er
}

func (cfp *ScanRepositoryCmd) setCommandPrerequisites(repository *utils.Repository, branch string, client vcsclient.VcsClient) (err error) {
cfp.details = utils.NewScanDetails(client, &repository.Server, &repository.Git).

cfp.scanDetails = utils.NewScanDetails(client, &repository.Server, &repository.Git).
SetXrayGraphScanParams(repository.Watches, repository.JFrogProjectKey).
SetFailOnInstallationErrors(*repository.FailOnSecurityIssues).
SetBaseBranch(branch).
SetFixableOnly(repository.FixableOnly).
SetMinSeverity(repository.MinSeverity)

cfp.aggregateFixes = repository.Git.AggregateFixes
cfp.OutputWriter = outputwriter.GetCompatibleOutputWriter(repository.GitProvider)
repositoryInfo, err := client.GetRepositoryInfo(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName)
repositoryInfo, err := client.GetRepositoryInfo(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName)
if err != nil {
return
}
remoteHttpsGitUrl := repositoryInfo.CloneInfo.HTTP
cfp.scanDetails.Git.RepositoryCloneUrl = repositoryInfo.CloneInfo.HTTP
cfp.gitManager, err = utils.NewGitManager().
SetAuth(cfp.details.Username, cfp.details.Token).
SetAuth(cfp.scanDetails.Username, cfp.scanDetails.Token).
SetDryRun(cfp.dryRun, cfp.dryRunRepoPath).
SetRemoteGitUrl(remoteHttpsGitUrl)
SetRemoteGitUrl(cfp.scanDetails.Git.RepositoryCloneUrl)
if err != nil {
return
}
_, err = cfp.gitManager.SetGitParams(cfp.details.Git)
_, err = cfp.gitManager.SetGitParams(cfp.scanDetails.Git)
return
}

func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) error {
var fixNeeded bool
// A map that contains the full project paths as a keys
// The value is a map of vulnerable package names -> the details of the vulnerable packages.
// That means we have a map of all the vulnerabilities that were found in a specific folder, along with their full details.
// The value is a map of vulnerable package names -> the scanDetails of the vulnerable packages.
// That means we have a map of all the vulnerabilities that were found in a specific folder, along with their full scanDetails.
vulnerabilitiesByPathMap := make(map[string]map[string]*utils.VulnerabilityDetails)
projectFullPathWorkingDirs := utils.GetFullPathWorkingDirs(cfp.details.Project.WorkingDirs, cfp.baseWd)
projectFullPathWorkingDirs := utils.GetFullPathWorkingDirs(cfp.scanDetails.Project.WorkingDirs, cfp.baseWd)
for _, fullPathWd := range projectFullPathWorkingDirs {
scanResults, err := cfp.scan(fullPathWd)
if err != nil {
Expand All @@ -129,7 +132,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) er
if repository.GitProvider.String() == vcsutils.GitHub.String() {
// Uploads Sarif results to GitHub in order to view the scan in the code scanning UI
// Currently available on GitHub only
if err = utils.UploadSarifResultsToGithubSecurityTab(scanResults, repository, cfp.details.BaseBranch(), cfp.details.Client()); err != nil {
if err = utils.UploadSarifResultsToGithubSecurityTab(scanResults, repository, cfp.scanDetails.BaseBranch(), cfp.scanDetails.Client()); err != nil {
log.Warn(err)
}
}
Expand All @@ -153,7 +156,7 @@ func (cfp *ScanRepositoryCmd) scanAndFixProject(repository *utils.Repository) er
// Audit the dependencies of the current commit.
func (cfp *ScanRepositoryCmd) scan(currentWorkingDir string) (*audit.Results, error) {
// Audit commit code
auditResults, err := cfp.details.RunInstallAndAudit(currentWorkingDir)
auditResults, err := cfp.scanDetails.RunInstallAndAudit(currentWorkingDir)
if err != nil {
return nil, err
}
Expand All @@ -172,7 +175,7 @@ func (cfp *ScanRepositoryCmd) getVulnerabilitiesMap(scanResults *xrayutils.Exten

// Nothing to fix, return
if len(vulnerabilitiesMap) == 0 {
log.Info("Didn't find vulnerable dependencies with existing fix versions for", cfp.details.RepoName)
log.Info("Didn't find vulnerable dependencies with existing fix versions for", cfp.scanDetails.RepoName)
}
return vulnerabilitiesMap, nil
}
Expand Down Expand Up @@ -216,7 +219,7 @@ func (cfp *ScanRepositoryCmd) fixProjectVulnerabilities(fullProjectPath string,
}

// After fixing the current vulnerability, checkout to the base branch to start fixing the next vulnerability
if e := cfp.gitManager.Checkout(cfp.details.BaseBranch()); e != nil {
if e := cfp.gitManager.Checkout(cfp.scanDetails.BaseBranch()); e != nil {
err = errors.Join(err, cfp.handleUpdatePackageErrors(e))
return
}
Expand Down Expand Up @@ -284,7 +287,7 @@ func (cfp *ScanRepositoryCmd) handleUpdatePackageErrors(err error) error {
func (cfp *ScanRepositoryCmd) fixSinglePackageAndCreatePR(vulnDetails *utils.VulnerabilityDetails) (err error) {
fixVersion := vulnDetails.SuggestedFixedVersion
log.Debug("Attempting to fix", vulnDetails.ImpactedDependencyName, "with", fixVersion)
fixBranchName, err := cfp.gitManager.GenerateFixBranchName(cfp.details.BaseBranch(), vulnDetails.ImpactedDependencyName, fixVersion)
fixBranchName, err := cfp.gitManager.GenerateFixBranchName(cfp.scanDetails.BaseBranch(), vulnDetails.ImpactedDependencyName, fixVersion)
if err != nil {
return
}
Expand Down Expand Up @@ -330,8 +333,8 @@ func (cfp *ScanRepositoryCmd) openFixingPullRequest(fixBranchName string, vulnDe
if err != nil {
return
}
log.Debug("Creating Pull Request form:", fixBranchName, " to:", cfp.details.BaseBranch())
return cfp.details.Client().CreatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, fixBranchName, cfp.details.BaseBranch(), pullRequestTitle, prBody)
log.Debug("Creating Pull Request form:", fixBranchName, " to:", cfp.scanDetails.BaseBranch())
return cfp.scanDetails.Client().CreatePullRequest(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName, fixBranchName, cfp.scanDetails.BaseBranch(), pullRequestTitle, prBody)
}

// openAggregatedPullRequest handles the opening or updating of a pull request when the aggregate mode is active.
Expand All @@ -349,11 +352,11 @@ func (cfp *ScanRepositoryCmd) openAggregatedPullRequest(fixBranchName string, pu
return
}
if pullRequestInfo == nil {
log.Info("Creating Pull Request from:", fixBranchName, "to:", cfp.details.BaseBranch())
return cfp.details.Client().CreatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, fixBranchName, cfp.details.BaseBranch(), pullRequestTitle, prBody)
log.Info("Creating Pull Request from:", fixBranchName, "to:", cfp.scanDetails.BaseBranch())
return cfp.scanDetails.Client().CreatePullRequest(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName, fixBranchName, cfp.scanDetails.BaseBranch(), pullRequestTitle, prBody)
}
log.Info("Updating Pull Request from:", fixBranchName, "to:", cfp.details.BaseBranch())
return cfp.details.Client().UpdatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, pullRequestTitle, prBody, "", int(pullRequestInfo.ID), vcsutils.Open)
log.Info("Updating Pull Request from:", fixBranchName, "to:", cfp.scanDetails.BaseBranch())
return cfp.scanDetails.Client().UpdatePullRequest(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName, pullRequestTitle, prBody, "", int(pullRequestInfo.ID), vcsutils.Open)
}

func (cfp *ScanRepositoryCmd) preparePullRequestDetails(vulnerabilitiesDetails ...*utils.VulnerabilityDetails) (string, string, error) {
Expand All @@ -378,7 +381,7 @@ func (cfp *ScanRepositoryCmd) preparePullRequestDetails(vulnerabilitiesDetails .

func (cfp *ScanRepositoryCmd) cloneRepositoryAndCheckoutToBranch() (tempWd string, restoreDir func() error, err error) {
if cfp.dryRun {
tempWd = filepath.Join(cfp.dryRunRepoPath, cfp.details.RepoName)
tempWd = filepath.Join(cfp.dryRunRepoPath, cfp.scanDetails.RepoName)
} else {
// Create temp working directory
if tempWd, err = fileutils.CreateTempDir(); err != nil {
Expand All @@ -388,7 +391,7 @@ func (cfp *ScanRepositoryCmd) cloneRepositoryAndCheckoutToBranch() (tempWd strin
log.Debug("Created temp working directory:", tempWd)

// Clone the content of the repo to the new working directory
if err = cfp.gitManager.Clone(tempWd, cfp.details.BaseBranch()); err != nil {
if err = cfp.gitManager.Clone(tempWd, cfp.scanDetails.BaseBranch()); err != nil {
return
}

Expand Down Expand Up @@ -471,7 +474,7 @@ func (cfp *ScanRepositoryCmd) updatePackageToFixedVersion(vulnDetails *utils.Vul

handler := cfp.handlers[vulnDetails.Technology]
if handler == nil {
handler = packagehandlers.GetCompatiblePackageHandler(vulnDetails, cfp.details)
handler = packagehandlers.GetCompatiblePackageHandler(vulnDetails, cfp.scanDetails)
cfp.handlers[vulnDetails.Technology] = handler
} else if _, unsupported := handler.(*packagehandlers.UnsupportedPackageHandler); unsupported {
return
Expand All @@ -497,7 +500,7 @@ func (cfp *ScanRepositoryCmd) getRemoteBranchScanHash(prBody string) string {
}

func (cfp *ScanRepositoryCmd) getOpenPullRequestBySourceBranch(branchName string) (prInfo *vcsclient.PullRequestInfo, err error) {
list, err := cfp.details.Client().ListOpenPullRequestsWithBody(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName)
list, err := cfp.scanDetails.Client().ListOpenPullRequestsWithBody(context.Background(), cfp.scanDetails.RepoOwner, cfp.scanDetails.RepoName)
if err != nil {
return
}
Expand Down Expand Up @@ -542,7 +545,7 @@ func (cfp *ScanRepositoryCmd) aggregateFixAndOpenPullRequest(vulnerabilitiesMap
}
}
log.Info("-----------------------------------------------------------------")
if e := cfp.gitManager.Checkout(cfp.details.BaseBranch()); e != nil {
if e := cfp.gitManager.Checkout(cfp.scanDetails.BaseBranch()); e != nil {
err = errors.Join(err, e)
}
return
Expand Down
2 changes: 1 addition & 1 deletion scanrepository/scanrepository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ func TestPackageTypeFromScan(t *testing.T) {
Project: &frogbotParams.Projects[0],
ServerDetails: &frogbotParams.Server,
}
testScan.details = &scanSetup
testScan.scanDetails = &scanSetup
scanResponse, err := testScan.scan(tmpDir)
assert.NoError(t, err)
verifyTechnologyNaming(t, scanResponse.ExtendedScanResults.XrayResults, pkg.packageType)
Expand Down
1 change: 1 addition & 0 deletions utils/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ type Git struct {
EmailAuthor string `yaml:"emailAuthor,omitempty"`
AggregateFixes bool `yaml:"aggregateFixes,omitempty"`
PullRequestDetails vcsclient.PullRequestInfo
RepositoryCloneUrl string
}

func (g *Git) setDefaultsIfNeeded(gitParamsFromEnv *Git) (err error) {
Expand Down
40 changes: 40 additions & 0 deletions utils/scandetails.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package utils

import (
"context"
"errors"
"fmt"
"github.com/jfrog/froggit-go/vcsclient"
Expand All @@ -9,6 +10,7 @@ import (
xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils"
"github.com/jfrog/jfrog-client-go/utils/log"
"github.com/jfrog/jfrog-client-go/xray/services"

"os/exec"
"path/filepath"
"strings"
Expand Down Expand Up @@ -171,6 +173,44 @@ func (sc *ScanDetails) runInstallCommand() ([]byte, error) {
return MapTechToResolvingFunc[sc.InstallCommandName](sc)
}

func (sc *ScanDetails) SetXscGitInfoContext(scannedBranch, gitProject string, client vcsclient.VcsClient) *ScanDetails {
XscGitInfoContext, err := sc.createGitInfoContext(scannedBranch, gitProject, client)
if err != nil {
log.Debug("failed trying to create GitInfoContext for Xsc with the following error: ", err.Error())
return sc
}
sc.XscGitInfoContext = XscGitInfoContext
return sc
}

// CreateGitInfoContext Creates GitInfoContext for XSC scans, this is optional.
// ScannedBranch - name of the branch we are scanning.
// GitProject - [Optional] relevant for azure repos and Bitbucket server.
// Client vscClient
func (sc *ScanDetails) createGitInfoContext(scannedBranch, gitProject string, client vcsclient.VcsClient) (gitInfo *services.XscGitInfoContext, err error) {
latestCommit, err := client.GetLatestCommit(context.Background(), sc.RepoOwner, sc.RepoName, scannedBranch)
if err != nil {
return nil, fmt.Errorf("failed getting latest commit, repository: %s, branch: %s. error: %s ", sc.RepoName, scannedBranch, err.Error())
}
// In some VCS providers, there are no git projects, fallback to the repository owner.
if gitProject == "" {
gitProject = sc.RepoOwner
}
gitInfo = &services.XscGitInfoContext{
// Use Clone URLs as Repo Url, on browsers it will redirect to repository URLS.
GitRepoUrl: sc.Git.RepositoryCloneUrl,
GitRepoName: sc.RepoName,
GitProvider: sc.GitProvider.String(),
GitProject: gitProject,
BranchName: scannedBranch,
LastCommit: latestCommit.Url,
CommitHash: latestCommit.Hash,
CommitMessage: latestCommit.Message,
CommitAuthor: latestCommit.AuthorName,
}
return
}

func GetFullPathWorkingDirs(workingDirs []string, baseWd string) []string {
var fullPathWds []string
if len(workingDirs) != 0 {
Expand Down

0 comments on commit 29324f6

Please sign in to comment.