diff --git a/go.mod b/go.mod index 67e486a0e..6b13ffa9b 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/jedib0t/go-pretty/v6 v6.4.7 github.com/jfrog/build-info-go v1.9.10 github.com/jfrog/gofrog v1.3.0 + github.com/jfrog/jfrog-apps-config v1.0.1 github.com/jfrog/jfrog-client-go v1.31.6 github.com/magiconair/properties v1.8.7 github.com/manifoldco/promptui v0.9.0 diff --git a/go.sum b/go.sum index 84ce912e5..83dfb2432 100644 --- a/go.sum +++ b/go.sum @@ -198,6 +198,8 @@ github.com/jfrog/build-info-go v1.9.10 h1:uXnDLVxpqxoAMpXcki00QaBB+M2BoGMMpHODPk github.com/jfrog/build-info-go v1.9.10/go.mod h1:ujJ8XQZMdT2tMkLSMJNyDd1pCY+duwHdjV+9or9FLIg= github.com/jfrog/gofrog v1.3.0 h1:o4zgsBZE4QyDbz2M7D4K6fXPTBJht+8lE87mS9bw7Gk= github.com/jfrog/gofrog v1.3.0/go.mod h1:IFMc+V/yf7rA5WZ74CSbXe+Lgf0iApEQLxRZVzKRUR0= +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-client-go v1.28.1-0.20230910192358-6994626b2069 h1:vk+P6jK4Zv8+F44ZnRxXUPT14BQxjJtNKdpGdemci7A= github.com/jfrog/jfrog-client-go v1.28.1-0.20230910192358-6994626b2069/go.mod h1:362+oa7uTTYurzBs1L0dmUTlLo7uhpAU/pwM5Zb9clg= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -320,7 +322,7 @@ github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+ github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= diff --git a/xray/commands/audit/jas/applicability/applicabilitymanager.go b/xray/commands/audit/jas/applicability/applicabilitymanager.go index 8c46f4865..d055c494c 100644 --- a/xray/commands/audit/jas/applicability/applicabilitymanager.go +++ b/xray/commands/audit/jas/applicability/applicabilitymanager.go @@ -3,6 +3,7 @@ package applicability import ( "path/filepath" + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "github.com/jfrog/gofrog/datastructures" @@ -98,19 +99,22 @@ func isDirectComponents(components []string, directDependencies []string) bool { return false } -func (asm *ApplicabilityScanManager) Run(wd string) (err error) { - if len(asm.scanner.WorkingDirs) > 1 { - log.Info("Running applicability scanning in the", wd, "directory...") +func (asm *ApplicabilityScanManager) Run(module jfrogappsconfig.Module) (err error) { + if jas.ShouldSkipScanner(module, utils.Applicability) { + return + } + if len(asm.scanner.JFrogAppsConfig.Modules) > 1 { + log.Info("Running applicability scanning in the", module.SourceRoot, "directory...") } else { log.Info("Running applicability scanning...") } - if err = asm.createConfigFile(wd); err != nil { + if err = asm.createConfigFile(module.SourceRoot); err != nil { return } if err = asm.runAnalyzerManager(); err != nil { return } - workingDirResults, err := jas.ReadJasScanRunsFromFile(asm.scanner.ResultsFileName, wd) + workingDirResults, err := jas.ReadJasScanRunsFromFile(asm.scanner.ResultsFileName, module.SourceRoot) if err != nil { return } @@ -148,7 +152,7 @@ func (asm *ApplicabilityScanManager) createConfigFile(workingDir string) error { Type: applicabilityScanType, GrepDisable: false, CveWhitelist: asm.directDependenciesCves, - SkippedDirs: jas.SkippedDirs, + SkippedDirs: jas.DefaultExcludePatterns, }, }, } diff --git a/xray/commands/audit/jas/applicability/applicabilitymanager_test.go b/xray/commands/audit/jas/applicability/applicabilitymanager_test.go index f887763c5..cd23f82e1 100644 --- a/xray/commands/audit/jas/applicability/applicabilitymanager_test.go +++ b/xray/commands/audit/jas/applicability/applicabilitymanager_test.go @@ -1,13 +1,14 @@ package applicability import ( + "os" + "path/filepath" + "testing" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "github.com/jfrog/jfrog-client-go/xray/services" "github.com/stretchr/testify/assert" - "os" - "path/filepath" - "testing" ) var mockDirectDependencies = []string{"issueId_2_direct_dependency", "issueId_1_direct_dependency"} @@ -36,7 +37,7 @@ func TestNewApplicabilityScanManager_DependencyTreeDoesntExist(t *testing.T) { // Assert if assert.NotNil(t, applicabilityManager) { assert.NotNil(t, applicabilityManager.scanner.ScannerDirCleanupFunc) - assert.Len(t, applicabilityManager.scanner.WorkingDirs, 1) + assert.Len(t, applicabilityManager.scanner.JFrogAppsConfig.Modules, 1) assert.NotEmpty(t, applicabilityManager.scanner.ConfigFileName) assert.NotEmpty(t, applicabilityManager.scanner.ResultsFileName) assert.Empty(t, applicabilityManager.directDependenciesCves) @@ -276,7 +277,7 @@ func TestParseResults_EmptyResults_AllCvesShouldGetUnknown(t *testing.T) { // Act var err error - applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot) if assert.NoError(t, err) { assert.Len(t, applicabilityManager.applicabilityScanResults, 1) @@ -293,7 +294,7 @@ func TestParseResults_ApplicableCveExist(t *testing.T) { // Act var err error - applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot) if assert.NoError(t, err) && assert.NotNil(t, applicabilityManager.applicabilityScanResults) { assert.Len(t, applicabilityManager.applicabilityScanResults, 1) @@ -310,7 +311,7 @@ func TestParseResults_AllCvesNotApplicable(t *testing.T) { // Act var err error - applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot) if assert.NoError(t, err) && assert.NotNil(t, applicabilityManager.applicabilityScanResults) { assert.Len(t, applicabilityManager.applicabilityScanResults, 1) diff --git a/xray/commands/audit/jas/common.go b/xray/commands/audit/jas/common.go index 7f4cbf5ab..7f9d578db 100644 --- a/xray/commands/audit/jas/common.go +++ b/xray/commands/audit/jas/common.go @@ -2,25 +2,29 @@ package jas import ( "errors" + "fmt" "os" "path/filepath" "strings" "testing" + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" rtutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "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/xray/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" + "golang.org/x/exp/slices" "gopkg.in/yaml.v3" ) var ( - SkippedDirs = []string{"**/*test*/**", "**/*venv*/**", "**/*node_modules*/**", "**/*target*/**"} + DefaultExcludePatterns = []string{"**/*test*/**", "**/*venv*/**", "**/*node_modules*/**", "**/*target*/**"} mapSeverityToScore = map[string]string{ "": "0.0", @@ -37,7 +41,7 @@ type JasScanner struct { ResultsFileName string AnalyzerManager utils.AnalyzerManager ServerDetails *config.ServerDetails - WorkingDirs []string + JFrogAppsConfig *jfrogappsconfig.JFrogAppsConfig ScannerDirCleanupFunc func() error } @@ -56,21 +60,41 @@ func NewJasScanner(workingDirs []string, serverDetails *config.ServerDetails) (s scanner.ServerDetails = serverDetails scanner.ConfigFileName = filepath.Join(tempDir, "config.yaml") scanner.ResultsFileName = filepath.Join(tempDir, "results.sarif") - scanner.WorkingDirs, err = coreutils.GetFullPathsWorkingDirs(workingDirs) + scanner.JFrogAppsConfig, err = createJFrogAppsConfig(workingDirs) return } +func createJFrogAppsConfig(workingDirs []string) (*jfrogappsconfig.JFrogAppsConfig, error) { + if jfrogAppsConfig, err := jfrogappsconfig.LoadConfigIfExist(); err != nil { + return nil, errorutils.CheckError(err) + } else if jfrogAppsConfig != nil { + // jfrog-apps-config.yml exist in the workspace + return jfrogAppsConfig, nil + } + + // jfrog-apps-config.yml does not exist in the workspace + fullPathsWorkingDirs, err := coreutils.GetFullPathsWorkingDirs(workingDirs) + if err != nil { + return nil, err + } + jfrogAppsConfig := new(jfrogappsconfig.JFrogAppsConfig) + for _, workingDir := range fullPathsWorkingDirs { + jfrogAppsConfig.Modules = append(jfrogAppsConfig.Modules, jfrogappsconfig.Module{SourceRoot: workingDir}) + } + return jfrogAppsConfig, nil +} + type ScannerCmd interface { - Run(wd string) (err error) + Run(module jfrogappsconfig.Module) (err error) } func (a *JasScanner) Run(scannerCmd ScannerCmd) (err error) { - for _, workingDir := range a.WorkingDirs { + for _, module := range a.JFrogAppsConfig.Modules { func() { defer func() { err = errors.Join(err, deleteJasProcessFiles(a.ConfigFileName, a.ResultsFileName)) }() - if err = scannerCmd.Run(workingDir); err != nil { + if err = scannerCmd.Run(module); err != nil { return } }() @@ -153,6 +177,7 @@ func CreateScannersConfigFile(fileName string, fileContent interface{}) error { if errorutils.CheckError(err) != nil { return err } + log.Debug("Input YAML:\n" + string(yamlData)) err = os.WriteFile(fileName, yamlData, 0644) return errorutils.CheckError(err) } @@ -191,3 +216,38 @@ func InitJasTest(t *testing.T, workingDirs ...string) (*JasScanner, func()) { func GetTestDataPath() string { return filepath.Join("..", "..", "..", "testdata") } + +func ShouldSkipScanner(module jfrogappsconfig.Module, scanType utils.JasScanType) bool { + lowerScanType := strings.ToLower(string(scanType)) + if slices.Contains(module.ExcludeScanners, lowerScanType) { + log.Info(fmt.Sprintf("Skipping %s scanning", scanType)) + return true + } + return false +} + +func GetSourceRoots(module jfrogappsconfig.Module, scanner *jfrogappsconfig.Scanner) ([]string, error) { + root, err := filepath.Abs(module.SourceRoot) + if err != nil { + return []string{}, errorutils.CheckError(err) + } + if scanner == nil || len(scanner.WorkingDirs) == 0 { + return []string{root}, errorutils.CheckError(err) + } + var roots []string + for _, workingDir := range scanner.WorkingDirs { + roots = append(roots, filepath.Join(root, workingDir)) + } + return roots, nil +} + +func GetExcludePatterns(module jfrogappsconfig.Module, scanner *jfrogappsconfig.Scanner) []string { + excludePatterns := module.ExcludePatterns + if scanner != nil { + excludePatterns = append(excludePatterns, scanner.ExcludePatterns...) + } + if len(excludePatterns) == 0 { + return DefaultExcludePatterns + } + return excludePatterns +} diff --git a/xray/commands/audit/jas/commons_test.go b/xray/commands/audit/jas/commons_test.go new file mode 100644 index 000000000..d600d7a73 --- /dev/null +++ b/xray/commands/audit/jas/commons_test.go @@ -0,0 +1,129 @@ +package jas + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" + clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" + + "github.com/jfrog/jfrog-cli-core/v2/xray/utils" + "github.com/stretchr/testify/assert" +) + +var createJFrogAppsConfigCases = []struct { + workingDirs []string +}{ + {workingDirs: []string{}}, + {workingDirs: []string{"working-dir"}}, + {workingDirs: []string{"working-dir-1", "working-dir-2"}}, +} + +func TestCreateJFrogAppsConfig(t *testing.T) { + wd, err := os.Getwd() + assert.NoError(t, err) + + for _, testCase := range createJFrogAppsConfigCases { + t.Run(fmt.Sprintf("%v", testCase.workingDirs), func(t *testing.T) { + jfrogAppsConfig, err := createJFrogAppsConfig(testCase.workingDirs) + assert.NoError(t, err) + assert.NotNil(t, jfrogAppsConfig) + if len(testCase.workingDirs) == 0 { + assert.Len(t, jfrogAppsConfig.Modules, 1) + assert.Equal(t, wd, jfrogAppsConfig.Modules[0].SourceRoot) + return + } + assert.Len(t, jfrogAppsConfig.Modules, len(testCase.workingDirs)) + for i, workingDir := range testCase.workingDirs { + assert.Equal(t, filepath.Join(wd, workingDir), jfrogAppsConfig.Modules[i].SourceRoot) + } + }) + } +} + +func TestCreateJFrogAppsConfigWithConfig(t *testing.T) { + wd, err := os.Getwd() + assert.NoError(t, err) + chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, "testdata") + defer chdirCallback() + + jfrogAppsConfig, err := createJFrogAppsConfig([]string{}) + assert.NoError(t, err) + assert.NotNil(t, jfrogAppsConfig) + assert.Equal(t, "1.0", jfrogAppsConfig.Version) + assert.Len(t, jfrogAppsConfig.Modules, 1) +} + +func TestShouldSkipScanner(t *testing.T) { + module := jfrogappsconfig.Module{} + assert.False(t, ShouldSkipScanner(module, utils.IaC)) + + module = jfrogappsconfig.Module{ExcludeScanners: []string{"sast"}} + assert.False(t, ShouldSkipScanner(module, utils.IaC)) + assert.True(t, ShouldSkipScanner(module, utils.Sast)) +} + +var getSourceRootsCases = []struct { + scanner *jfrogappsconfig.Scanner +}{ + {scanner: nil}, + {&jfrogappsconfig.Scanner{WorkingDirs: []string{"working-dir"}}}, + {&jfrogappsconfig.Scanner{WorkingDirs: []string{"working-dir-1", "working-dir-2"}}}, +} + +func TestGetSourceRoots(t *testing.T) { + testGetSourceRoots(t, "source-root") +} + +func TestGetSourceRootsEmptySourceRoot(t *testing.T) { + testGetSourceRoots(t, "") +} + +func testGetSourceRoots(t *testing.T, sourceRoot string) { + sourceRoot, err := filepath.Abs(sourceRoot) + assert.NoError(t, err) + module := jfrogappsconfig.Module{SourceRoot: sourceRoot} + for _, testCase := range getSourceRootsCases { + t.Run("", func(t *testing.T) { + scanner := testCase.scanner + actualSourceRoots, err := GetSourceRoots(module, scanner) + assert.NoError(t, err) + if scanner == nil { + assert.ElementsMatch(t, []string{module.SourceRoot}, actualSourceRoots) + return + } + expectedWorkingDirs := []string{} + for _, workingDir := range scanner.WorkingDirs { + expectedWorkingDirs = append(expectedWorkingDirs, filepath.Join(module.SourceRoot, workingDir)) + } + assert.ElementsMatch(t, actualSourceRoots, expectedWorkingDirs) + }) + } +} + +var getExcludePatternsCases = []struct { + scanner *jfrogappsconfig.Scanner +}{ + {scanner: nil}, + {&jfrogappsconfig.Scanner{WorkingDirs: []string{"exclude-dir"}}}, + {&jfrogappsconfig.Scanner{WorkingDirs: []string{"exclude-dir-1", "exclude-dir-2"}}}, +} + +func TestGetExcludePatterns(t *testing.T) { + module := jfrogappsconfig.Module{ExcludePatterns: []string{"exclude-root"}} + for _, testCase := range getExcludePatternsCases { + t.Run("", func(t *testing.T) { + scanner := testCase.scanner + actualExcludePatterns := GetExcludePatterns(module, scanner) + if scanner == nil { + assert.ElementsMatch(t, module.ExcludePatterns, actualExcludePatterns) + return + } + expectedExcludePatterns := module.ExcludePatterns + expectedExcludePatterns = append(expectedExcludePatterns, scanner.ExcludePatterns...) + assert.ElementsMatch(t, actualExcludePatterns, expectedExcludePatterns) + }) + } +} \ No newline at end of file diff --git a/xray/commands/audit/jas/iac/iacscanner.go b/xray/commands/audit/jas/iac/iacscanner.go index c4ccfdd39..5bf085865 100644 --- a/xray/commands/audit/jas/iac/iacscanner.go +++ b/xray/commands/audit/jas/iac/iacscanner.go @@ -1,9 +1,11 @@ package iac import ( - "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "path/filepath" + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" + "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" + "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/owenrumney/go-sarif/v2/sarif" @@ -48,15 +50,17 @@ func newIacScanManager(scanner *jas.JasScanner) (manager *IacScanManager) { } } -func (iac *IacScanManager) Run(wd string) (err error) { - scanner := iac.scanner - if err = iac.createConfigFile(wd); err != nil { +func (iac *IacScanManager) Run(module jfrogappsconfig.Module) (err error) { + if jas.ShouldSkipScanner(module, utils.IaC) { + return + } + if err = iac.createConfigFile(module); err != nil { return } if err = iac.runAnalyzerManager(); err != nil { return } - workingDirResults, err := jas.ReadJasScanRunsFromFile(scanner.ResultsFileName, wd) + workingDirResults, err := jas.ReadJasScanRunsFromFile(iac.scanner.ResultsFileName, module.SourceRoot) if err != nil { return } @@ -75,14 +79,18 @@ type iacScanConfiguration struct { SkippedDirs []string `yaml:"skipped-folders"` } -func (iac *IacScanManager) createConfigFile(currentWd string) error { +func (iac *IacScanManager) createConfigFile(module jfrogappsconfig.Module) error { + roots, err := jas.GetSourceRoots(module, module.Scanners.Iac) + if err != nil { + return err + } configFileContent := iacScanConfig{ Scans: []iacScanConfiguration{ { - Roots: []string{currentWd}, + Roots: roots, Output: iac.scanner.ResultsFileName, Type: iacScannerType, - SkippedDirs: jas.SkippedDirs, + SkippedDirs: jas.GetExcludePatterns(module, module.Scanners.Iac), }, }, } diff --git a/xray/commands/audit/jas/iac/iacscanner_test.go b/xray/commands/audit/jas/iac/iacscanner_test.go index a2332421d..595715bfd 100644 --- a/xray/commands/audit/jas/iac/iacscanner_test.go +++ b/xray/commands/audit/jas/iac/iacscanner_test.go @@ -1,11 +1,13 @@ package iac import ( - "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "os" "path/filepath" "testing" + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" + "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/stretchr/testify/assert" ) @@ -20,7 +22,7 @@ func TestNewIacScanManager(t *testing.T) { if assert.NotNil(t, iacScanManager) { assert.NotEmpty(t, iacScanManager.scanner.ConfigFileName) assert.NotEmpty(t, iacScanManager.scanner.ResultsFileName) - assert.NotEmpty(t, iacScanManager.scanner.WorkingDirs) + assert.NotEmpty(t, iacScanManager.scanner.JFrogAppsConfig.Modules[0].SourceRoot) assert.Equal(t, &jas.FakeServerDetails, iacScanManager.scanner.ServerDetails) } } @@ -33,7 +35,7 @@ func TestIacScan_CreateConfigFile_VerifyFileWasCreated(t *testing.T) { currWd, err := coreutils.GetWorkingDirectory() assert.NoError(t, err) - err = iacScanManager.createConfigFile(currWd) + err = iacScanManager.createConfigFile(jfrogappsconfig.Module{SourceRoot: currWd}) defer func() { err = os.Remove(iacScanManager.scanner.ConfigFileName) @@ -57,7 +59,7 @@ func TestIacParseResults_EmptyResults(t *testing.T) { // Act var err error - iacScanManager.iacScannerResults, err = jas.ReadJasScanRunsFromFile(iacScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + iacScanManager.iacScannerResults, err = jas.ReadJasScanRunsFromFile(iacScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot) if assert.NoError(t, err) && assert.NotNil(t, iacScanManager.iacScannerResults) { assert.Len(t, iacScanManager.iacScannerResults, 1) assert.Empty(t, iacScanManager.iacScannerResults[0].Results) @@ -73,7 +75,7 @@ func TestIacParseResults_ResultsContainIacViolations(t *testing.T) { // Act var err error - iacScanManager.iacScannerResults, err = jas.ReadJasScanRunsFromFile(iacScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + iacScanManager.iacScannerResults, err = jas.ReadJasScanRunsFromFile(iacScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot) if assert.NoError(t, err) && assert.NotNil(t, iacScanManager.iacScannerResults) { assert.Len(t, iacScanManager.iacScannerResults, 1) assert.Len(t, iacScanManager.iacScannerResults[0].Results, 4) diff --git a/xray/commands/audit/jas/sast/sastscanner.go b/xray/commands/audit/jas/sast/sastscanner.go index 35211396f..ee6b69a67 100644 --- a/xray/commands/audit/jas/sast/sastscanner.go +++ b/xray/commands/audit/jas/sast/sastscanner.go @@ -1,6 +1,9 @@ package sast import ( + "path/filepath" + + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/log" @@ -9,6 +12,7 @@ import ( ) const ( + sastScannerType = "sast" sastScanCommand = "zd" ) @@ -38,12 +42,18 @@ func newSastScanManager(scanner *jas.JasScanner) (manager *SastScanManager) { } } -func (ssm *SastScanManager) Run(wd string) (err error) { +func (ssm *SastScanManager) Run(module jfrogappsconfig.Module) (err error) { + if jas.ShouldSkipScanner(module, utils.Sast) { + return + } + if err = ssm.createConfigFile(module); err != nil { + return + } scanner := ssm.scanner - if err = ssm.runAnalyzerManager(wd); err != nil { + if err = ssm.runAnalyzerManager(filepath.Dir(ssm.scanner.AnalyzerManager.AnalyzerManagerFullPath)); err != nil { return } - workingDirRuns, err := jas.ReadJasScanRunsFromFile(scanner.ResultsFileName, wd) + workingDirRuns, err := jas.ReadJasScanRunsFromFile(scanner.ResultsFileName, module.SourceRoot) if err != nil { return } @@ -51,8 +61,43 @@ func (ssm *SastScanManager) Run(wd string) (err error) { return } +type sastScanConfig struct { + Scans []scanConfiguration `yaml:"scans,omitempty"` +} + +type scanConfiguration struct { + Roots []string `yaml:"roots,omitempty"` + Type string `yaml:"type,omitempty"` + Language string `yaml:"language,omitempty"` + ExcludePatterns []string `yaml:"exclude_patterns,omitempty"` + ExcludedRules []string `yaml:"excluded-rules,omitempty"` +} + +func (ssm *SastScanManager) createConfigFile(module jfrogappsconfig.Module) error { + sastScanner := module.Scanners.Sast + if sastScanner == nil { + sastScanner = &jfrogappsconfig.SastScanner{} + } + roots, err := jas.GetSourceRoots(module, &sastScanner.Scanner) + if err != nil { + return err + } + configFileContent := sastScanConfig{ + Scans: []scanConfiguration{ + { + Type: sastScannerType, + Roots: roots, + Language: sastScanner.Language, + ExcludedRules: sastScanner.ExcludedRules, + ExcludePatterns: jas.GetExcludePatterns(module, &sastScanner.Scanner), + }, + }, + } + return jas.CreateScannersConfigFile(ssm.scanner.ConfigFileName, configFileContent) +} + func (ssm *SastScanManager) runAnalyzerManager(wd string) error { - return ssm.scanner.AnalyzerManager.Exec(ssm.scanner.ResultsFileName, sastScanCommand, wd, ssm.scanner.ServerDetails) + return ssm.scanner.AnalyzerManager.ExecWithOutputFile(ssm.scanner.ConfigFileName, sastScanCommand, wd, ssm.scanner.ResultsFileName, ssm.scanner.ServerDetails) } // In the Sast scanner, there can be multiple results with the same location. diff --git a/xray/commands/audit/jas/sast/sastscanner_test.go b/xray/commands/audit/jas/sast/sastscanner_test.go index 66c423403..a0d27044e 100644 --- a/xray/commands/audit/jas/sast/sastscanner_test.go +++ b/xray/commands/audit/jas/sast/sastscanner_test.go @@ -19,7 +19,7 @@ func TestNewSastScanManager(t *testing.T) { if assert.NotNil(t, sastScanManager) { assert.NotEmpty(t, sastScanManager.scanner.ConfigFileName) assert.NotEmpty(t, sastScanManager.scanner.ResultsFileName) - assert.NotEmpty(t, sastScanManager.scanner.WorkingDirs) + assert.NotEmpty(t, sastScanManager.scanner.JFrogAppsConfig.Modules[0].SourceRoot) assert.Equal(t, &jas.FakeServerDetails, sastScanManager.scanner.ServerDetails) } } @@ -34,7 +34,7 @@ func TestSastParseResults_EmptyResults(t *testing.T) { // Act var err error - sastScanManager.sastScannerResults, err = jas.ReadJasScanRunsFromFile(sastScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + sastScanManager.sastScannerResults, err = jas.ReadJasScanRunsFromFile(sastScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot) // Assert if assert.NoError(t, err) && assert.NotNil(t, sastScanManager.sastScannerResults) { @@ -55,7 +55,7 @@ func TestSastParseResults_ResultsContainIacViolations(t *testing.T) { // Act var err error - sastScanManager.sastScannerResults, err = jas.ReadJasScanRunsFromFile(sastScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + sastScanManager.sastScannerResults, err = jas.ReadJasScanRunsFromFile(sastScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot) // Assert if assert.NoError(t, err) && assert.NotNil(t, sastScanManager.sastScannerResults) { diff --git a/xray/commands/audit/jas/secrets/secretsscanner.go b/xray/commands/audit/jas/secrets/secretsscanner.go index cf5df05f8..2a3c657d5 100644 --- a/xray/commands/audit/jas/secrets/secretsscanner.go +++ b/xray/commands/audit/jas/secrets/secretsscanner.go @@ -4,6 +4,7 @@ import ( "path/filepath" "strings" + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/log" @@ -48,19 +49,21 @@ func newSecretsScanManager(scanner *jas.JasScanner) (manager *SecretScanManager) } } -func (s *SecretScanManager) Run(wd string) (err error) { - scanner := s.scanner - if err = s.createConfigFile(wd); err != nil { +func (ssm *SecretScanManager) Run(module jfrogappsconfig.Module) (err error) { + if jas.ShouldSkipScanner(module, utils.Secrets) { return } - if err = s.runAnalyzerManager(); err != nil { + if err = ssm.createConfigFile(module); err != nil { return } - workingDirRuns, err := jas.ReadJasScanRunsFromFile(scanner.ResultsFileName, wd) + if err = ssm.runAnalyzerManager(); err != nil { + return + } + workingDirRuns, err := jas.ReadJasScanRunsFromFile(ssm.scanner.ResultsFileName, module.SourceRoot) if err != nil { return } - s.secretsScannerResults = append(s.secretsScannerResults, processSecretScanRuns(workingDirRuns)...) + ssm.secretsScannerResults = append(ssm.secretsScannerResults, processSecretScanRuns(workingDirRuns)...) return } @@ -75,14 +78,18 @@ type secretsScanConfiguration struct { SkippedDirs []string `yaml:"skipped-folders"` } -func (s *SecretScanManager) createConfigFile(currentWd string) error { +func (s *SecretScanManager) createConfigFile(module jfrogappsconfig.Module) error { + roots, err := jas.GetSourceRoots(module, module.Scanners.Secrets) + if err != nil { + return err + } configFileContent := secretsScanConfig{ Scans: []secretsScanConfiguration{ { - Roots: []string{currentWd}, + Roots: roots, Output: s.scanner.ResultsFileName, Type: secretsScannerType, - SkippedDirs: jas.SkippedDirs, + SkippedDirs: jas.GetExcludePatterns(module, module.Scanners.Secrets), }, }, } diff --git a/xray/commands/audit/jas/secrets/secretsscanner_test.go b/xray/commands/audit/jas/secrets/secretsscanner_test.go index 14e917e16..bd9ec826d 100644 --- a/xray/commands/audit/jas/secrets/secretsscanner_test.go +++ b/xray/commands/audit/jas/secrets/secretsscanner_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" @@ -29,7 +30,7 @@ func TestSecretsScan_CreateConfigFile_VerifyFileWasCreated(t *testing.T) { currWd, err := coreutils.GetWorkingDirectory() assert.NoError(t, err) - err = secretScanManager.createConfigFile(currWd) + err = secretScanManager.createConfigFile(jfrogappsconfig.Module{SourceRoot: currWd}) assert.NoError(t, err) defer func() { @@ -65,7 +66,7 @@ func TestParseResults_EmptyResults(t *testing.T) { // Act var err error - secretScanManager.secretsScannerResults, err = jas.ReadJasScanRunsFromFile(secretScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + secretScanManager.secretsScannerResults, err = jas.ReadJasScanRunsFromFile(secretScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot) // Assert if assert.NoError(t, err) && assert.NotNil(t, secretScanManager.secretsScannerResults) { @@ -88,7 +89,7 @@ func TestParseResults_ResultsContainSecrets(t *testing.T) { // Act var err error - secretScanManager.secretsScannerResults, err = jas.ReadJasScanRunsFromFile(secretScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0]) + secretScanManager.secretsScannerResults, err = jas.ReadJasScanRunsFromFile(secretScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot) // Assert if assert.NoError(t, err) && assert.NotNil(t, secretScanManager.secretsScannerResults) { diff --git a/xray/commands/audit/jas/testdata/.jfrog/jfrog-apps-config.yml b/xray/commands/audit/jas/testdata/.jfrog/jfrog-apps-config.yml new file mode 100644 index 000000000..9357059d7 --- /dev/null +++ b/xray/commands/audit/jas/testdata/.jfrog/jfrog-apps-config.yml @@ -0,0 +1,50 @@ +# [Required] JFrog Applications Config version +version: "1.0" + +modules: + # [Required] Module name + - name: FrogLeapApp + # [Optional, default: "."] Application's root directory + source_root: "src" + # [Optional] Directories to exclude from scanning across all scanners + exclude_patterns: + - "docs/" + # [Optional] Scanners to exclude from JFrog Advanced Security (Options: "secrets", "sast", "iac") + exclude_scanners: + - secrets + # [Optional] Customize scanner configurations + scanners: + # [Optional] Configuration for Static Application Security Testing (SAST) + sast: + # [Optional] Specify the programming language for SAST + language: java + # [Optional] Working directories specific to SAST (Relative to source_root) + working_dirs: + - "src/module1" + - "src/module2" + # [Optional] Additional exclude patterns for this scanner + exclude_patterns: + - "src/module1/test" + # [Optional] List of specific scan rules to exclude from the scan + excluded_rules: + - xss-injection + + # [Optional] Configuration for secrets scan + secrets: + # [Optional] Working directories specific to the secret scanner (Relative to source_root) + working_dirs: + - "src/module1" + - "src/module2" + # [Optional] Additional exclude patterns for this scanner + exclude_patterns: + - "src/module1/test" + + # [Optional] Configuration for Infrastructure as Code scan (IaC) + iac: + # [Optional] Working directories specific to IaC (Relative to source_root) + working_dirs: + - "src/module1" + - "src/module2" + # [Optional] Additional exclude patterns for this Scanner + exclude_patterns: + - "src/module1/test" \ No newline at end of file diff --git a/xray/utils/analyzermanager.go b/xray/utils/analyzermanager.go index dedcd98e5..cc1d244a7 100644 --- a/xray/utils/analyzermanager.go +++ b/xray/utils/analyzermanager.go @@ -92,10 +92,21 @@ type AnalyzerManager struct { } func (am *AnalyzerManager) Exec(configFile, scanCommand, workingDir string, serverDetails *config.ServerDetails) (err error) { + return am.ExecWithOutputFile(configFile, scanCommand, workingDir, "", serverDetails) +} + +func (am *AnalyzerManager) ExecWithOutputFile(configFile, scanCommand, workingDir, outputFile string, serverDetails *config.ServerDetails) (err error) { if err = SetAnalyzerManagerEnvVariables(serverDetails); err != nil { return err } - cmd := exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile) + var cmd *exec.Cmd + if len(outputFile) > 0 { + log.Debug("Executing", am.AnalyzerManagerFullPath, scanCommand, configFile, outputFile) + cmd = exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile, outputFile) + } else { + log.Debug("Executing", am.AnalyzerManagerFullPath, scanCommand, configFile) + cmd = exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile) + } defer func() { if !cmd.ProcessState.Exited() { if killProcessError := cmd.Process.Kill(); errorutils.CheckError(killProcessError) != nil {