diff --git a/audit_test.go b/audit_test.go index 51bafd55..1126b385 100644 --- a/audit_test.go +++ b/audit_test.go @@ -3,7 +3,10 @@ package main import ( "encoding/json" "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-security/formats" + "github.com/jfrog/jfrog-cli-security/utils" + xscservices "github.com/jfrog/jfrog-client-go/xsc/services" "os" "os/exec" "path/filepath" @@ -35,7 +38,7 @@ func TestXrayAuditNpmSimpleJson(t *testing.T) { } func testXrayAuditNpm(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() npmProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "npm", "npm") @@ -61,7 +64,7 @@ func TestXrayAuditPnpmSimpleJson(t *testing.T) { } func testXrayAuditPnpm(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() npmProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "npm", "npm-no-lock") @@ -117,7 +120,7 @@ func TestXrayAuditYarnV1SimpleJson(t *testing.T) { } func testXrayAuditYarn(t *testing.T, projectDirName string, yarnCmd func()) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() yarnProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "yarn", projectDirName) @@ -232,7 +235,7 @@ func TestXrayAuditNugetSimpleJson(t *testing.T) { } func testXrayAuditNuget(t *testing.T, projectName, format string, restoreTech string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() projectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "nuget", projectName) @@ -261,7 +264,7 @@ func TestXrayAuditGradleSimpleJson(t *testing.T) { } func testXrayAuditGradle(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() gradleProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "gradle", "gradle") @@ -285,7 +288,7 @@ func TestXrayAuditMavenSimpleJson(t *testing.T) { } func testXrayAuditMaven(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() mvnProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "maven", "maven") @@ -299,7 +302,7 @@ func testXrayAuditMaven(t *testing.T, format string) string { } func TestXrayAuditNoTech(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() prevWd := securityTestUtils.ChangeWD(t, tempDirPath) @@ -310,7 +313,7 @@ func TestXrayAuditNoTech(t *testing.T) { } func TestXrayAuditMultiProjects(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() multiProject := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects") @@ -350,7 +353,7 @@ func TestXrayAuditPipSimpleJsonWithRequirementsFile(t *testing.T) { } func testXrayAuditPip(t *testing.T, format, requirementsFile string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() pipProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "python", "pip", "pip-project") @@ -379,7 +382,7 @@ func TestXrayAuditPipenvSimpleJson(t *testing.T) { } func testXrayAuditPipenv(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() pipenvProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "python", "pipenv", "pipenv-project") @@ -403,7 +406,7 @@ func TestXrayAuditPoetrySimpleJson(t *testing.T) { } func testXrayAuditPoetry(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() poetryProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "python", "poetry", "poetry-project") @@ -445,7 +448,7 @@ func TestXrayAuditJasNoViolationsSimpleJson(t *testing.T) { } func testXrayAuditJas(t *testing.T, format string, project string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() projectDir := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), filepath.Join("projects", project)) @@ -462,7 +465,7 @@ func testXrayAuditJas(t *testing.T, format string, project string) string { } func TestXrayAuditDetectTech(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() mvnProjectPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "maven", "maven") @@ -480,7 +483,7 @@ func TestXrayAuditDetectTech(t *testing.T) { } func TestXrayRecursiveScan(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() projectDir := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers") @@ -514,3 +517,35 @@ func TestXrayRecursiveScan(t *testing.T) { // We anticipate receiving an array with a length of 2 to confirm that we have obtained results from two distinct inner projects. assert.Len(t, results, 2) } + +func TestXscAnalyticsForAudit(t *testing.T) { + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, xscservices.AnalyticsMetricsMinXscVersion) + reportUsageCallBack := clientTests.SetEnvWithCallbackAndAssert(t, coreutils.ReportUsage, "true") + defer reportUsageCallBack() + // Scan npm project and verify that analytics general event were sent to XSC. + output := testXrayAuditNpm(t, string(format.SimpleJson)) + validateAnalyticsBasicEvent(t, output) +} + +func validateAnalyticsBasicEvent(t *testing.T, output string) { + // Get MSI. + var results formats.SimpleJsonResults + err := json.Unmarshal([]byte(output), &results) + assert.NoError(t, err) + + // Verify analytics metrics. + am := utils.NewAnalyticsMetricsService(securityTests.XscDetails) + assert.NotNil(t, am) + assert.NotEmpty(t, results.MultiScanId) + event, err := am.GetGeneralEvent(results.MultiScanId) + assert.NoError(t, err) + + // Event creation and addition information. + assert.Equal(t, "cli", event.Product) + assert.Equal(t, 1, event.EventType) + assert.NotEmpty(t, event.AnalyzerManagerVersion) + assert.NotEmpty(t, event.EventStatus) + // The information that was added after updating the event with the scan's results. + assert.NotEmpty(t, event.TotalScanDuration) + assert.True(t, event.TotalFindings > 0) +} diff --git a/cli/scancommands.go b/cli/scancommands.go index ebc6aee6..a1bdf275 100644 --- a/cli/scancommands.go +++ b/cli/scancommands.go @@ -349,6 +349,8 @@ func createAuditCmd(c *components.Context) (*audit.AuditCommand, error) { if err != nil { return nil, err } + auditCmd.SetAnalyticsMetricsService(utils.NewAnalyticsMetricsService(serverDetails)) + auditCmd.SetTargetRepoPath(addTrailingSlashToRepoPathIfNeeded(c)). SetProject(c.GetStringFlagValue(flags.Project)). SetIncludeVulnerabilities(shouldIncludeVulnerabilities(c)). diff --git a/commands/audit/audit.go b/commands/audit/audit.go index 4d57bd26..a9e05bd6 100644 --- a/commands/audit/audit.go +++ b/commands/audit/audit.go @@ -2,28 +2,30 @@ package audit import ( "errors" - "github.com/jfrog/jfrog-cli-security/scangraph" - "os" - + "fmt" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-security/scangraph" "github.com/jfrog/jfrog-cli-security/utils" clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray" "github.com/jfrog/jfrog-client-go/xray/services" + xscservices "github.com/jfrog/jfrog-client-go/xsc/services" "golang.org/x/sync/errgroup" + "os" xrayutils "github.com/jfrog/jfrog-cli-security/utils" ) type AuditCommand struct { - watches []string - projectKey string - targetRepoPath string - IncludeVulnerabilities bool - IncludeLicenses bool - Fail bool - PrintExtendedTable bool + watches []string + projectKey string + targetRepoPath string + IncludeVulnerabilities bool + IncludeLicenses bool + Fail bool + PrintExtendedTable bool + analyticsMetricsService *xrayutils.AnalyticsMetricsService AuditParams } @@ -66,6 +68,11 @@ func (auditCmd *AuditCommand) SetPrintExtendedTable(printExtendedTable bool) *Au return auditCmd } +func (auditCmd *AuditCommand) SetAnalyticsMetricsService(analyticsMetricsService *xrayutils.AnalyticsMetricsService) *AuditCommand { + auditCmd.analyticsMetricsService = analyticsMetricsService + return auditCmd +} + func (auditCmd *AuditCommand) CreateXrayGraphScanParams() *services.XrayGraphScanParams { params := &services.XrayGraphScanParams{ RepoPath: auditCmd.targetRepoPath, @@ -79,6 +86,17 @@ func (auditCmd *AuditCommand) CreateXrayGraphScanParams() *services.XrayGraphSca } params.IncludeVulnerabilities = auditCmd.IncludeVulnerabilities params.IncludeLicenses = auditCmd.IncludeLicenses + params.MultiScanId = auditCmd.analyticsMetricsService.GetMsi() + if params.MultiScanId != "" { + xscManager := auditCmd.analyticsMetricsService.XscManager() + if xscManager != nil { + version, err := xscManager.GetVersion() + if err != nil { + log.Debug(fmt.Sprintf("Can't get XSC version for xray graph scan params. Cause: %s", err.Error())) + } + params.XscVersion = version + } + } return params } @@ -90,6 +108,8 @@ func (auditCmd *AuditCommand) Run() (err error) { return } + // Should be called before creating the audit params, so the params will contain XSC information. + auditCmd.analyticsMetricsService.AddGeneralEvent(auditCmd.analyticsMetricsService.CreateGeneralEvent(xscservices.CliProduct, xscservices.CliEventType)) auditParams := NewAuditParams(). SetXrayGraphScanParams(auditCmd.CreateXrayGraphScanParams()). SetWorkingDirs(workingDirs). @@ -98,10 +118,12 @@ func (auditCmd *AuditCommand) Run() (err error) { SetGraphBasicParams(auditCmd.AuditBasicParams). SetThirdPartyApplicabilityScan(auditCmd.thirdPartyApplicabilityScan) auditParams.SetIsRecursiveScan(isRecursiveScan).SetExclusions(auditCmd.Exclusions()) + auditResults, err := RunAudit(auditParams) if err != nil { return } + auditCmd.analyticsMetricsService.UpdateGeneralEvent(auditCmd.analyticsMetricsService.CreateXscAnalyticsGeneralEventFinalizeFromAuditResults(auditResults)) if auditCmd.Progress() != nil { if err = auditCmd.Progress().Quit(); err != nil { return @@ -171,12 +193,7 @@ func RunAudit(auditParams *AuditParams) (results *xrayutils.Results, err error) errGroup.Go(utils.DownloadAnalyzerManagerIfNeeded) } - if auditParams.xrayGraphScanParams.XscGitInfoContext != nil { - if err = xrayutils.SendXscGitInfoRequestIfEnabled(auditParams.xrayGraphScanParams, xrayManager); err != nil { - return nil, err - } - results.MultiScanId = auditParams.xrayGraphScanParams.MultiScanId - } + results.MultiScanId = auditParams.XrayGraphScanParams().MultiScanId // The sca scan doesn't require the analyzer manager, so it can run separately from the analyzer manager download routine. results.ScaError = runScaScan(auditParams, results) @@ -188,7 +205,7 @@ func RunAudit(auditParams *AuditParams) (results *xrayutils.Results, err error) // Run scanners only if the user is entitled for Advanced Security if results.ExtendedScanResults.EntitledForJas { - results.JasError = runJasScannersAndSetResults(results, auditParams.DirectDependencies(), serverDetails, auditParams.workingDirs, auditParams.Progress(), auditParams.thirdPartyApplicabilityScan) + results.JasError = runJasScannersAndSetResults(results, auditParams.DirectDependencies(), serverDetails, auditParams.workingDirs, auditParams.Progress(), auditParams.thirdPartyApplicabilityScan, auditParams.XrayGraphScanParams().MultiScanId) } return } diff --git a/commands/audit/jas/common.go b/commands/audit/jas/common.go index 55e2fdc4..bf66b271 100644 --- a/commands/audit/jas/common.go +++ b/commands/audit/jas/common.go @@ -3,6 +3,7 @@ package jas import ( "errors" "fmt" + clientutils "github.com/jfrog/jfrog-client-go/utils" "os" "path/filepath" "strings" @@ -274,3 +275,43 @@ func GetExcludePatterns(module jfrogappsconfig.Module, scanner *jfrogappsconfig. } return excludePatterns } + +func SetAnalyticsMetricsDataForAnalyzerManager(msi string, technologies []coreutils.Technology) func() { + errMsg := "failed %s %s environment variable. Cause: %s" + resetAnalyzerManageJfMsiVar, err := clientutils.SetEnvWithResetCallback(utils.JfMsiEnvVariable, msi) + if err != nil { + log.Debug(fmt.Sprintf(errMsg, "setting", utils.JfMsiEnvVariable, err.Error())) + } + if len(technologies) != 1 { + // Only report analytics for one technology at a time. + return func() { + err = resetAnalyzerManageJfMsiVar() + if err != nil { + log.Debug(fmt.Sprintf(errMsg, "restoring", utils.JfMsiEnvVariable, err.Error())) + } + } + } + technology := technologies[0] + resetAnalyzerManagerPackageManagerVar, err := clientutils.SetEnvWithResetCallback(utils.JfPackageManagerEnvVariable, technology.String()) + if err != nil { + log.Debug(fmt.Sprintf(errMsg, "setting", utils.JfPackageManagerEnvVariable, err.Error())) + } + resetAnalyzerManagerLanguageVar, err := clientutils.SetEnvWithResetCallback(utils.JfLanguageEnvVariable, string(utils.TechnologyToLanguage(technology))) + if err != nil { + log.Debug(fmt.Sprintf(errMsg, "setting", utils.JfLanguageEnvVariable, err.Error())) + } + return func() { + err = resetAnalyzerManageJfMsiVar() + if err != nil { + log.Debug(fmt.Sprintf(errMsg, "restoring", utils.JfMsiEnvVariable, err.Error())) + } + err = resetAnalyzerManagerPackageManagerVar() + if err != nil { + log.Debug(fmt.Sprintf(errMsg, "restoring", utils.JfPackageManagerEnvVariable, err.Error())) + } + err = resetAnalyzerManagerLanguageVar() + if err != nil { + log.Debug(fmt.Sprintf(errMsg, "restoring", utils.JfLanguageEnvVariable, err.Error())) + } + } +} diff --git a/commands/audit/jas/common_test.go b/commands/audit/jas/common_test.go index 98129bcf..6b6ea9da 100644 --- a/commands/audit/jas/common_test.go +++ b/commands/audit/jas/common_test.go @@ -1,6 +1,8 @@ package jas import ( + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "os" "testing" "github.com/jfrog/jfrog-cli-security/utils" @@ -88,3 +90,42 @@ func TestAddScoreToRunRules(t *testing.T) { assert.Equal(t, test.expectedOutput, test.sarifRun.Tool.Driver.Rules) } } + +func TestSetAnalyticsMetricsDataForAnalyzerManager(t *testing.T) { + type args struct { + msi string + technologies []coreutils.Technology + } + tests := []struct { + name string + args args + want func() + }{ + {name: "One valid technology", args: args{msi: "msi", technologies: []coreutils.Technology{coreutils.Maven}}, want: func() { + assert.Equal(t, string(coreutils.Maven), os.Getenv(utils.JfPackageManagerEnvVariable)) + assert.Equal(t, string(utils.Java), os.Getenv(utils.JfLanguageEnvVariable)) + assert.Equal(t, "msi", os.Getenv(utils.JfMsiEnvVariable)) + }}, + {name: "Multiple technologies", args: args{msi: "msi", technologies: []coreutils.Technology{coreutils.Maven, coreutils.Npm}}, want: func() { + assert.Equal(t, "", os.Getenv(utils.JfPackageManagerEnvVariable)) + assert.Equal(t, "", os.Getenv(utils.JfLanguageEnvVariable)) + assert.Equal(t, "msi", os.Getenv(utils.JfMsiEnvVariable)) + }}, + {name: "Zero technologies", args: args{msi: "msi", technologies: []coreutils.Technology{}}, want: func() { + assert.Equal(t, "", os.Getenv(utils.JfPackageManagerEnvVariable)) + assert.Equal(t, "", os.Getenv(utils.JfLanguageEnvVariable)) + assert.Equal(t, "msi", os.Getenv(utils.JfMsiEnvVariable)) + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + callback := SetAnalyticsMetricsDataForAnalyzerManager(tt.args.msi, tt.args.technologies) + tt.want() + callback() + assert.Equal(t, "", os.Getenv(utils.JfPackageManagerEnvVariable)) + assert.Equal(t, "", os.Getenv(utils.JfLanguageEnvVariable)) + assert.Equal(t, "", os.Getenv(utils.JfMsiEnvVariable)) + + }) + } +} diff --git a/commands/audit/jasrunner.go b/commands/audit/jasrunner.go index 434d9044..03398730 100644 --- a/commands/audit/jasrunner.go +++ b/commands/audit/jasrunner.go @@ -14,7 +14,7 @@ import ( ) func runJasScannersAndSetResults(scanResults *utils.Results, directDependencies []string, - serverDetails *config.ServerDetails, workingDirs []string, progress io.ProgressMgr, thirdPartyApplicabilityScan bool) (err error) { + serverDetails *config.ServerDetails, workingDirs []string, progress io.ProgressMgr, thirdPartyApplicabilityScan bool, msi string) (err error) { if serverDetails == nil || len(serverDetails.Url) == 0 { log.Warn("To include 'Advanced Security' scan as part of the audit output, please run the 'jf c add' command before running this command.") return @@ -30,6 +30,9 @@ func runJasScannersAndSetResults(scanResults *utils.Results, directDependencies if progress != nil { progress.SetHeadlineMsg("Running applicability scanning") } + // Set environments variables for analytics in analyzers manager. + callback := jas.SetAnalyticsMetricsDataForAnalyzerManager(msi, scanResults.GetScaScannedTechnologies()) + defer callback() scanResults.ExtendedScanResults.ApplicabilityScanResults, err = applicability.RunApplicabilityScan(scanResults.GetScaScansXrayResults(), directDependencies, scanResults.GetScaScannedTechnologies(), scanner, thirdPartyApplicabilityScan) if err != nil { return diff --git a/commands/audit/jasrunner_test.go b/commands/audit/jasrunner_test.go index 20b28c8a..d9587dab 100644 --- a/commands/audit/jasrunner_test.go +++ b/commands/audit/jasrunner_test.go @@ -22,14 +22,14 @@ func TestGetExtendedScanResults_AnalyzerManagerDoesntExist(t *testing.T) { assert.NoError(t, os.Unsetenv(coreutils.HomeDir)) }() scanResults := &utils.Results{ScaResults: []utils.ScaScanResult{{Technology: coreutils.Yarn, XrayResults: jas.FakeBasicXrayResults}}, ExtendedScanResults: &utils.ExtendedScanResults{}} - err = runJasScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, &jas.FakeServerDetails, nil, nil, false) + err = runJasScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, &jas.FakeServerDetails, nil, nil, false, "") // Expect error: assert.Error(t, err) } func TestGetExtendedScanResults_ServerNotValid(t *testing.T) { scanResults := &utils.Results{ScaResults: []utils.ScaScanResult{{Technology: coreutils.Pip, XrayResults: jas.FakeBasicXrayResults}}, ExtendedScanResults: &utils.ExtendedScanResults{}} - err := runJasScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, nil, nil, nil, false) + err := runJasScannersAndSetResults(scanResults, []string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, nil, nil, nil, false, "") assert.NoError(t, err) } @@ -37,7 +37,7 @@ func TestGetExtendedScanResults_AnalyzerManagerReturnsError(t *testing.T) { assert.NoError(t, utils.DownloadAnalyzerManagerIfNeeded()) scanResults := &utils.Results{ScaResults: []utils.ScaScanResult{{Technology: coreutils.Yarn, XrayResults: jas.FakeBasicXrayResults}}, ExtendedScanResults: &utils.ExtendedScanResults{}} - err := runJasScannersAndSetResults(scanResults, []string{"issueId_2_direct_dependency", "issueId_1_direct_dependency"}, &jas.FakeServerDetails, nil, nil, false) + err := runJasScannersAndSetResults(scanResults, []string{"issueId_2_direct_dependency", "issueId_1_direct_dependency"}, &jas.FakeServerDetails, nil, nil, false, "") // Expect error: assert.ErrorContains(t, err, "failed to run Applicability scan") diff --git a/commands/audit/sca/python/python.go b/commands/audit/sca/python/python.go index 3cdde455..4a7c4d31 100644 --- a/commands/audit/sca/python/python.go +++ b/commands/audit/sca/python/python.go @@ -123,7 +123,7 @@ func installPoetryDeps(auditPython *AuditPython) (restoreEnv func() error, err e return nil } if auditPython.RemotePypiRepo != "" { - rtUrl, username, password, err := utils.GetPypiRepoUrlWithCredentials(auditPython.Server, auditPython.RemotePypiRepo) + rtUrl, username, password, err := utils.GetPypiRepoUrlWithCredentials(auditPython.Server, auditPython.RemotePypiRepo, false) if err != nil { return restoreEnv, err } @@ -162,7 +162,7 @@ func installPipDeps(auditPython *AuditPython) (restoreEnv func() error, err erro remoteUrl := "" if auditPython.RemotePypiRepo != "" { - remoteUrl, err = utils.GetPypiRepoUrl(auditPython.Server, auditPython.RemotePypiRepo) + remoteUrl, err = utils.GetPypiRepoUrl(auditPython.Server, auditPython.RemotePypiRepo, false) if err != nil { return } @@ -210,7 +210,7 @@ func getPipInstallArgs(requirementsFile, remoteUrl string) []string { } func runPipenvInstallFromRemoteRegistry(server *config.ServerDetails, depsRepoName string) (err error) { - rtUrl, err := utils.GetPypiRepoUrl(server, depsRepoName) + rtUrl, err := utils.GetPypiRepoUrl(server, depsRepoName, false) if err != nil { return err } diff --git a/commands/scan/scan.go b/commands/scan/scan.go index b21e2ea5..c8035d84 100644 --- a/commands/scan/scan.go +++ b/commands/scan/scan.go @@ -349,11 +349,6 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, indexedFil if err != nil { return err } - if params.XscGitInfoContext != nil { - if err = utils.SendXscGitInfoRequestIfEnabled(scanGraphParams.XrayGraphScanParams(), xrayManager); err != nil { - return err - } - } scanResults, err := scangraph.RunScanGraphAndGetResults(scanGraphParams, xrayManager) if err != nil { log.Error(fmt.Sprintf("scanning '%s' failed with error: %s", graph.Id, err.Error())) diff --git a/go.mod b/go.mod index b8f2b676..6885091e 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/jfrog/build-info-go v1.9.25 github.com/jfrog/gofrog v1.6.3 github.com/jfrog/jfrog-apps-config v1.0.1 - github.com/jfrog/jfrog-cli-core/v2 v2.50.0 + github.com/jfrog/jfrog-cli-core/v2 v2.49.0 github.com/jfrog/jfrog-client-go v1.39.0 github.com/magiconair/properties v1.8.7 github.com/owenrumney/go-sarif/v2 v2.3.0 @@ -98,8 +98,8 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -// replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240404075604-3df49e9a9d64 -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go dev +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240403100335-8292671b7cc4 // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev diff --git a/go.sum b/go.sum index a5be4576..f23e834c 100644 --- a/go.sum +++ b/go.sum @@ -102,10 +102,10 @@ github.com/jfrog/gofrog v1.6.3 h1:F7He0+75HcgCe6SGTSHLFCBDxiE2Ja0tekvvcktW6wc= github.com/jfrog/gofrog v1.6.3/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.50.0 h1:QmjSIktMKAbNH7OGY+eVZKx9husqgMANSI5kB8MlvlA= -github.com/jfrog/jfrog-cli-core/v2 v2.50.0/go.mod h1:95AsjwlMLNWU0v71/3dS715e1RAQfvPO47RRHz2xKh8= -github.com/jfrog/jfrog-client-go v1.39.0 h1:GZ1qbpUDzYz8ZEycYicDkbVMN2H0VSCuz8mUNTyf7tc= -github.com/jfrog/jfrog-client-go v1.39.0/go.mod h1:tUyEmxznphh0nwAGo6xz9Sps7RRW/TBMxIJZteo+j2k= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240404075604-3df49e9a9d64 h1:eCAqJ8hqJ6bqgmjswjpqhInJMG80MT5D2r465s/fXzg= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240404075604-3df49e9a9d64/go.mod h1:iQoYSsjLWF8x//rtQCwNPE2ycle2X2x6VFQM0LQE2n0= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240403100335-8292671b7cc4 h1:A67yoFRYjRzg+xhLYhH0QN7b4/wggRa/lSQKSjzOwNQ= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240403100335-8292671b7cc4/go.mod h1:tUyEmxznphh0nwAGo6xz9Sps7RRW/TBMxIJZteo+j2k= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= diff --git a/jfrogclisecurity_test.go b/jfrogclisecurity_test.go index 3af50790..94253546 100644 --- a/jfrogclisecurity_test.go +++ b/jfrogclisecurity_test.go @@ -40,6 +40,7 @@ func setupIntegrationTests() { // Init utils.InitTestCliDetails() utils.AuthenticateArtifactory() + utils.AuthenticateXsc() utils.CreateRequiredRepositories() } diff --git a/scans_test.go b/scans_test.go index 6d9b6bae..eb257be6 100644 --- a/scans_test.go +++ b/scans_test.go @@ -74,13 +74,13 @@ func TestXrayBinaryScanSimpleJsonWithProgress(t *testing.T) { } func testXrayBinaryScan(t *testing.T, format string) string { - securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scangraph.GraphScanMinXrayVersion, "") binariesPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "binaries", "*") return securityTests.PlatformCli.RunCliCmdWithOutput(t, "scan", binariesPath, "--licenses", "--format="+format) } func TestXrayBinaryScanWithBypassArchiveLimits(t *testing.T) { - securityTestUtils.InitSecurityTest(t, scan.BypassArchiveLimitsMinXrayVersion) + securityTestUtils.InitSecurityTest(t, scan.BypassArchiveLimitsMinXrayVersion, "") unsetEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "JF_INDEXER_COMPRESS_MAXENTITIES", "10") defer unsetEnv() binariesPath := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "binaries", "*") @@ -236,7 +236,7 @@ func createTestWatch(t *testing.T) (string, func()) { // Curation tests func TestCurationAudit(t *testing.T) { - securityTestUtils.InitSecurityTest(t, "") + securityTestUtils.InitSecurityTest(t, "", "") tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) defer createTempDirCallback() multiProject := filepath.Join(filepath.FromSlash(securityTestUtils.GetTestResourcesPath()), "projects", "package-managers", "npm") diff --git a/tests/config.go b/tests/config.go index 9fe0641e..743ab542 100644 --- a/tests/config.go +++ b/tests/config.go @@ -16,6 +16,9 @@ var ( XrDetails *config.ServerDetails XrAuth auth.ServiceDetails + XscDetails *config.ServerDetails + XscAuth auth.ServiceDetails + RtDetails *config.ServerDetails RtAuth auth.ServiceDetails RtHttpDetails httputils.HttpClientDetails diff --git a/tests/consts.go b/tests/consts.go index 2b39678b..6e6f9f8e 100644 --- a/tests/consts.go +++ b/tests/consts.go @@ -16,6 +16,7 @@ const ( const ( XrayEndpoint = "xray/" + XscEndpoint = "xsc/" ArtifactoryEndpoint = "artifactory/" AccessEndpoint = "access/" RepoDetailsEndpoint = "api/repositories/" diff --git a/tests/utils/test_config.go b/tests/utils/test_config.go index 63c2c1e5..57705110 100644 --- a/tests/utils/test_config.go +++ b/tests/utils/test_config.go @@ -78,6 +78,28 @@ func authenticateXray() string { return cred } +func AuthenticateXsc() string { + *configTests.JfrogUrl = clientUtils.AddTrailingSlashIfNeeded(*configTests.JfrogUrl) + configTests.XscDetails = &config.ServerDetails{Url: *configTests.JfrogUrl, ArtifactoryUrl: *configTests.JfrogUrl + configTests.ArtifactoryEndpoint, XrayUrl: *configTests.JfrogUrl + configTests.XrayEndpoint, XscUrl: *configTests.JfrogUrl + configTests.XscEndpoint} + cred := fmt.Sprintf("--url=%s", configTests.XscDetails.XrayUrl) + if *configTests.JfrogAccessToken != "" { + + configTests.XscDetails.AccessToken = *configTests.JfrogAccessToken + cred += fmt.Sprintf(" --access-token=%s", configTests.XscDetails.AccessToken) + } else { + configTests.XscDetails.User = *configTests.JfrogUser + configTests.XscDetails.Password = *configTests.JfrogPassword + cred += fmt.Sprintf(" --user=%s --password=%s", configTests.XscDetails.User, configTests.XscDetails.Password) + } + + var err error + if configTests.XscAuth, err = configTests.XscDetails.CreateXscAuthConfig(); err != nil { + coreutils.ExitOnErr(errors.New("Failed while attempting to authenticate with Xsc: " + err.Error())) + } + configTests.XscDetails.XscUrl = configTests.XscAuth.GetUrl() + return cred +} + func AuthenticateArtifactory() string { *configTests.JfrogUrl = clientUtils.AddTrailingSlashIfNeeded(*configTests.JfrogUrl) configTests.RtDetails = &config.ServerDetails{Url: *configTests.JfrogUrl, ArtifactoryUrl: *configTests.JfrogUrl + configTests.ArtifactoryEndpoint, SshKeyPath: *configTests.JfrogSshKeyPath, SshPassphrase: *configTests.JfrogSshPassphrase} diff --git a/tests/utils/test_utils.go b/tests/utils/test_utils.go index 0988f4b4..9198b15d 100644 --- a/tests/utils/test_utils.go +++ b/tests/utils/test_utils.go @@ -16,11 +16,12 @@ import ( clientTests "github.com/jfrog/jfrog-client-go/utils/tests" ) -func InitSecurityTest(t *testing.T, minVersion string) { +func InitSecurityTest(t *testing.T, xrayMinVersion, xscMinVersion string) { if !*configTests.TestSecurity { t.Skip("Skipping Security test. To run Security test add the '-test.security=true' option.") } - ValidateXrayVersion(t, minVersion) + ValidateXrayVersion(t, xrayMinVersion) + ValidateXscVersion(t, xscMinVersion) } func GetTestResourcesPath() string { @@ -57,6 +58,11 @@ func getXrayVersion() (version.Version, error) { return *version.NewVersion(xrayVersion), err } +func getXscVersion() (version.Version, error) { + xscVersion, err := configTests.XscAuth.GetVersion() + return *version.NewVersion(xscVersion), err +} + func ChangeWD(t *testing.T, newPath string) string { prevDir, err := os.Getwd() assert.NoError(t, err, "Failed to get current dir") diff --git a/tests/utils/test_validation.go b/tests/utils/test_validation.go index b92e451d..7c781819 100644 --- a/tests/utils/test_validation.go +++ b/tests/utils/test_validation.go @@ -26,6 +26,17 @@ func ValidateXrayVersion(t *testing.T, minVersion string) { } } +func ValidateXscVersion(t *testing.T, minVersion string) { + xscVersion, err := getXscVersion() + if err != nil { + t.Skip(err) + } + err = clientUtils.ValidateMinimumVersion(clientUtils.Xsc, xscVersion.GetVersion(), minVersion) + if err != nil { + t.Skip(err) + } +} + func VerifyJsonScanResults(t *testing.T, content string, minViolations, minVulnerabilities, minLicenses int) { var results []services.ScanResponse err := json.Unmarshal([]byte(content), &results) diff --git a/utils/analyticsmetrics.go b/utils/analyticsmetrics.go new file mode 100644 index 00000000..3dbd5cfd --- /dev/null +++ b/utils/analyticsmetrics.go @@ -0,0 +1,173 @@ +package utils + +import ( + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-core/v2/utils/usage" + clientutils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/xsc" + xscservices "github.com/jfrog/jfrog-client-go/xsc/services" + "strings" + "time" +) + +type AnalyticsMetricsService struct { + xscManager *xsc.XscServicesManager + // Should the CLI reports analytics metrics to XSC. + shouldReportEvents bool + msi string + startTime time.Time + // In the case of multiple scanning, aggregate all audit results into one finalize event. + finalizeEvent *xscservices.XscAnalyticsGeneralEventFinalize +} + +func NewAnalyticsMetricsService(serviceDetails *config.ServerDetails) *AnalyticsMetricsService { + ams := AnalyticsMetricsService{} + xscManager, err := CreateXscServiceManager(serviceDetails) + if err != nil { + // When an error occurs, shouldReportEvents will be false and no XscServiceManager commands will be executed. + log.Debug(fmt.Sprintf("Failed to create xsc manager for analytics metrics service. %s", err.Error())) + return &ams + } + ams.xscManager = xscManager + ams.shouldReportEvents = ams.calcShouldReportEvents() + return &ams +} + +func (ams *AnalyticsMetricsService) calcShouldReportEvents() bool { + // A user who explicitly requests not to send reports will not receive XSC analytics metrics. + if !usage.ShouldReportUsage() { + return false + } + // Verify xsc version. + xscVersion, err := ams.xscManager.GetVersion() + if err != nil { + return false + } + if err = clientutils.ValidateMinimumVersion(clientutils.Xsc, xscVersion, xscservices.AnalyticsMetricsMinXscVersion); err != nil { + return false + } + return true +} + +func (ams *AnalyticsMetricsService) XscManager() *xsc.XscServicesManager { + return ams.xscManager +} + +func (ams *AnalyticsMetricsService) SetMsi(msi string) { + ams.msi = msi +} + +func (ams *AnalyticsMetricsService) GetMsi() string { + return ams.msi +} + +func (ams *AnalyticsMetricsService) SetStartTime() { + ams.startTime = time.Now() +} + +func (ams *AnalyticsMetricsService) GetStartTime() time.Time { + return ams.startTime +} + +func (ams *AnalyticsMetricsService) ShouldReportEvents() bool { + return ams.shouldReportEvents +} + +func (ams *AnalyticsMetricsService) FinalizeEvent() *xscservices.XscAnalyticsGeneralEventFinalize { + return ams.finalizeEvent +} + +func (ams *AnalyticsMetricsService) SetFinalizeEvent(finalizeEvent *xscservices.XscAnalyticsGeneralEventFinalize) { + ams.finalizeEvent = finalizeEvent +} + +func (ams *AnalyticsMetricsService) CreateGeneralEvent(product xscservices.ProductName, eventType xscservices.EventType) *xscservices.XscAnalyticsGeneralEvent { + osAndArc, err := coreutils.GetOSAndArc() + curOs, curArch := "", "" + if err != nil { + log.Debug(fmt.Errorf("failed to get os and arcitucture for general event request to XSC service, error: %s ", err.Error())) + } else { + splitOsAndArch := strings.Split(osAndArc, "-") + curOs = splitOsAndArch[0] + curArch = splitOsAndArch[1] + } + + event := xscservices.XscAnalyticsGeneralEvent{ + XscAnalyticsBasicGeneralEvent: xscservices.XscAnalyticsBasicGeneralEvent{ + EventType: eventType, + EventStatus: xscservices.Started, + Product: product, + JfrogUser: ams.xscManager.Config().GetServiceDetails().GetUser(), + OsPlatform: curOs, + OsArchitecture: curArch, + AnalyzerManagerVersion: GetAnalyzerManagerVersion(), + }, + } + return &event +} + +func (ams *AnalyticsMetricsService) AddGeneralEvent(event *xscservices.XscAnalyticsGeneralEvent) { + if !ams.ShouldReportEvents() { + log.Debug("Analytics metrics are disabled, skipping sending event request to XSC") + return + } + msi, err := ams.xscManager.AddAnalyticsGeneralEvent(*event) + if err != nil { + log.Debug(fmt.Errorf("failed sending general event request to XSC service, error: %s ", err.Error())) + return + } + log.Debug(fmt.Sprintf("New General event added successfully. multi_scan_id %s", msi)) + // Set event's analytics data. + ams.SetMsi(msi) + ams.SetStartTime() +} + +func (ams *AnalyticsMetricsService) UpdateGeneralEvent(event *xscservices.XscAnalyticsGeneralEventFinalize) { + if !ams.ShouldReportEvents() { + log.Debug("Analytics metrics are disabled, skipping sending update event request to XSC") + return + } + if ams.msi == "" { + log.Debug("MultiScanId is empty, skipping update general event.") + return + } + err := ams.xscManager.UpdateAnalyticsGeneralEvent(*event) + if err != nil { + log.Debug(fmt.Sprintf("failed updading general event request in XSC service for multi_scan_id %s, error: %s \"", ams.GetMsi(), err.Error())) + } else { + log.Debug(fmt.Sprintf("General event updated\n%v", *event)) + } +} + +func (ams *AnalyticsMetricsService) GetGeneralEvent(msi string) (*xscservices.XscAnalyticsGeneralEvent, error) { + if !ams.ShouldReportEvents() { + log.Debug("Can't get general event from XSC - analytics metrics are disabled.") + return nil, nil + } + event, err := ams.xscManager.GetAnalyticsGeneralEvent(msi) + if err != nil { + log.Debug(fmt.Sprintf("failed getting general event from XSC service for multi_scan_id %s, error: %s \"", msi, err.Error())) + } + return event, err +} + +func (ams *AnalyticsMetricsService) CreateXscAnalyticsGeneralEventFinalizeFromAuditResults(auditResults *Results) *xscservices.XscAnalyticsGeneralEventFinalize { + totalDuration := time.Since(ams.GetStartTime()) + eventStatus := xscservices.Completed + if auditResults.ScaError != nil || auditResults.JasError != nil { + eventStatus = xscservices.Failed + } + + basicEvent := xscservices.XscAnalyticsBasicGeneralEvent{ + EventStatus: eventStatus, + TotalFindings: auditResults.CountScanResultsFindings(), + TotalScanDuration: totalDuration.String(), + } + return &xscservices.XscAnalyticsGeneralEventFinalize{ + MultiScanId: ams.msi, + XscAnalyticsBasicGeneralEvent: basicEvent, + } +} diff --git a/utils/analyticsmetrics_test.go b/utils/analyticsmetrics_test.go new file mode 100644 index 00000000..12f0bfb0 --- /dev/null +++ b/utils/analyticsmetrics_test.go @@ -0,0 +1,112 @@ +package utils + +import ( + "errors" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/jfrog/jfrog-client-go/xray/services" + xscservices "github.com/jfrog/jfrog-client-go/xsc/services" + "github.com/owenrumney/go-sarif/v2/sarif" + "github.com/stretchr/testify/assert" + "os" + "testing" + "time" +) + +const ( + lowerAnalyticsMetricsMinXscVersion = "1.6.0" + higherAnalyticsMetricsMinXscVersion = "1.10.0" +) + +func TestCalcShouldReportEvents(t *testing.T) { + // Save original environment information. + msiCallback := tests.SetEnvWithCallbackAndAssert(t, JfMsiEnvVariable, "") + defer msiCallback() + reportUsageCallback := tests.SetEnvWithCallbackAndAssert(t, coreutils.ReportUsage, "") + defer reportUsageCallback() + + // Minimum Xsc version. + mockServer, serverDetails := xscServer(t, xscservices.AnalyticsMetricsMinXscVersion) + defer mockServer.Close() + am := NewAnalyticsMetricsService(serverDetails) + assert.True(t, am.calcShouldReportEvents()) + + // Lower Xsc version. + mockServerLowerVersion, serverDetails := xscServer(t, lowerAnalyticsMetricsMinXscVersion) + defer mockServerLowerVersion.Close() + am = NewAnalyticsMetricsService(serverDetails) + assert.False(t, am.calcShouldReportEvents()) + + // Higher Xsc version. + mockServerHigherVersion, serverDetails := xscServer(t, higherAnalyticsMetricsMinXscVersion) + defer mockServerHigherVersion.Close() + am = NewAnalyticsMetricsService(serverDetails) + assert.True(t, am.calcShouldReportEvents()) + + // JFROG_CLI_REPORT_USAGE is false. + err := os.Setenv(JfMsiEnvVariable, "") + assert.NoError(t, err) + err = os.Setenv(coreutils.ReportUsage, "false") + assert.NoError(t, err) + assert.False(t, am.calcShouldReportEvents()) +} + +func TestAddGeneralEvent(t *testing.T) { + msiCallback := tests.SetEnvWithCallbackAndAssert(t, JfMsiEnvVariable, "") + defer msiCallback() + usageCallback := tests.SetEnvWithCallbackAndAssert(t, coreutils.ReportUsage, "true") + defer usageCallback() + // Successful flow. + mockServer, serverDetails := xscServer(t, xscservices.AnalyticsMetricsMinXscVersion) + defer mockServer.Close() + am := NewAnalyticsMetricsService(serverDetails) + am.AddGeneralEvent(am.CreateGeneralEvent(xscservices.CliProduct, xscservices.CliEventType)) + assert.Equal(t, testMsi, am.GetMsi()) + + // In case cli should not report analytics, verify that request won't be sent. + am.shouldReportEvents = false + am.SetMsi("test-msi") + am.AddGeneralEvent(am.CreateGeneralEvent(xscservices.CliProduct, xscservices.CliEventType)) + assert.Equal(t, "test-msi", am.GetMsi()) +} + +func TestAnalyticsMetricsService_createAuditResultsFromXscAnalyticsBasicGeneralEvent(t *testing.T) { + usageCallback := tests.SetEnvWithCallbackAndAssert(t, coreutils.ReportUsage, "true") + defer usageCallback() + vulnerabilities := []services.Vulnerability{{IssueId: "CVE-123", Components: map[string]services.Component{"issueId_2_direct_dependency": {}}}} + scaResults := []ScaScanResult{{XrayResults: []services.ScanResponse{{Vulnerabilities: vulnerabilities}}}} + auditResults := Results{ + ScaResults: scaResults, + ExtendedScanResults: &ExtendedScanResults{ + ApplicabilityScanResults: []*sarif.Run{{}, {}}, + SecretsScanResults: []*sarif.Run{{}, {}}, + IacScanResults: []*sarif.Run{{}, {}}, + SastScanResults: []*sarif.Run{{}, {}}, + }, + } + testStruct := []struct { + name string + auditResults *Results + want xscservices.XscAnalyticsBasicGeneralEvent + }{ + {name: "No audit results", auditResults: &Results{}, want: xscservices.XscAnalyticsBasicGeneralEvent{EventStatus: xscservices.Completed}}, + {name: "Valid audit result", auditResults: &auditResults, want: xscservices.XscAnalyticsBasicGeneralEvent{TotalFindings: 7, EventStatus: xscservices.Completed}}, + {name: "Scan failed because jas errors.", auditResults: &Results{JasError: errors.New("jas error"), ScaResults: scaResults}, want: xscservices.XscAnalyticsBasicGeneralEvent{TotalFindings: 1, EventStatus: xscservices.Failed}}, + {name: "Scan failed because sca errors.", auditResults: &Results{JasError: errors.New("sca error")}, want: xscservices.XscAnalyticsBasicGeneralEvent{TotalFindings: 0, EventStatus: xscservices.Failed}}, + } + mockServer, serverDetails := xscServer(t, xscservices.AnalyticsMetricsMinXscVersion) + defer mockServer.Close() + am := NewAnalyticsMetricsService(serverDetails) + am.SetStartTime() + time.Sleep(time.Millisecond) + for _, tt := range testStruct { + t.Run(tt.name, func(t *testing.T) { + event := am.CreateXscAnalyticsGeneralEventFinalizeFromAuditResults(tt.auditResults) + assert.Equal(t, tt.want.TotalFindings, event.TotalFindings) + assert.Equal(t, tt.want.EventStatus, event.EventStatus) + totalDuration, err := time.ParseDuration(event.TotalScanDuration) + assert.NoError(t, err) + assert.True(t, totalDuration > 0) + }) + } +} diff --git a/utils/analyzermanager.go b/utils/analyzermanager.go index 5a20be0d..6916465b 100644 --- a/utils/analyzermanager.go +++ b/utils/analyzermanager.go @@ -36,7 +36,9 @@ const ( unsupportedOsExitCode = 55 ErrFailedScannerRun = "failed to run %s scan. Exit code received: %s" jfrogCliAnalyzerManagerVersionEnvVariable = "JFROG_CLI_ANALYZER_MANAGER_VERSION" - jfMsiEnvVariable = "JF_MSI" + JfMsiEnvVariable = "JF_MSI" + JfPackageManagerEnvVariable = "AM_PACKAGE_MANAGER" + JfLanguageEnvVariable = "AM_LANGUAGE" ) type ApplicabilityStatus string @@ -92,7 +94,7 @@ func (am *AnalyzerManager) ExecWithOutputFile(configFile, scanCommand, workingDi return } var cmd *exec.Cmd - multiScanId := os.Getenv(jfMsiEnvVariable) + multiScanId := os.Getenv(JfMsiEnvVariable) if len(outputFile) > 0 { log.Debug("Executing", am.AnalyzerManagerFullPath, scanCommand, configFile, outputFile, multiScanId) cmd = exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile, outputFile) diff --git a/utils/results.go b/utils/results.go index 9ab671ea..863826da 100644 --- a/utils/results.go +++ b/utils/results.go @@ -69,6 +69,39 @@ func (r *Results) IsIssuesFound() bool { return false } +// Counts the total amount of findings in the provided results and updates the AnalyticsMetricsService with the amount of the new added findings +func (r *Results) CountScanResultsFindings() int { + findingsCountMap := make(map[string]int) + var totalFindings int + + // Counting ScaResults + for _, scaResult := range r.ScaResults { + for _, xrayResult := range scaResult.XrayResults { + // XrayResults may contain Vulnerabilities OR Violations, but not both. Therefore, only one of them will be counted + for _, vulnerability := range xrayResult.Vulnerabilities { + findingsCountMap[vulnerability.IssueId] += len(vulnerability.Components) + } + + for _, violation := range xrayResult.Violations { + findingsCountMap[violation.IssueId] += len(violation.Components) + } + } + } + + for _, issueIdCount := range findingsCountMap { + totalFindings += issueIdCount + } + + // Counting ExtendedScanResults + if r.ExtendedScanResults != nil { + totalFindings += len(r.ExtendedScanResults.SastScanResults) + totalFindings += len(r.ExtendedScanResults.IacScanResults) + totalFindings += len(r.ExtendedScanResults.SecretsScanResults) + } + + return totalFindings +} + type ScaScanResult struct { Technology coreutils.Technology `json:"Technology"` WorkingDirectory string `json:"WorkingDirectory"` diff --git a/utils/resultwriter.go b/utils/resultwriter.go index b7dafea4..ef2a1ed2 100644 --- a/utils/resultwriter.go +++ b/utils/resultwriter.go @@ -365,6 +365,7 @@ func ConvertXrayScanToSimpleJson(results *Results, isMultipleRoots, includeLicen jsonTable.LicensesViolations = licViolationsJsonTable jsonTable.OperationalRiskViolations = opRiskViolationsJsonTable } + jsonTable.MultiScanId = results.MultiScanId return jsonTable, nil } diff --git a/utils/techutils.go b/utils/techutils.go new file mode 100644 index 00000000..874968a8 --- /dev/null +++ b/utils/techutils.go @@ -0,0 +1,30 @@ +package utils + +import "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + +func TechnologyToLanguage(technology coreutils.Technology) CodeLanguage { + languageMap := map[coreutils.Technology]CodeLanguage{ + coreutils.Npm: JavaScript, + coreutils.Pip: Python, + coreutils.Poetry: Python, + coreutils.Pipenv: Python, + coreutils.Go: GoLang, + coreutils.Maven: Java, + coreutils.Gradle: Java, + coreutils.Nuget: CSharp, + coreutils.Dotnet: CSharp, + coreutils.Yarn: JavaScript, + coreutils.Pnpm: JavaScript, + } + return languageMap[technology] +} + +type CodeLanguage string + +const ( + JavaScript CodeLanguage = "javascript" + Python CodeLanguage = "python" + GoLang CodeLanguage = "go" + Java CodeLanguage = "java" + CSharp CodeLanguage = "C#" +) diff --git a/utils/techutils_test.go b/utils/techutils_test.go new file mode 100644 index 00000000..e231f275 --- /dev/null +++ b/utils/techutils_test.go @@ -0,0 +1,32 @@ +package utils + +import ( + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestTechnologyToLanguage(t *testing.T) { + tests := []struct { + name string + technology coreutils.Technology + language CodeLanguage + }{ + {name: "Maven to Java", technology: coreutils.Maven, language: Java}, + {name: "Gradle to Java", technology: coreutils.Gradle, language: Java}, + {name: "Npm to JavaScript", technology: coreutils.Npm, language: JavaScript}, + {name: "Pnpm to JavaScript", technology: coreutils.Pnpm, language: JavaScript}, + {name: "Yarn to JavaScript", technology: coreutils.Yarn, language: JavaScript}, + {name: "Go to GoLang", technology: coreutils.Go, language: GoLang}, + {name: "Pip to Python", technology: coreutils.Pip, language: Python}, + {name: "Pipenv to Python", technology: coreutils.Pipenv, language: Python}, + {name: "Poetry to Python", technology: coreutils.Poetry, language: Python}, + {name: "Nuget to CSharp", technology: coreutils.Nuget, language: CSharp}, + {name: "Dotnet to CSharp", technology: coreutils.Dotnet, language: CSharp}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.language, TechnologyToLanguage(tt.technology), "TechnologyToLanguage(%v) == %v", tt.technology, tt.language) + }) + } +} diff --git a/utils/test_mocks.go b/utils/test_mocks.go new file mode 100644 index 00000000..f3d308a0 --- /dev/null +++ b/utils/test_mocks.go @@ -0,0 +1,51 @@ +package utils + +import ( + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/artifactory" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +const testMsi = "27e175b8-e525-11ee-842b-7aa2c69b8f1f" + +type restsTestHandler func(w http.ResponseWriter, r *http.Request) + +// Create mock server to test REST APIs. +// testHandler - The HTTP handler of the test +func CreateRestsMockServer(testHandler restsTestHandler) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(testHandler)) +} +func CreateXscRestsMockServer(t *testing.T, testHandler restsTestHandler) (*httptest.Server, *config.ServerDetails, artifactory.ArtifactoryServicesManager) { + testServer := CreateRestsMockServer(testHandler) + serverDetails := &config.ServerDetails{Url: testServer.URL + "/", XrayUrl: testServer.URL + "/xray/"} + + serviceManager, err := utils.CreateServiceManager(serverDetails, -1, 0, false) + assert.NoError(t, err) + return testServer, serverDetails, serviceManager +} + +func xscServer(t *testing.T, xscVersion string) (*httptest.Server, *config.ServerDetails) { + serverMock, serverDetails, _ := CreateXscRestsMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/xsc/api/v1/system/version" { + _, err := w.Write([]byte(fmt.Sprintf(`{"xsc_version": "%s"}`, xscVersion))) + if err != nil { + return + } + } + if r.RequestURI == "/xsc/api/v1/event" { + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusCreated) + _, err := w.Write([]byte(fmt.Sprintf(`{"multi_scan_id": "%s"}`, testMsi))) + if err != nil { + return + } + } + } + }) + return serverMock, serverDetails +} diff --git a/utils/xraymanager.go b/utils/xraymanager.go index 62aa5b92..56ed02a0 100644 --- a/utils/xraymanager.go +++ b/utils/xraymanager.go @@ -1,17 +1,13 @@ package utils import ( - "fmt" "github.com/jfrog/jfrog-cli-core/v2/utils/config" clientconfig "github.com/jfrog/jfrog-client-go/config" - "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray" - "github.com/jfrog/jfrog-client-go/xray/services" - "os" ) -func CreateXrayServiceManager(serviceDetails *config.ServerDetails) (*xray.XrayServicesManager, error) { - xrayDetails, err := serviceDetails.CreateXrayAuthConfig() +func CreateXrayServiceManager(serverDetails *config.ServerDetails) (*xray.XrayServicesManager, error) { + xrayDetails, err := serverDetails.CreateXrayAuthConfig() if err != nil { return nil, err } @@ -35,25 +31,3 @@ func CreateXrayServiceManagerAndGetVersion(serviceDetails *config.ServerDetails) } return xrayManager, xrayVersion, nil } - -func SendXscGitInfoRequestIfEnabled(graphScanParams *services.XrayGraphScanParams, xrayManager *xray.XrayServicesManager) (err error) { - if graphScanParams.XscVersion, err = xrayManager.XscEnabled(); err != nil { - return err - } - if graphScanParams.XscVersion == "" || graphScanParams.MultiScanId != "" { - // XSC is not enabled or multiScanId already provided. - return - } - // Generate multiScanId and set it to the scan params. - multiScanId, err := xrayManager.SendXscGitInfoRequest(graphScanParams.XscGitInfoContext) - if err != nil { - return fmt.Errorf("failed sending Git Info request to XSC service, error: %s ", err.Error()) - } - graphScanParams.MultiScanId = multiScanId - log.Debug(fmt.Sprintf("Created xsc git info successfully. multi_scan_id %s", multiScanId)) - if err = os.Setenv("JF_MSI", multiScanId); err != nil { - // Not a fatal error, if not set the scan will not be shown at the XSC UI, should not fail the scan. - log.Debug(fmt.Sprintf("failed setting MSI as environment variable. Cause: %s", err.Error())) - } - return nil -} diff --git a/utils/xscmanager.go b/utils/xscmanager.go new file mode 100644 index 00000000..5eb5edf7 --- /dev/null +++ b/utils/xscmanager.go @@ -0,0 +1,21 @@ +package utils + +import ( + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + clientconfig "github.com/jfrog/jfrog-client-go/config" + "github.com/jfrog/jfrog-client-go/xsc" +) + +func CreateXscServiceManager(serviceDetails *config.ServerDetails) (*xsc.XscServicesManager, error) { + xscDetails, err := serviceDetails.CreateXscAuthConfig() + if err != nil { + return nil, err + } + serviceConfig, err := clientconfig.NewConfigBuilder(). + SetServiceDetails(xscDetails). + Build() + if err != nil { + return nil, err + } + return xsc.New(serviceConfig) +} diff --git a/xray_test.go b/xray_test.go index 695162dc..5845cdd6 100644 --- a/xray_test.go +++ b/xray_test.go @@ -11,7 +11,7 @@ import ( ) func TestXrayCurl(t *testing.T) { - securityTestUtils.InitSecurityTest(t, "") + securityTestUtils.InitSecurityTest(t, "", "") // Configure a new server named "default". securityTestUtils.CreateJfrogHomeConfig(t, true) defer securityTestUtils.CleanTestsHomeEnv() @@ -27,7 +27,7 @@ func TestXrayCurl(t *testing.T) { } func TestXrayOfflineDBSyncV3(t *testing.T) { - securityTestUtils.InitSecurityTest(t, "") + securityTestUtils.InitSecurityTest(t, "", "") // Validate license-id err := securityTests.PlatformCli.WithoutCredentials().Exec("xr", "ou") assert.EqualError(t, err, "Mandatory flag 'license-id' is missing")