diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 998e7ef68..18899ab58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -311,7 +311,7 @@ jobs: # Test - name: Run tests - run: go test ${{ env.GO_COMMON_TEST_ARGS }} --test.curation --test.enrich --ci.runId=${{ runner.os }}-sec-test + run: go test ${{ env.GO_COMMON_TEST_ARGS }} --test.curation --test.enrich --test.maliciousScan --ci.runId=${{ runner.os }}-sec-test Git_Commands_Integration_Tests: name: "[${{ matrix.os }}] Git Commands Integration Tests" diff --git a/cli/scancommands.go b/cli/scancommands.go index fb3a8bfeb..dd6b2e568 100644 --- a/cli/scancommands.go +++ b/cli/scancommands.go @@ -23,7 +23,7 @@ import ( auditSpecificDocs "github.com/jfrog/jfrog-cli-security/cli/docs/auditspecific" enrichDocs "github.com/jfrog/jfrog-cli-security/cli/docs/enrich" - // maliciousScanDocs "github.com/jfrog/jfrog-cli-security/cli/docs/maliciousscan" + maliciousScanDocs "github.com/jfrog/jfrog-cli-security/cli/docs/maliciousscan" mcpDocs "github.com/jfrog/jfrog-cli-security/cli/docs/mcp" sastServerDocs "github.com/jfrog/jfrog-cli-security/cli/docs/sastserver" auditDocs "github.com/jfrog/jfrog-cli-security/cli/docs/scan/audit" @@ -79,15 +79,15 @@ func getAuditAndScansCommands() []components.Command { Category: securityCategory, Action: EnrichCmd, }, - // { - // Name: "malicious-scan", - // Aliases: []string{"ms"}, - // Flags: flags.GetCommandFlags(flags.MaliciousScan), - // Description: maliciousScanDocs.GetDescription(), - // Arguments: maliciousScanDocs.GetArguments(), - // Category: securityCategory, - // Action: MaliciousScanCmd, - // }, + { + Name: "malicious-scan", + Aliases: []string{"ms"}, + Flags: flags.GetCommandFlags(flags.MaliciousScan), + Description: maliciousScanDocs.GetDescription(), + Arguments: maliciousScanDocs.GetArguments(), + Category: securityCategory, + Action: MaliciousScanCmd, + }, { Name: "build-scan", Aliases: []string{"bs"}, diff --git a/commands/maliciousscan/maliciousscan.go b/commands/maliciousscan/maliciousscan.go index d0ac92a9f..c658942c9 100644 --- a/commands/maliciousscan/maliciousscan.go +++ b/commands/maliciousscan/maliciousscan.go @@ -16,10 +16,17 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/results/output" "github.com/jfrog/jfrog-cli-security/utils/severityutils" - "github.com/jfrog/jfrog-cli-security/utils/xray" + xrayUtils "github.com/jfrog/jfrog-cli-security/utils/xray" + clientUtils "github.com/jfrog/jfrog-client-go/utils" ioUtils "github.com/jfrog/jfrog-client-go/utils/io" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/xray" +) + +const ( + MaliciousScanFeatureId = "ai_catalog" + MinimumXrayVersionForMaliciousScan = "3.132.0" ) type MaliciousScanCommand struct { @@ -106,21 +113,20 @@ func (cmd *MaliciousScanCommand) Run() (err error) { } func (cmd *MaliciousScanCommand) validateAndPrepare() (xrayVersion string, entitledForJas bool, workingDirs []string, err error) { - xrayManager, xrayVersion, err := xray.CreateXrayServiceManagerAndGetVersion(cmd.serverDetails, xray.WithScopedProjectKey(cmd.project)) + xrayManager, xrayVersion, err := xrayUtils.CreateXrayServiceManagerAndGetVersion(cmd.serverDetails, xrayUtils.WithScopedProjectKey(cmd.project)) if err != nil { return "", false, nil, err } - - entitledForJas, err = jas.IsEntitledForJas(xrayManager, xrayVersion) - if err != nil { + if err = clientUtils.ValidateMinimumVersion(clientUtils.Xray, xrayVersion, MinimumXrayVersionForMaliciousScan); err != nil { + return "", false, nil, err + } + log.Info("JFrog Xray version is:", xrayVersion) + if entitledForJas, err = IsEntitledForMaliciousScan(xrayManager, xrayVersion); err != nil { return "", false, nil, err } if !entitledForJas { - return "", false, nil, errors.New("JAS (Advanced Security) feature is not entitled") + return "", false, nil, errors.New("malicious scan feature is not entitled") } - - log.Info("JFrog Xray version is:", xrayVersion) - workingDirs, err = coreutils.GetFullPathsWorkingDirs(cmd.workingDirs) if err != nil { return "", false, nil, err @@ -130,6 +136,10 @@ func (cmd *MaliciousScanCommand) validateAndPrepare() (xrayVersion string, entit return xrayVersion, entitledForJas, workingDirs, nil } +func IsEntitledForMaliciousScan(xrayManager *xray.XrayServicesManager, xrayVersion string) (entitled bool, err error) { + return xrayUtils.IsEntitled(xrayManager, xrayVersion, MaliciousScanFeatureId) +} + func (cmd *MaliciousScanCommand) initializeCommandResults(xrayVersion string, entitledForJas bool) *results.SecurityCommandResults { cmdResults := results.NewCommandResults(utils.SourceCode) cmdResults.SetXrayVersion(xrayVersion) diff --git a/maliciousscan_test.go b/maliciousscan_test.go index 78d211b59..a074daa0b 100644 --- a/maliciousscan_test.go +++ b/maliciousscan_test.go @@ -1,112 +1,116 @@ package main import ( -// "strconv" -// "strings" -// "testing" + "strconv" + "strings" + "testing" -// "path/filepath" + "path/filepath" -// "github.com/stretchr/testify/assert" -// securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" -// "github.com/jfrog/jfrog-cli-security/tests/validations" + "github.com/jfrog/jfrog-cli-security/commands/maliciousscan" + securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" + "github.com/jfrog/jfrog-cli-security/tests/validations" + "github.com/stretchr/testify/assert" -// "github.com/jfrog/jfrog-cli-core/v2/common/format" -// securityTests "github.com/jfrog/jfrog-cli-security/tests" -// securityIntegrationTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils/integration" + "github.com/jfrog/jfrog-cli-core/v2/common/format" + securityTests "github.com/jfrog/jfrog-cli-security/tests" + securityIntegrationTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils/integration" ) -// type maliciousScanCommandTestParams struct { -// WorkingDirsToScan []string -// Format format.OutputFormat -// Threads int -// MinSeverity string -// } +type maliciousScanCommandTestParams struct { + WorkingDirsToScan []string + Format format.OutputFormat + Threads int + MinSeverity string +} -// func getMaliciousScanCmdArgs(params maliciousScanCommandTestParams) (args []string) { -// args = []string{"malicious-scan"} -// if len(params.WorkingDirsToScan) > 0 { -// args = append(args, "--working-dirs="+strings.Join(params.WorkingDirsToScan, ",")) -// } -// if params.Format != "" { -// args = append(args, "--format="+string(params.Format)) -// } -// if params.Threads > 0 { -// args = append(args, "--threads="+strconv.Itoa(params.Threads)) -// } -// if params.MinSeverity != "" { -// args = append(args, "--min-severity="+params.MinSeverity) -// } -// return args -// } +func getMaliciousScanCmdArgs(params maliciousScanCommandTestParams) (args []string) { + args = []string{"malicious-scan"} + if len(params.WorkingDirsToScan) > 0 { + args = append(args, "--working-dirs="+strings.Join(params.WorkingDirsToScan, ",")) + } + if params.Format != "" { + args = append(args, "--format="+string(params.Format)) + } + if params.Threads > 0 { + args = append(args, "--threads="+strconv.Itoa(params.Threads)) + } + if params.MinSeverity != "" { + args = append(args, "--min-severity="+params.MinSeverity) + } + return args +} -// func runMaliciousScan(t *testing.T, params maliciousScanCommandTestParams) (string, error) { -// cleanUp := securityIntegrationTestUtils.UseTestHomeWithDefaultXrayConfig(t) -// defer cleanUp() -// return securityTests.PlatformCli.RunCliCmdWithOutputs(t, getMaliciousScanCmdArgs(params)...) -// } +func runMaliciousScan(t *testing.T, params maliciousScanCommandTestParams) (string, error) { + cleanUp := securityIntegrationTestUtils.UseTestHomeWithDefaultXrayConfig(t) + defer cleanUp() + return securityTests.PlatformCli.RunCliCmdWithOutputs(t, getMaliciousScanCmdArgs(params)...) +} -// func TestMaliciousScan(t *testing.T) { -// testCases := []struct { -// name string -// format format.OutputFormat -// projectPath string -// expectedIssues int -// }{ -// { -// name: "Malicious scan with findings (Simple JSON)", -// format: format.SimpleJson, -// projectPath: filepath.Join("projects", "jas", "jas", "malicious"), -// expectedIssues: 1, -// }, -// { -// name: "Malicious scan without findings (Simple JSON)", -// format: format.SimpleJson, -// projectPath: filepath.Join("projects", "empty_project", "python_project_with_no_deps"), -// expectedIssues: 0, -// }, -// } -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// fullProjectPath := filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), tc.projectPath) -// _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, fullProjectPath) -// defer cleanUp() +func TestMaliciousScan(t *testing.T) { + securityIntegrationTestUtils.InitMaliciousScanTest(t, maliciousscan.MinimumXrayVersionForMaliciousScan) + testCases := []struct { + name string + format format.OutputFormat + projectPath string + expectedIssues int + }{ + { + name: "Malicious scan with findings (Simple JSON)", + format: format.SimpleJson, + projectPath: filepath.Join("projects", "jas", "jas", "malicious"), + expectedIssues: 1, + }, + { + name: "Malicious scan without findings (Simple JSON)", + format: format.SimpleJson, + projectPath: filepath.Join("projects", "empty_project", "python_project_with_no_deps"), + expectedIssues: 0, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fullProjectPath := filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), tc.projectPath) + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, fullProjectPath) + defer cleanUp() -// params := maliciousScanCommandTestParams{ -// Format: tc.format, -// } -// output, err := runMaliciousScan(t, params) -// assert.NoError(t, err) + params := maliciousScanCommandTestParams{ + Format: tc.format, + } + output, err := runMaliciousScan(t, params) + assert.NoError(t, err) -// validationsParams := validations.ValidationParams{ -// Vulnerabilities: &validations.VulnerabilityCount{ -// ValidateScan: &validations.ScanCount{MaliciousCode: tc.expectedIssues}, -// }, -// } -// if tc.expectedIssues == 0 { -// validationsParams.ExactResultsMatch = true -// } -// validations.ValidateCommandOutput(t, output, tc.format, validationsParams) -// }) -// } -// } + validationsParams := validations.ValidationParams{ + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{MaliciousCode: tc.expectedIssues}, + }, + } + if tc.expectedIssues == 0 { + validationsParams.ExactResultsMatch = true + } + validations.ValidateCommandOutput(t, output, tc.format, validationsParams) + }) + } +} -// func TestMaliciousScanWithWorkingDirs(t *testing.T) { -// maliciousProjectPath := filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "jas", "jas", "malicious") -// _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, maliciousProjectPath) -// defer cleanUp() +func TestMaliciousScanWithWorkingDirs(t *testing.T) { + securityIntegrationTestUtils.InitMaliciousScanTest(t, maliciousscan.MinimumXrayVersionForMaliciousScan) -// params := maliciousScanCommandTestParams{ -// WorkingDirsToScan: []string{"."}, -// Format: format.SimpleJson, -// } -// output, err := runMaliciousScan(t, params) -// assert.NoError(t, err) + maliciousProjectPath := filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "jas", "jas", "malicious") + _, cleanUp := securityTestUtils.CreateTestProjectEnvAndChdir(t, maliciousProjectPath) + defer cleanUp() -// validationsParams := validations.ValidationParams{ -// Vulnerabilities: &validations.VulnerabilityCount{ -// ValidateScan: &validations.ScanCount{MaliciousCode: 1}, -// }, -// } -// validations.ValidateCommandOutput(t, output, format.SimpleJson, validationsParams) -// } + params := maliciousScanCommandTestParams{ + WorkingDirsToScan: []string{"."}, + Format: format.SimpleJson, + } + output, err := runMaliciousScan(t, params) + assert.NoError(t, err) + + validationsParams := validations.ValidationParams{ + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{MaliciousCode: 1}, + }, + } + validations.ValidateCommandOutput(t, output, format.SimpleJson, validationsParams) +} diff --git a/tests/config.go b/tests/config.go index 55146b92c..16b68fc7f 100644 --- a/tests/config.go +++ b/tests/config.go @@ -35,15 +35,16 @@ var ( // Test flags var ( - TestUnit *bool - TestArtifactory *bool - TestXray *bool - TestXsc *bool - TestScan *bool - TestDockerScan *bool - TestCuration *bool - TestEnrich *bool - TestGit *bool + TestUnit *bool + TestArtifactory *bool + TestXray *bool + TestXsc *bool + TestScan *bool + TestDockerScan *bool + TestCuration *bool + TestEnrich *bool + TestMaliciousScan *bool + TestGit *bool TestAuditGeneral *bool TestAuditNewSca *bool @@ -124,6 +125,7 @@ func init() { TestAuditPython = flag.Bool("test.audit.Python", false, "Run Python technologies (Pip, PipEnv, Poetry) audit integration tests") TestAuditCocoapods = flag.Bool("test.audit.Cocoapods", false, "Run Cocoapods technologies audit integration tests") TestAuditSwift = flag.Bool("test.audit.Swift", false, "Run Swift technologies audit integration tests") + TestMaliciousScan = flag.Bool("test.maliciousScan", false, "Run Malicious scan command integration tests") JfrogUrl = flag.String("jfrog.url", getTestUrlDefaultValue(), "JFrog platform url") JfrogUser = flag.String("jfrog.user", getTestUserDefaultValue(), "JFrog platform username") @@ -140,7 +142,7 @@ func init() { func InitTestFlags() { flag.Parse() // If no test types flags were set, run all types - shouldRunAllTests := !isAtLeastOneFlagSet(TestUnit, TestArtifactory, TestXray, TestXsc, TestAuditGeneral, TestAuditNewSca, TestAuditJas, TestAuditJavaScript, TestAuditJava, TestAuditCTypes, TestAuditGo, TestAuditPython, TestAuditCocoapods, TestAuditSwift, TestScan, TestDockerScan, TestCuration, TestEnrich, TestGit) + shouldRunAllTests := !isAtLeastOneFlagSet(TestUnit, TestArtifactory, TestXray, TestXsc, TestAuditGeneral, TestAuditNewSca, TestAuditJas, TestAuditJavaScript, TestAuditJava, TestAuditCTypes, TestAuditGo, TestAuditPython, TestAuditCocoapods, TestAuditSwift, TestScan, TestDockerScan, TestCuration, TestEnrich, TestMaliciousScan, TestGit) if shouldRunAllTests { log.Info("Running all tests. To run only specific tests, please specify the desired test flags.") *TestUnit = true @@ -161,6 +163,7 @@ func InitTestFlags() { *TestDockerScan = true *TestCuration = true *TestEnrich = true + *TestMaliciousScan = true *TestGit = true } } diff --git a/tests/utils/integration/test_integrationutils.go b/tests/utils/integration/test_integrationutils.go index f4cef72b8..6122a92fe 100644 --- a/tests/utils/integration/test_integrationutils.go +++ b/tests/utils/integration/test_integrationutils.go @@ -201,6 +201,13 @@ func InitEnrichTest(t *testing.T, minVersion string) { testUtils.GetAndValidateXrayVersion(t, minVersion) } +func InitMaliciousScanTest(t *testing.T, minVersion string) { + if !*configTests.TestMaliciousScan { + t.Skip(getSkipTestMsg("Malicious scan command integration", "--test.maliciousScan")) + } + testUtils.GetAndValidateXrayVersion(t, minVersion) +} + func InitGitTest(t *testing.T, minXrayVersion string) (string, string, func()) { if !*configTests.TestGit { t.Skip(getSkipTestMsg("Git commands integration", "--test.git"))